Anpassungen für das Hosting bei Djangoeurope und damit verbundenen Versionen Django 1.8 und Python 2.7

This commit is contained in:
Christian Berg
2015-08-05 18:55:48 +02:00
committed by Christian Berg
parent cb4b15b3c6
commit 37d3cb78c1
1355 changed files with 7289 additions and 6858 deletions

View File

@@ -0,0 +1 @@

21
src/aggregator/admin.py Normal file
View File

@@ -0,0 +1,21 @@
from django.contrib import admin
from models import Feed, FeedItem
admin.site.register(
Feed,
list_display=["title", "public_url", "last_update", 'is_functional'],
list_filter=["is_functional"],
ordering=["title"],
search_fields=["title", "public_url"],
list_per_page=500,
)
admin.site.register(
FeedItem,
list_display=['title', 'feed', 'date_modified'],
list_filter=['feed'],
search_fields=['feed__title', 'feed__public_url', 'title'],
date_heirarchy=['date_modified'],
)

25
src/aggregator/feeds.py Normal file
View File

@@ -0,0 +1,25 @@
import django.contrib.syndication.views
from .models import FeedItem
# noinspection PyMethodMayBeStatic
class LatestFeedItems(django.contrib.syndication.views.Feed):
link = "http://aol.animanga.at/"
description = "Aktuelle Nachrichten aus der Austrian Otaku League"
title = "AOL - Newsfeed"
def items(self):
return FeedItem.objects.get_recent_items()
def item_title(self, item):
return "%s: %s" % (item.feed.title, item.title)
def item_description(self, item):
return item.summary
def item_author_name(self, item):
return item.feed.title
def item_pubdate(self, item):
return item.date_modified

View File

@@ -0,0 +1 @@
# -

View File

@@ -0,0 +1 @@
#

View File

@@ -0,0 +1,21 @@
"""
Update feeds for Django community page. Requires Mark Pilgrim's excellent
Universal Feed Parser (http://feedparser.org)
"""
from django.core.management.base import BaseCommand
from aggregator.models import Feed
class Command(BaseCommand):
help = "Updates all RSS Feeds"
def handle(self, *args, **options):
verbose = int(options['verbosity']) > 0
for feed in Feed.objects.filter(is_functional=True):
if verbose:
print
print "%s - URL: %s" % (feed.title, feed.feed_url)
print '=' * 80
feed.parse()

114
src/aggregator/models.py Normal file
View File

@@ -0,0 +1,114 @@
"""
Created on 05.02.2011
@author: christian
"""
from datetime import datetime, timedelta
import HTMLParser
import urllib2
from django.conf import settings
from django.contrib.sites.models import Site
from django.utils import timezone
import django.db.models
import feedparser
from utils.html5 import models
class FeedManager(django.db.models.Manager):
def active(self):
site = settings.SITE_ID
feeds = self.filter(is_functional=True, site=site)
for feed in feeds:
if feed.last_update:
feed_age = timezone.now() - feed.last_update
if feed_age > timedelta(hours=12):
feed.parse()
else:
feed.parse()
return feeds
class FeedItemManager(django.db.models.Manager):
def recent_items(self, max_items=10, site=None):
site = site or settings.SITE_ID
return self.select_related().filter(feed__site=site)[:max_items]
class Feed(django.db.models.Model):
title = models.CharField(max_length=500)
site = models.ForeignKey(Site)
feed_url = models.URLField(unique=True, max_length=255)
public_url = models.URLField(max_length=255)
last_update = models.DateTimeField(blank=True, null=True)
is_functional = models.BooleanField(default=True)
objects = FeedManager()
def __unicode__(self):
return self.title
def parse(self):
parsed_feed = feedparser.parse(self.feed_url)
html_parser = HTMLParser.HTMLParser()
if parsed_feed.bozo and type(
parsed_feed.bozo_exception) == urllib2.URLError:
self.is_functional = False
return self.save()
for feed_entry in parsed_feed.entries:
title = html_parser.unescape(feed_entry.title)
if not title:
continue
link = feed_entry.link
guid = feed_entry.get("id", link)
summary = html_parser.unescape(
feed_entry.get("summary", feed_entry.get(
"description",
feed_entry.get("content", u"")
))
)
date_modified = feed_entry.get(
"published_parsed",
parsed_feed.get("published_parsed",
timezone.now))
date_modified = timezone.make_aware(
datetime(*date_modified[:6]),
timezone.get_current_timezone())
feed_item, updated = self.feed_items.get_or_create(
guid=guid,
defaults={
'title': title,
'link': link,
'summary': summary,
'date_modified': date_modified
})
feed_item.save()
self.last_update = timezone.now()
return self.save()
class Meta:
ordering = ("title",)
class FeedItem(django.db.models.Model):
feed = models.ForeignKey(Feed, related_name='feed_items')
title = models.CharField(max_length=500)
link = models.URLField(max_length=500)
guid = models.CharField(max_length=255, unique=True, db_index=True)
summary = models.TextField(blank=True)
date_modified = models.DateTimeField()
objects = FeedItemManager()
class Meta:
ordering = ("-date_modified",)
def __unicode__(self):
return self.title
def get_absolute_url(self):
return self.link

View File

@@ -0,0 +1,16 @@
from django.contrib.sitemaps import Sitemap
from models import FeedItem
# noinspection PyMethodMayBeStatic
class FeedItemSitemap(Sitemap):
changefreq = "never"
priority = 0.5
def items(self):
return FeedItem.objects.get_recent_items()
def lastmod(self, obj):
return obj.date_modified

View File

@@ -0,0 +1,31 @@
from django import template
from models import Feed
class FeedListNode(template.Node):
def __init__(self, varname):
self.varname = varname
def render(self, context):
context[self.varname] = Feed.objects.filter(is_defunct=False)
return ''
# noinspection PyUnusedLocal
def do_get_feed_list(parser, token):
"""
{% get_feed_list as feed_list %}
"""
bits = token.contents.split()
if len(bits) != 3:
raise template.TemplateSyntaxError, \
"'%s' tag takes two arguments" % bits[0]
if bits[1] != "as":
raise template.TemplateSyntaxError, \
"First argument to '%s' tag must be 'as'" % bits[0]
return FeedListNode(bits[2])
register = template.Library()
register.tag('get_feed_list', do_get_feed_list)

0
src/content/__init__.py Normal file
View File

64
src/content/admin.py Normal file
View File

@@ -0,0 +1,64 @@
"""
Created on 19.09.2011
@author: christian
"""
# import stuff we need from django
from django.contrib import admin
from . import models
class PageTabularInline(admin.TabularInline):
fields = ('title_de', 'menu_name_de', "position",)
model = models.Page
sortable_field_name = "position"
class ArticleAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("headline_de",)}
list_display = ('headline', 'category', 'date_created', 'author',)
list_editable = ('category', 'author')
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'description']
list_display_links = ('name', 'slug',)
prepopulated_fields = {'slug': ('name_de',)}
class PageAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ('menu_name_de',)}
inlines = [PageTabularInline, ]
list_display = ('position', 'menu_name', 'title', 'parent', 'path',)
list_display_links = ('menu_name', 'title',)
list_editable = ('position',)
list_filter = ('parent',)
search_fields = ('menu_name_de', 'title_de',)
fieldsets = (
('Deutsch', {
'fields': ('menu_name_de', 'title_de', 'pdf_de', 'content_de',),
'classes': ('grp-collapse grp-open',),
}),
('English', {
'fields': ('menu_name_en', 'title_en', 'pdf_en', 'content_en'),
'classes': ('grp-collapse grp-closed',),
}),
('Meta Data', {
'fields': (
'content_type', 'slug', ('parent', 'position'),
'status', 'template',
)
}),
)
class Media(object):
js = [
'/static/grappelli/tinymce/jscripts/tiny_mce/tiny_mce.js',
'/static/js/tinymce_setup.js',
]
admin.site.register(models.Article, ArticleAdmin)
admin.site.register(models.Page, PageAdmin)
admin.site.register(models.Category, CategoryAdmin)

View File

@@ -0,0 +1,50 @@
# -*- encoding: UTF-8 -*-
"""
Created on 30.09.2011
@author: christian
"""
from django.core.cache import cache
from . import models
def content_menus(request):
current_page = None
current_top_page = None
current_path = request.path_info[1:request.path_info.rfind('.')]
# erzeuge das Top-Level Menü
top_menu_items = []
top_level_pages = cache.get('top_level_pages')
if top_level_pages is None:
top_level_pages = models.Page.objects.filter(parent=None)
top_level_pages = top_level_pages.exclude(slug='index')
top_level_pages = top_level_pages.order_by('position')
cache.set('top_level_pages', top_level_pages, 360)
for item in top_level_pages:
if current_path.startswith(item.path):
item.active = True
current_top_page = item
else:
item.active = False
top_menu_items.append(item)
# Entdecke die aktuell geöffnete Seite
all_pages = cache.get('all_pages')
if all_pages is None:
all_pages = models.Page.objects.values_list('path', 'id')
all_pages = dict((path, page_id) for path, page_id in all_pages)
cache.set('all_pages', all_pages, 360)
while len(current_path) > 0:
if current_path in all_pages:
current_page = models.Page.objects.get(pk=all_pages[current_path])
break
current_path = current_path[0:current_path.rfind('.')]
return {'top_menu_items': top_menu_items,
'current_top_page': current_top_page,
'current_path': current_path,
'current_page': current_page
}

68
src/content/feeds.py Normal file
View File

@@ -0,0 +1,68 @@
from datetime import datetime, time
from django.conf import settings
from django.utils.translation import ugettext as _
import django_comments as comments
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import Rss201rev2Feed
from models import Article
# noinspection PyMethodMayBeStatic
class LatestNews(Feed):
link = "http://www.kasu.at/"
description = _("Current news from Kasu")
title = "Kasu - traditonelle asiatische Spielkultur"
feed_type = Rss201rev2Feed
def items(self):
return Article.objects.published()[:10]
def item_title(self, item):
return item.headline
def item_author_name(self, item):
return item.author.username
def item_categories(self, item):
return item.category.name,
def item_description(self, item):
return item.content
def item_pubdate(self, item):
return datetime.combine(item.date_created, time())
# noinspection PyMethodMayBeStatic
class LatestComments(Feed):
"""Feed of latest comments on the current site."""
link = "http://www.kasu.at/"
description = _("Latest comments on kasu.at")
title = _("Kasu - latest comments")
feed_type = Rss201rev2Feed
def items(self):
qs = comments.get_model().objects.filter(
site__pk=settings.SITE_ID,
is_public=True, is_removed=False
)
return qs.order_by('-submit_date')[:40]
def item_author_name(self, item):
return item.user_name
def item_description(self, item):
return item.comment
def item_pubdate(self, item):
return item.submit_date
def item_title(self, item):
return 'From %(user_name)s in %(content_object)s' % {
'user_name': item.user_name,
'content_object': item.content_object
}

61
src/content/forms.py Normal file
View File

@@ -0,0 +1,61 @@
"""
Created on 04.10.2011
@author: christian
"""
import django.forms
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext as _
from utils.html5 import forms
from . import models
class ArticleForm(forms.ModelForm):
error_css_class = 'error'
required_css_class = 'required'
class Meta(object):
fields = (
'headline_de', 'content_de',
'headline_en', 'content_en',
'category',
'image'
)
model = models.Article
def save(self, force_insert=False, force_update=False, commit=True):
article = super(ArticleForm, self).save(commit=False)
article.slug = slugify(article.headline_de)[:50]
if commit:
article.save(force_insert=force_insert, force_update=force_update)
return article
class PageForm(forms.ModelForm):
error_css_class = 'error'
required_css_class = 'required'
content_type = django.forms.ChoiceField(
choices=models.CONTENT_CHOICES,
widget=django.forms.RadioSelect
)
class Meta(object):
exclude = ('position',)
model = models.Page
def clean(self):
cleaned_data = super(PageForm, self).clean()
content_type = cleaned_data.get("content_type")
pdf_de = cleaned_data.get("pdf_de")
if not pdf_de and content_type == "2":
msg = _('Please upload a PDF-File to this PDF-Page.')
self._errors["content_type"] = self.error_class([msg])
self._errors["pdf_de"] = self.error_class([msg])
# These fields are no longer valid. Remove them from the
# cleaned data.
del cleaned_data["content_type"]
del cleaned_data["pdf_de"]
# Always return the full collection of cleaned data.
return cleaned_data

Binary file not shown.

View File

@@ -0,0 +1,249 @@
# 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: kasu.content\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-05 19:23+0100\n"
"PO-Revision-Date: 2014-12-08 16:15+0100\n"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Translated-Using: django-rosetta 0.7.2\n"
"X-Generator: Poedit 1.6.11\n"
#: feeds.py:16
msgid "Current news from Kasu"
msgstr "Aktuelle Nachrichten von Kasu"
#: feeds.py:44
msgid "Latest comments on kasu.at"
msgstr "Neueste Kommentare auf Kasu.at "
#: feeds.py:45
msgid "Kasu - latest comments"
msgstr "Kasu - neue Kommentare"
#: forms.py:52 models.py:201
msgid "Please upload a PDF-File to this PDF-Page."
msgstr "Bitte eine PDF Datei für diese PDF Seite hochladen."
#: models.py:48
msgid "Headline"
msgstr "Schlagzeile"
#: models.py:50
msgid "Content"
msgstr "Inhalt"
#: models.py:52 models.py:231 templates/content/article_detail.html:34
msgid "Category"
msgstr "Kategorie"
#: models.py:53 models.py:225
msgid "Image"
msgstr "Bild"
#: models.py:55 models.py:227
msgid "Slug"
msgstr "Slug"
#: models.py:57 templates/content/article_detail.html:32
msgid "Author"
msgstr "Autor"
#: models.py:58
msgid "Status"
msgstr "Status"
#: models.py:60
msgid "Created"
msgstr "Erstellt"
#: models.py:61
msgid "Modified"
msgstr "Bearbeitet"
#: models.py:65
msgid "Article"
msgstr "Artikel"
#: models.py:66
msgid "Articles"
msgstr "Artikel"
#: models.py:123 models.py:129
msgid "The short name for the menu-entry of this page"
msgstr "Ein kurzer Name für den Menüeintrag"
#: models.py:133 models.py:136
msgid "This title appears in the HTML header"
msgstr "Der Titel erscheint im HTML Header"
#: models.py:137
msgid "slug"
msgstr "Slug"
#: models.py:138
msgid "Path"
msgstr "Pfad"
#: models.py:144
msgid "Position"
msgstr "Position"
#: models.py:146
msgid "status"
msgstr "Status"
#: models.py:152
msgid "enable comments"
msgstr "Kommentare möglich"
#: models.py:153
msgid "Template"
msgstr "Vorlage"
#: models.py:216
msgid "Page"
msgstr "Seite"
#: models.py:217
msgid "Pages"
msgstr "Seiten"
#: models.py:221 models.py:222
msgid "Name"
msgstr "Name"
#: models.py:223 models.py:224
msgid "Description"
msgstr "Beschreibung"
#: models.py:232
msgid "Categories"
msgstr "Kategorien"
#: views.py:42
msgid "This Category does not exist."
msgstr "Diese Kategorie existiert nicht."
#: views.py:93 templates/content/article_detail.html:52
#: templates/content/article_form.html:17
msgid "Edit Article"
msgstr "Artikel bearbeiten"
#: views.py:95 templates/content/article_form.html:17
msgid "Create Article"
msgstr "Artikel erstellen"
#: views.py:160
#, python-format
msgid "No Page found matching the Path %s"
msgstr "Keine Seite unter dem Pfad %s gefunden"
#: views.py:174
#, python-format
msgid "No PDF Document found matching the Path %s"
msgstr "Kein PDF Dokument unter dem Pfad %s gefunden."
#: templates/content/article_archive.html:5
#: templates/content/article_archive.html:11
#: templates/content/article_archive.html:17
msgid "Article Archive"
msgstr "Nachrichtenarchiv"
#: templates/content/article_archive.html:32
#: templates/content/article_archive_month.html:5
#: templates/content/article_archive_year.html:7
msgid "Archive"
msgstr "Archiv"
#: templates/content/article_archive.html:49
msgid "All Categories"
msgstr "Alle Kategorien"
#: templates/content/article_archive.html:64
msgid "created on"
msgstr "erstellt am"
#: templates/content/article_archive.html:65
msgid "by"
msgstr "von"
#: templates/content/article_archive.html:66
msgid "comments"
msgstr "Kommentare"
#: templates/content/article_archive.html:70
msgid "Read More"
msgstr "Mehr lesen"
#: templates/content/article_archive.html:73
msgid "We're sorry. Your search yielded no results."
msgstr "Es tut uns leid. Deine Suche ergab keine Treffer."
#: templates/content/article_archive.html:91
msgid "Add Article"
msgstr "neuer Artikel "
#: templates/content/article_archive_month.html:7
msgid "back"
msgstr "Zurück"
#: templates/content/article_detail.html:33
msgid "Created on"
msgstr "Erstellt am"
#: templates/content/article_detail.html:39
msgid "Share on Google+"
msgstr "Auf Google+ teilen"
#: templates/content/article_detail.html:40
msgid "Share on Twitter"
msgstr "Auf Twitter teilen"
#: templates/content/article_detail.html:41
msgid "Share on Facebook"
msgstr "Auf Facebook teilen"
#: templates/content/article_form.html:22 templates/content/page_form.html:40
#: templates/content/page_form.html:46
msgid "German"
msgstr "Deutsch"
#: templates/content/article_form.html:23 templates/content/page_form.html:41
#: templates/content/page_form.html:50
msgid "English"
msgstr "Englisch"
#: templates/content/article_form.html:36 templates/content/page_form.html:61
msgid "reset"
msgstr "Zurücksetzen"
#: templates/content/article_form.html:37 templates/content/page_form.html:62
msgid "save"
msgstr "Speichern"
#: templates/content/page_form.html:5 templates/content/page_form.html:35
msgid "Edit Page"
msgstr "Seite bearbeiten"
#: templates/content/page_form.html:6 templates/content/page_form.html:20
#: templates/content/page_form.html:35
msgid "Add Page"
msgstr "Seite hinzufügen"
#: templates/content/page_form.html:56
msgid "HTML Specific"
msgstr "HTML spezifisch"
#~ msgid "Subpages"
#~ msgstr "Unterseiten"

View File

@@ -0,0 +1 @@
#

View File

@@ -0,0 +1 @@
#!/usr/bin/python

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
import re
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from django.template.defaultfilters import slugify
from django.utils.datetime_safe import datetime
from content.models import Article, Category
import xlrd
# noinspection PyPep8
class Command(BaseCommand):
help = "Importiert die alten Daten aus einer CSV Datei" # @ReservedAssignment
date_header_regex = r"""<h1><span class=\"small\">(?P<date>[\d\.]*)[\ -]*</span>[\ -]*(?P<title>.*)</h1>(?P<content>.*)"""
header_regex = r"""<h1>[\ -]*(?P<title>.*)</h1>(?P<content>.*)"""
def __init__(self):
self.author = get_user_model().objects.get(username="xeniac")
self.category = Category.objects.get(slug='allgemeines')
super(Command, self).__init__()
def create_article(self):
self.slug = slugify(self.headline[:50])
article, created = Article.objects.get_or_create(slug=self.slug,
date_created=self.date_created,
defaults={
'author': self.author,
'headline_de': self.headline,
'content_de': self.content,
'category': self.category
})
if created:
print "Created: %s - %s" % (self.date_created, self.headline)
article.clean()
article.save()
def parse_with_date(self, original):
match_obj = re.search(self.date_header_regex, original,
re.IGNORECASE | re.DOTALL)
if match_obj:
self.date_created = datetime.strptime(match_obj.group('date'),
'%d.%m.%Y')
self.headline = match_obj.group('title').strip()
self.content = match_obj.group('content').strip()
return True
else:
return False
def parse_without_date(self, original):
match_obj = re.search(self.header_regex, original,
re.IGNORECASE | re.DOTALL)
if match_obj:
self.date_created = datetime.strptime('01.01.1982', '%d.%m.%Y')
self.headline = match_obj.group('title').strip()
self.content = match_obj.group('content').strip()
return True
else:
return False
def handle(self, *args, **options):
try:
xls_file = xlrd.open_workbook(args[0])
except IndexError:
print "Bitte den Pfad zur CSV Datei angeben!"
return False
except IOError:
print "Datei '%s' wurde nicht gefunden! " % args[0]
return False
table = xls_file.sheet_by_index(0)
for row in xrange(1, table.nrows):
if not table.cell_value(row, 2) in ('Archiv', 'News'):
continue
original = table.cell_value(row, 3)
if self.parse_with_date(original) or self.parse_without_date(
original):
self.create_article()
else:
print "Fehler bei String!"
print table.cell_value(row, 3)

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from django.utils.datetime_safe import datetime
from events.models import Event, Location
import xlrd
class Command(BaseCommand):
help = "Importiert die alten Events" # @ReservedAssignment
def __init__(self):
self.author = get_user_model().objects.get(username="xeniac")
super(Command, self).__init__()
def handle(self, *args, **options):
try:
xls_file = xlrd.open_workbook(args[0])
except IndexError:
return "Bitte den Pfad zur Excel Datei angeben!"
except IOError:
return "Datei '%s' wurde nicht gefunden! " % args[0]
table = xls_file.sheet_by_index(0)
for row in xrange(1, table.nrows):
name = table.cell_value(row, 0)
print name
start = xlrd.xldate_as_tuple(table.cell_value(row, 1),
xls_file.datemode)
start = datetime(start[0], start[1], start[2], 0, 0, 0)
end = xlrd.xldate_as_tuple(table.cell_value(row, 2),
xls_file.datemode)
end = datetime(end[0], end[1], end[2], 23, 59, 59)
location = Location.objects.get(pk=table.cell_value(row, 3))
Event.objects.get_or_create(name=name, start=start, defaults={
'end': end,
'location': location,
})

259
src/content/models.py Normal file
View File

@@ -0,0 +1,259 @@
# -*- coding: utf-8 -*-
from os import path
from django.conf import settings
from django.utils.timezone import now
from django.core.urlresolvers import reverse
from django.core.cache import cache
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext as _
from django.core.exceptions import ValidationError
from kasu.image_models import ImageModel
from utils import STATUS_CHOICES, STATUS_WAITING, STATUS_PUBLISHED, \
cleaner
CONTENT_CHOICES = (
(0, u'Django View'),
(1, u'HTML'),
(2, u'PDF')
)
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, Category):
return "categories/%s.%s" % (instance.slug, extension)
class ArticleManager(models.Manager):
def published(self):
return self.filter(status=STATUS_PUBLISHED, date_created__lte=now())
class Article(ImageModel):
headline_de = models.CharField(_('Headline'), max_length=255)
headline_en = models.CharField('Headline', max_length=255, blank=True)
content_de = models.TextField(_('Content'))
content_en = models.TextField('Content', blank=True)
category = models.ForeignKey('Category', verbose_name=_('Category'))
image = models.ImageField(_('Image'), upload_to='news/',
blank=True, null=True)
slug = models.SlugField(_('Slug'), unique_for_month='date_created')
author = models.ForeignKey(settings.AUTH_USER_MODEL,
verbose_name=_('Author'))
status = models.SmallIntegerField(_('Status'), choices=STATUS_CHOICES,
default=STATUS_PUBLISHED)
date_created = models.DateTimeField(_('Created'), blank=True)
date_modified = models.DateTimeField(_('Modified'), auto_now=True)
objects = ArticleManager()
class Meta(object):
verbose_name = _('Article')
verbose_name_plural = _('Articles')
ordering = ('-date_created',)
def clean(self):
if not self.date_created:
self.date_created = now()
if not self.slug:
self.slug = slugify(self.headline_de)[:50]
self.content_de = cleaner.clean_html(self.content_de)
self.content_en = cleaner.clean_html(self.content_en)
def __unicode__(self):
return self.headline
@property
def posting_image(self):
if self.image:
return self.article
else:
return self.category.article
def get_absolute_url(self):
kwargs = {
'year': self.date_created.strftime('%Y'),
'month': self.date_created.strftime('%m'),
'slug': self.slug,
}
return reverse('show-article', kwargs=kwargs)
@property
def headline(self):
headline = getattr(self, "headline_%s" % get_language())
if not headline:
return mark_safe(self.headline_de)
else:
return mark_safe(headline)
@property
def content(self):
content = getattr(self, "content_%s" % get_language(), self.content_de)
if not content:
return mark_safe(self.content_de)
else:
return mark_safe(content)
class Page(models.Model):
"""
Eine Seite auf der Homepage. Sie kann eine "statische" HTML Seite,
die URL einer dynamische Django View, oder ein PDF Dokument sein.
Jede Seite kann neben Deutsch auch auf Englisch angeboten werden.
Ist keine englische Übersetzung vorhanden, wird die deutsche Version
angeboten.
"""
menu_name_de = models.CharField(
'Menü Name',
max_length=255,
help_text=_('The short name for the menu-entry of this page')
)
menu_name_en = models.CharField(
'Menu Name',
max_length=255,
blank=True,
help_text=_('The short name for the menu-entry of this page')
)
title_de = models.CharField('Titel', max_length=255,
help_text=_(
'This title appears in the HTML header'))
title_en = models.CharField('Title', max_length=255, blank=True,
help_text=_(
'This title appears in the HTML header'))
slug = models.SlugField(_('slug'))
path = models.CharField(_('Path'), max_length=100, db_index=True,
editable=False, unique=True)
parent = models.ForeignKey('self', blank=True, null=True,
related_name='subpages',
on_delete=models.SET_NULL)
position = models.PositiveSmallIntegerField(_('Position'),
blank=True, null=True)
status = models.SmallIntegerField(_('status'), choices=STATUS_CHOICES,
default=STATUS_WAITING)
content_type = models.IntegerField(choices=CONTENT_CHOICES)
content_de = models.TextField('Inhalt', blank=True)
content_en = models.TextField('Content', blank=True)
enable_comments = models.BooleanField(_('enable comments'), default=True)
template = models.CharField(_('Template'), max_length=100,
default="content/page.html")
pdf_de = models.FileField(upload_to='pdf/de/', blank=True, null=True)
pdf_en = models.FileField(upload_to='pdf/en/', blank=True, null=True)
def __unicode__(self):
return u'%s' % self.title
@property
def content(self):
cont = getattr(self, "content_%s" % get_language()) or self.content_de
return mark_safe(cont)
@property
def css_class(self):
return CONTENT_CHOICES[self.content_type][1].lower().replace(' ', '_')
@property
def menu_name(self):
return getattr(self,
"menu_name_%s" % get_language()) or self.menu_name_de
@property
def pdf_file(self):
return getattr(self, "pdf_%s" % get_language()) or self.pdf_de
@property
def title(self):
return getattr(self, "title_%s" % get_language()) or self.title_de
def clean(self):
if self.parent is None:
self.path = self.slug
else:
self.path = path.join(self.parent.path, self.slug)
if self.content_type is None:
if self.pdf_de:
self.content_type = 2
if self.content_de:
self.content_type = 1
else:
self.content_type = 0
if self.content_type == 1:
self.content_de = cleaner.clean_html(self.content_de)
self.content_en = cleaner.clean_html(self.content_en)
elif self.content_type == 2 and not self.pdf_de.name:
raise ValidationError(
_(u'Please upload a PDF-File to this PDF-Page.'))
def get_absolute_url(self):
aboslute_url = '/' + self.path
if self.content_type == 1:
aboslute_url += '.html'
elif self.content_type == 2:
aboslute_url += '.pdf'
else:
aboslute_url += '/'
return aboslute_url
class Meta(object):
ordering = ['parent__id', 'position']
unique_together = (('slug', 'parent'),)
verbose_name = _('Page')
verbose_name_plural = _('Pages')
class Category(ImageModel):
name_de = models.CharField(_('Name'), max_length=80)
name_en = models.CharField(_('Name'), max_length=80, blank=True)
description_de = models.TextField(_('Description'))
description_en = models.TextField(_('Description'), blank=True)
image = models.ImageField(_('Image'), upload_to='news/categories/',
blank=True, null=True)
slug = models.SlugField(_('Slug'), unique=True, db_index=True)
class Meta(object):
ordering = ('slug',)
verbose_name = _('Category')
verbose_name_plural = _('Categories')
@property
def name(self):
return getattr(self, "name_%s" % get_language(), self.name_de)
@property
def description(self):
return getattr(self, "description_%s" % get_language(),
self.description_de)
def get_absolute_url(self):
return reverse('article-archive', kwargs={'category': self.slug})
def __unicode__(self):
return self.name
def force_cache_update(sender, instance, **kwargs):
for page in instance.subpages.all():
page.clean()
page.save()
cache.delete('all_pages')
cache.delete('top_level_pages')
models.signals.post_delete.connect(force_cache_update, sender=Page)
models.signals.post_save.connect(force_cache_update, sender=Page)

30
src/content/news_urls.py Normal file
View File

@@ -0,0 +1,30 @@
# -*- encoding: utf-8 -*-
"""
Created on 03.10.2011
@author: christian
"""
from django.conf.urls import * # @UnusedWildImport
from .views import ArticleArchiveIndex, ArticleForm, ArticleYearArchive, \
ArticleMonthArchive, ArticleDetail
urlpatterns = patterns(
'content.views',
url(r'^$', ArticleArchiveIndex.as_view(), name='article-archive'),
url(r'^add/$', ArticleForm.as_view(), name='add-article'),
url(r'^edit/(?P<pk>[\d]+)/$', ArticleForm.as_view(), name='edit-article'),
url(r'^(?P<year>[\d]{4})/$', ArticleYearArchive.as_view(),
name='article-archive'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/$', ArticleMonthArchive.as_view(),
name='article-archive'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/(?P<slug>[\-\d\w]+)/$',
ArticleDetail.as_view(), name='show-article'),
url(r'^(?P<category>[\-\d\w]+)/$', ArticleArchiveIndex.as_view(),
name='article-archive'),
url(r'^(?P<category>[\-\d\w]+)/(?P<year>[\d]{4})/$',
ArticleYearArchive.as_view(), name='article-archive'),
url(r'^(?P<category>[\-\d\w]+)/(?P<year>[\d]{4})/(?P<month>[\d]+)/$',
ArticleMonthArchive.as_view(), name='article-archive'),
)

View File

@@ -0,0 +1,93 @@
{% extends "base.html" %}
{% load i18n comments %}
{% block title %}
{% trans 'Article Archive' %}
{% if active_category %} - {{active_category.name}}{% endif %}
{% if month %}{{ month|date:'F Y' }}</h2>{% elif year %}{{year}}{% endif %}
{% endblock %}
{% block meta_title %}
{% trans 'Article Archive' %}
{% if active_category %} - {{active_category.name}}{% endif %}
{% if month %}{{ month|date:'F Y' }}</h2>{% elif year %}{{year}}{% endif %}
{% endblock %}
{% block teaser %}<h2>
{% trans 'Article Archive' %}
{% if active_category %} - {{active_category.name}}{% endif %}
{% if month %}{{ month|date:'F Y' }}{% elif year %}{{year}}{% endif %}
</h2>
<div id="teaser_text">
{% if active_category %}
<p>{{ active_category.description }}</p>
{% else %}
{{current_page.content|safe}}
{% endif %}
</div>
{% endblock %}
{% block redbox %}
{% block date_list %}
<h2>{% trans "Archive" %} {{year}}</h2>
<ul class="list" style="margin: 20px;">
{% if active_category %}
{% for date in date_list %}
<li class="date"><a href="{% url 'article-archive' category=active_category.slug year=date|date:'Y' %}">{{active_category.name}}: {{ date|date:'Y' }}</a></li>
{% endfor %}
{% else %}
{% for date in date_list %}
<li class="date"><a href="{% url 'article-archive' year=date|date:'Y' %}">{{ date|date:'Y' }}</a></li>
{% endfor %}
{% endif %}
</ul>
{% endblock %}
{% endblock %}
{% block navigation %}
<ul id="navigation">
<li><a href="{{current_top_page.get_absolute_url}}" {% if not active_category %}class="active"{% endif %}>{% trans 'All Categories' %}</a></li>
{% for category in categories %}
<li><a href="{% url 'article-archive' category=category.slug %}"
{% ifequal category.slug active_category.slug %}class="active"{% endifequal %}>{{ category.name }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% block maincontent %}
<div class="grid_8">
{% for article in article_list %}
{% get_comment_count for article as comment_count %}
<article class="article">
<h3><a href="{{article.get_absolute_url}}">{{article.headline}}</a></h3>
<ul class="info">
<li><img src="{{STATIC_URL}}icons/calendar.png" alt="{% trans 'created on' %}:" title="{% trans 'created on' %}"/> <time datetime="{{article.date_created|date:'c'}}">{{ article.date_created|date }}</time></li>
<li><img src="{{STATIC_URL}}icons/user_red.png" alt="{% trans 'by' %}:" title="{% trans 'by' %}"/> <span class="author">{{ article.author }}</span></li>
<li><img src="{{STATIC_URL}}icons/comments.png" alt="{% trans 'comments' %}:" title="{% trans 'comments' %}"/> <a href="{{article.get_absolute_url}}#comments" >{{comment_count}} {% trans "comments" %}</a></li>
</ul>
<a href="{{article.get_absolute_url}}"><img src="{{article.posting_image.url}}" alt="{{article.category}}:" class="posting_image"/></a>
{{article.content|truncatewords_html:50}}
<p class="more_link"><a href="{{article.get_absolute_url}}" class="button">{% trans "Read More"%} <img src="{{STATIC_URL}}icons/page_go.png" alt="&raquo;" /></a></p>
</article>
{% empty %}
<p>{% trans "We're sorry. Your search yielded no results." %}</p>
{% endfor %}
</div>
<aside class="grid_4">
{% for feed in feeds %}
<h2><a href="{{feed.public_url}}">{{feed.title}}</a></h2>
<ul>
{% for item in feed.feed_items.all|slice:":10" %}
<li><a href="{{item.link}}">{{item.title}}</a></li>
{% endfor %}
</ul>
{% endfor %}
</aside>
{% endblock %}
{% block additional_buttonbar %}
{% if perms.content.add_article %}
<a href="{% url 'add-article' %}" class="button"><img src="{{ STATIC_URL }}icons/note_add.png" alt="" /> {% trans "Add Article" %}</a>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,9 @@
{% extends "content/article_archive.html" %}
{% load i18n %}
{% block date_list %}
<h2>{% trans "Archive" %} {{month|date:'E'}}</h2>
<div class="buttonbar">
<a href="{% url 'article-archive' year=month|date:'Y' %}" class="button"><img src="{{ STATIC_URL }}icons/arrow_undo.png" alt="{% trans 'back' %}" /> {% trans 'back' %}</a>
</div>
{% endblock %}

View File

@@ -0,0 +1,20 @@
{% extends "content/article_archive.html" %}
{% load i18n %}
{% block meta_title %}{{ article.headline }}{% endblock %}
{% block date_list %}
<h2>{% trans "Archive" %} {{year}}</h2>
<ul class="list" style="margin: 20px;">
{% if active_category %}
{% for date in date_list %}
<li class="date"><a href="{% url 'article-archive' category=active_category.slug year=year|date:'Y' month=date|date:'m' %}">{{active_category.name}}: {{ date|date:'F' }}</a></li>
{% endfor %}
{% else %}
{% for date in date_list %}
<li class="date"><a href="{% url 'article-archive' year=year|date:'Y' month=date|date:'m' %}">{{ date|date:'F' }}</a></li>
{% endfor %}
{% endif %}
</ul>
{% endblock %}

View File

@@ -0,0 +1,54 @@
{% extends "base.html" %}
{% load i18n comments %}
{% block title %}{{ article.headline }}{% endblock %}
{% block description %}{{article.content|striptags|truncatewords:16}}{% endblock %}
{% block opengraph %}
<meta property="og:type" content="article" />
<meta property="og:title" content="{{ article.headline|force_escape }}" />
<meta property="og:url" content="http://www.kasu.at{{ article.get_absolute_url }}" />
<meta property="og:image" content="http://www.kasu.at{{article.posting_image.url}}" />
<meta property="og:description" content="{{article.content|striptags|truncatewords:25|force_escape}}" />
<link rel="image_src" type="image/jpeg" href="{{article.posting_image.url}}" />
{% endblock %}
{% block itemscope %}itemscope itemtype="http://schema.org/Article"{% endblock %}
{% block teaser %}<h2 itemprop="name">{{article.headline}}</h2>{% endblock %}
{% block content %}
<p>&nbsp;</p>
<div itemprop="articleBody">{{ article.content }}</div>
{% endblock %}
{% block sidebar %}
<p>&nbsp;</p>
<img alt="{{article.category.name}}" src="{{article.posting_image.url}}" class="posting_image" itemprop="image"/>
<p>&nbsp;</p>
<ul class="info">
<li class="user"><strong>{% trans 'Author' %}:</strong> <a href="{% url 'membership-details' article.author %}" itemprop="author">{{article.author}}</a></li>
<li class="date"><strong>{% trans 'Created on' %}: </strong><time datetime="{{article.date_created|date:'Y-m-d H:i'}}">{{ article.date_created|date }}</time></li>
<li class="category"><strong>{% trans "Category"%}: </strong><a href="{{ article.category.get_absolute_url }}" itemprop="articleSection">{{article.category.name}}</a></li>
</ul>
<p class="center">
<a href="https://plus.google.com/share?url=http%3A%2F%2Fwww.kasu.at{{article.get_absolute_url|urlencode}}" onclick="javascript:window.open(this.href,
'', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;"><img src="{{STATIC_URL}}img/google_plus.png" alt="Google+" title="{% trans 'Share on Google+'%}" width="39" height="39"/></a>
<a href="https://twitter.com/share?url=http%3A%2F%2Fwww.kasu.at{{article.get_absolute_url|urlencode}}" target='_blank'><img src="{{STATIC_URL}}img/twitter.png" alt="Twitter" title="{% trans 'Share on Twitter' %}" width="39" height="39"/></a>
<a href="http://facebook.com/sharer.php?u=http%3A%2F%2Fwww.kasu.at{{article.get_absolute_url|urlencode}}?t={{article.headline|urlencode}}" target="_blank"><img src="{{STATIC_URL}}img/facebook.png" alt="Facebook" title="{% trans 'Share on Facebook'%}" width="39" height="39"/></a>
</p>
{% endblock %}
{% block comments%}
{% render_comment_list for article %}
{% render_comment_form for article %}
{% endblock %}
{% block buttonbar %}
{% if perms.content.change_article %}
<a href="{% url 'edit-article' article.id %}" class="button"><img src="{{STATIC_URL}}icons/note_edit.png" alt="" />{% trans "Edit Article" %}</a>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,48 @@
{% extends "base.html" %}
{% load i18n fieldset_extras %}
{% block extra_head %}
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/language_tabs.js" ></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/ckeditor/ckeditor.js"></script>
{% endblock %}
{% block maincontent %}
{% get_fieldset "category, image" from form as fieldset_common %}
{% get_fieldset "headline_de, content_de" from form as fieldset_de %}
{% get_fieldset "headline_en, content_en" from form as fieldset_en %}
<form action="" method="post" enctype="multipart/formdata" class="grid_12">
<fieldset>
<legend>{% if object.pk %}{% trans "Edit Article" %}{% else %}{% trans "Create Article" %}{% endif %}</legend>
{% csrf_token %}
{% with fieldset_common as form %}{% include "form.html" %}{% endwith %}
</fieldset>
<ul class="tabs">
<li><a href="#de">{% trans "German" %}</a></li>
<li><a href="#en">{% trans "English" %}</a></li>
</ul>
<div class="tab_container">
<fieldset id="de" class="tab_content">
<legend>Deutsch</legend>
{% with fieldset_de as form %}{% include "form.html" %}{% endwith %}
</fieldset>
<fieldset id="en" class="tab_content">
<legend>English</legend>
{% with fieldset_en as form %}{% include "form.html" %}{% endwith %}
</fieldset>
</div>
<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/disk.png" alt="{% trans 'save' %}" /> {% trans 'save' %}</button>
</p>
</form>
{% endblock %}
{% block javascript %}
CKEDITOR.replace( 'id_content_de' );
CKEDITOR.replace( 'id_content_en' );
{% endblock %}
{% block buttonbar %}{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% load comments %}
{% block meta_title %}{{ page.title }}{% endblock %}
{% block title %}{{page.title}}{% endblock %}
{% block opengraph %}
<meta property="og:type" content="website" />
<meta property="og:title" content="{{page.title}}" />
<meta property="og:url" content="http://www.kasu.at{{ page.get_absolute_url }}" />
<meta property="og:image" content="http://www.kasu.at/static/img/logo.png" />
<meta property="og:description" content="{{ page.content|striptags|truncatewords:25 }}" />
{% endblock %}
{% block maincontent %}
<h2 class="grid_12">{{ page.title }}</h2>
{{ page.content }}
{% endblock %}
{% block comments %}
{% if page.enable_comments %}
{% render_comment_list for page %}
{% render_comment_form for page %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,38 @@
{% extends "base.html" %}
{% load comments i18n %}
{% block meta_title %}{{ title }}{% endblock %}
{% block title %}{{page.title}}{% endblock %}
{% block opengraph %}
<meta property="og:type" content="website" />
<meta property="og:title" content="{{page.title}}" />
<meta property="og:url" content="http://www.kasu.at{{ page.get_absolute_url }}" />
<meta property="og:image" content="http://www.kasu.at/static/img/logo.png" />
<meta property="og:description" content="{{ page.content|striptags|truncatewords:25 }}" />
{% endblock %}
{% block content %}
{{ page.content }}
{% endblock %}
{% block sidebar %}
{% if current_top_page.subpages %}
{% for subpage in current_top_page.subpages.all %}
{% if subpage.content_type > 0 %}
<h2><a href="{{ subpage.get_absolute_url }}">{{subpage.title}}</a></h2>
<ul class="list">
{% for subpage in subpage.subpages.all %}
<li class="{{subpage.css_class}}"><a href="{{ subpage.get_absolute_url }}">{{ subpage.menu_name }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}
{% block comments %}{% if page.enable_comments %}
{% render_comment_list for page %}
{% render_comment_form for page %}
{% endif %}{% endblock %}

View File

@@ -0,0 +1,71 @@
{% extends "base.html" %}
{% load i18n fieldset_extras %}
{% block title %}
{% if object.pk %}{% trans "Edit Page" %}: {{ page.title }}
{% else %}{% trans "Add Page" %}
{% endif %}
{% endblock %}
{% block extra_head %}
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery-2.1.3.min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/language_tabs.js" ></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/ckeditor/ckeditor.js"></script>
{% endblock %}
{% block jumbotron_background %}{{STATIC_URL}}img/teaser/edit_page.jpg{% endblock %}
{% block teaser %}
{% if object.pk %}<h1>{{ page.title }}</h1>
{% else %}<h1>{% trans "Add Page" %}</h1>
{% endif %}
{% endblock %}
{% block maincontent %}
{% get_fieldset "parent, slug, content_type, status" from form as fieldset_common %}
{% get_fieldset "menu_name_de, title_de, pdf_de, content_de" from form as fieldset_de %}
{% get_fieldset "menu_name_en, title_en, pdf_en, content_en" from form as fieldset_en %}
{% get_fieldset "template, enable_comments" from form as fieldset_html %}
<form method="post" enctype="multipart/form-data" class="grid_12">
{% csrf_token %}
<fieldset>
<legend>{% if object.pk %}{% trans "Edit Page" %}{% else %}{% trans "Add Page" %}{% endif %}</legend>
{% with fieldset_common as form %}{% include "form.html" %}{% endwith %}
</fieldset>
<ul class="tabs">
<li><a href="#de">{% trans "German" %}</a></li>
<li><a href="#en">{% trans "English" %}</a></li>
</ul>
<div class="tab_container">
<fieldset id="de" class="tab_content">
<legend>{% trans "German" %}</legend>
{% with fieldset_de as form %}{% include "form.html" %}{% endwith %}
</fieldset>
<fieldset id="en" class="tab_content">
<legend>{% trans "English" %}</legend>
{% with fieldset_en as form %}{% include "form.html" %}{% endwith %}
</fieldset>
</div>
<fieldset>
<legend>{% trans "HTML Specific" %}</legend>
{% with fieldset_html as form %}{% include "form.html" %}{% endwith %}
</fieldset>
<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/page_save.png" alt="{% trans 'save' %}" /> {% trans 'save' %}</button>
</p>
</form>
{% endblock %}
{% block javascript %}
CKEDITOR.replace( 'id_content_de' );
CKEDITOR.replace( 'id_content_en' );
{% endblock %}

View File

@@ -0,0 +1,66 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>{% block title %}{{page.title}}{% endblock %}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
@font-face {
font-family: 'Amerika Sans';
src: url('{{ STATIC_ROOT }}/fonts/amerika_sans.ttf');
}
@font-face {
font-family: 'Philosopher';
src: url('{{ STATIC_ROOT }}/fonts/philosopher-regular.ttf');
font-weight: normal;
font-style: normal;
}
h1,h2,h3,h4,h5,h6 {
font-family: 'Amerika Sans', Helvetica;
font-variant: small-caps;
font-weight: bold;
margin: 0.5em 0 0.2em 0;
}
body {
font-family: Philosopher;
font-size: 12pt;
}
#page_header {
text-align: right;
}
@page {
margin-right: 0;
margin-bottom: 0;
margin-top: 35mm;
margin-left: 2cm;
@frame header {
-pdf-frame-content : page_header;
top: 0;
margin: 5mm;
height: 4cm;
}
@frame footer {
-pdf-frame-content: page_footer;
bottom: 0cm;
height: 2cm;
margin: 5mm;
}
}
</style>
</head>
<body>
<h1>{{ page.title }}</h1>
{{ page.content }}
<div id="page_header">
<img src="{{STATIC_ROOT}}/img/logo.png" alt="Kasu">
</div>
<div id="page_footer">
{{ page.title }} Seite:
<pdf:pagenumber>
</div>
</body>
</html>

View File

View File

@@ -0,0 +1,47 @@
"""
Created on 10.06.2012
@author: christian
"""
import copy
from django import template
from django.utils.datastructures import SortedDict
register = template.Library()
class FieldSetNode(template.Node):
def __init__(self, form_variable, variable_name, fields):
self.fields = fields
self.variable_name = variable_name
self.form_variable = form_variable
def render(self, context):
form = template.Variable(self.form_variable).resolve(context)
new_form = copy.copy(form)
# new_form.fields = SortedDict([(key, value) for key, value in
# form.fields.items() if key in self.fields])
new_form.fields = SortedDict(
[(key, form.fields[key]) for key in self.fields]
)
context[self.variable_name] = new_form
return u''
def get_fieldset(parser, token):
try:
_name_, fields, _from_, form, _as_, variable_name = \
token.split_contents()
fields = fields[1:-1]
fields = [field.strip() for field in fields.split(',')]
except ValueError:
raise template.TemplateSyntaxError(
'bad arguments for %r' % token.split_contents()[0]
)
return FieldSetNode(form, variable_name, fields)
get_fieldset = register.tag(get_fieldset)

16
src/content/tests.py Normal file
View File

@@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

15
src/content/urls.py Normal file
View File

@@ -0,0 +1,15 @@
# -*- encoding: utf-8 -*-
"""
Created on 03.10.2011
@author: christian
"""
from django.conf.urls import * # @UnusedWildImport
from .views import ImageList, PageList
urlpatterns = patterns(
'content.views',
url(r'^image_list.js$', ImageList.as_view(), name='content-image-list'),
url(r'^link_list.js$', PageList.as_view(), name='content-page-list'),
)

227
src/content/views.py Normal file
View File

@@ -0,0 +1,227 @@
# Create your views here.
import os
from django.conf import settings
import django_comments as comments
from django.http import HttpResponse, Http404
from django.utils.translation import ugettext as _
from django.views import generic
from django.shortcuts import get_object_or_404
from . import models, forms
from aggregator.models import Feed
from utils.mixins import PermissionRequiredMixin
import events.models
class ArticleArchiveMixin(object):
def get_context_data(self, **kwargs):
context = super(ArticleArchiveMixin, self).get_context_data(**kwargs)
context['categories'] = models.Category.objects.all()
context['active_category'] = self.category
context['feeds'] = Feed.objects.active()
return context
# noinspection PyAttributeOutsideInit
class ArticleArchiveIndex(ArticleArchiveMixin, generic.ArchiveIndexView):
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
date_field = 'date_created'
paginate_by = 5
context_object_name = 'article_list'
allow_empty = True
def get_queryset(self):
queryset = generic.ArchiveIndexView.get_queryset(self)
self.category = self.kwargs.get('category')
if self.category:
try:
self.category = models.Category.objects.get(slug=self.category)
queryset = queryset.filter(category=self.category)
except models.Category.DoesNotExist:
raise Http404(_("This Category does not exist."))
return queryset
class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView):
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
date_field = 'date_created'
paginate_by = 5
year_format = '%Y'
make_object_list = True
allow_empty = True
def get_queryset(self):
queryset = generic.YearArchiveView.get_queryset(self)
self.category = self.kwargs.get('category')
if self.category:
self.category = get_object_or_404(models.Category,
slug=self.category)
queryset = queryset.filter(category=self.category)
return queryset
class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView):
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
date_field = 'date_created'
month_format = '%m'
paginate_by = 5
make_object_list = True
allow_empty = True
def get_queryset(self):
queryset = generic.MonthArchiveView.get_queryset(self)
self.category = self.kwargs.get('category')
if self.category:
self.category = models.Category.objects.get(slug=self.category)
queryset = queryset.filter(category=self.category)
return queryset
class ArticleDetail(generic.DetailView):
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
class ArticleForm(PermissionRequiredMixin, generic.UpdateView):
model = models.Article
form_class = forms.ArticleForm
permission_required = 'content.change_article'
def get_context_data(self, **kwargs):
context = super(ArticleForm, self).get_context_data(**kwargs)
if self.kwargs.get('pk'):
context['title'] = _("Edit Article")
else:
context['title'] = _("Create Article")
return context
def get_object(self, **kwargs):
if self.kwargs.get('pk', None):
return models.Article.objects.get(pk=self.kwargs['pk'])
else:
return models.Article(author=self.request.user)
class ImageList(generic.View):
# noinspection PyMethodMayBeStatic
def get(self, kwargs):
image_list = []
response = HttpResponse(content_type='text/javascript')
response.write('var tinyMCEImageList = new Array(')
os.chdir(settings.MEDIA_ROOT)
for dirpath, dirnames, filenames in os.walk(
'images'): # @UnusedVariable @IgnorePep8
filenames.sort()
for filename in filenames:
image_list.append('["%(name)s", "%(path)s"]' % {
'name': os.path.join(dirpath, filename),
'path': os.path.join(settings.MEDIA_URL, dirpath, filename)
})
response.write(', '.join(image_list))
response.write(');')
return response
class PageAddForm(PermissionRequiredMixin, generic.CreateView):
form_class = forms.PageForm
template_name = 'content/page_form.html'
permission_required = 'content.add_page'
def get_initial(self):
path = os.path.splitext(self.kwargs['path'])[0]
if path.startswith('/'):
path = path[1:]
if path.endswith('/'):
path = path[:-1]
parent = models.Page.objects.get(path=path)
return {'parent': parent}
class PageEditForm(PermissionRequiredMixin, generic.UpdateView):
form_class = forms.PageForm
permission_required = 'content.change_page'
def get_object(self, queryset=None):
path = os.path.splitext(self.kwargs['path'])[0]
if path.startswith('/'):
path = path[1:]
if path.endswith('/'):
path = path[:-1]
return models.Page.objects.get(path=path)
class PageHtml(generic.DetailView):
def get_object(self, queryset=None):
try:
return models.Page.objects.get(path=self.kwargs['path'],
content_type=1)
except models.Page.DoesNotExist:
raise Http404(
_("No Page found matching the Path %s") % self.request.path
)
def get_template_names(self):
return self.object.template
class PagePdf(generic.DeleteView):
def get_object(self, queryset=None):
try:
return models.Page.objects.get(path=self.kwargs['path'],
content_type=2)
except models.Page.DoesNotExist:
raise Http404(
_("No PDF Document found matching the Path %s") %
self.request.path
)
def render_to_response(self, context, **response_kwargs):
try:
pdf_file = open(self.object.pdf_file.path, 'rb')
response = HttpResponse(pdf_file.read(),
content_type='application/pdf')
pdf_file.close()
return response
except:
raise Http404('File not Found %s.pdf' % self.kwargs['path'])
class PageList(generic.View):
# noinspection PyMethodMayBeStatic
def get(self, kwargs):
response = HttpResponse(content_type='text/javascript')
response.write('var tinyMCELinkList = new Array(')
page_list = []
for page in models.Page.objects.filter(status=models.STATUS_PUBLISHED):
page_list.append('["%(name)s", "%(path)s"]' % {
'name': page.menu_name,
'path': page.get_absolute_url()
})
response.write(', '.join(page_list))
response.write(');')
return response
class StartPage(generic.TemplateView):
template_name = 'index.html'
def get_context_data(self, **kwargs):
page = models.Page.objects.get(slug='index')
random_photo = events.models.Photo.objects.get_random(startpage=True)
recent_comment_list = comments.get_model().objects.filter(
site__pk=settings.SITE_ID,
is_public=True,
is_removed=False,
)
recent_comment_list = recent_comment_list.order_by('-submit_date')[:10]
context = {
'title': page.title,
'content': page.content,
'random_photo': random_photo.callout,
'recent_article_list': models.Article.objects.published()[:3],
'recent_comment_list': recent_comment_list,
}
return context
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)

0
src/events/__init__.py Normal file
View File

50
src/events/admin.py Normal file
View File

@@ -0,0 +1,50 @@
"""
Created on 19.09.2011
@author: christian
"""
# import stuff we need from django
from django.contrib import admin
from imagekit.admin import AdminThumbnail
from django.utils.translation import gettext as _
from events.models import Event, Photo, Location
class EventInline(admin.TabularInline):
model = Event
fields = ('name', 'start', 'end')
verbose_name_plural = _('Event Series')
class EventAdmin(admin.ModelAdmin):
list_display = ('name', 'start', 'end', 'location',)
list_editable = ('start', 'end', 'location',)
readonly_fields = ('event_series',)
date_hierarchy = 'start'
search_fields = ('name', 'description')
list_per_page = 50
inlines = (EventInline,)
class LocationAdmin(admin.ModelAdmin):
list_display = ('name', 'street_address', 'postal_code', 'locality')
class PhotoAdmin(admin.ModelAdmin):
admin_thumbnail = AdminThumbnail(image_field='thumbnail')
fields = ('image', 'event', 'name', 'description',
('anchor_horizontal', 'anchor_vertical'),
('photographer', 'created_date'))
list_filter = ('event', 'on_startpage',)
list_display = ('admin_thumbnail', 'image', 'name', 'event',
'photographer', 'on_startpage')
list_display_links = ('image',)
list_editable = ('on_startpage', 'name', 'event', 'photographer')
# register with CMS
admin.site.register(Event, EventAdmin)
admin.site.register(Photo, PhotoAdmin)
admin.site.register(Location, LocationAdmin)

View File

@@ -0,0 +1,15 @@
# -*- encoding: UTF-8 -*-
"""
Created on 30.09.2011
@author: christian
"""
from . import models
def upcoming_events(request):
return {
'current_event': models.Event.objects.current_event(),
'next_event': models.Event.objects.next_event(),
'upcoming_events': models.Event.objects.upcoming(),
}

60
src/events/forms.py Normal file
View File

@@ -0,0 +1,60 @@
"""
Created on 03.10.2011
@author: christian
"""
from django import forms
from django.utils.translation import ugettext as _
from django.contrib.auth import get_user_model
from . import models
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
exclude = ('event_count', 'event_series', )

Binary file not shown.

View File

@@ -0,0 +1,302 @@
# 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: kasu.events\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-05 19:23+0100\n"
"PO-Revision-Date: 2014-12-08 16:06+0100\n"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Translated-Using: django-rosetta 0.7.2\n"
"X-Generator: Poedit 1.6.11\n"
#: admin.py:17 models.py:81
msgid "Event Series"
msgstr "Veranstaltungsreihen"
#: forms.py:23
msgid "Images"
msgstr "Bilder"
#: forms.py:50
msgid "start"
msgstr "Beginn"
#: forms.py:54
msgid "end"
msgstr "Ende"
#: models.py:67 models.py:155
msgid "Name"
msgstr "Name"
#: models.py:68 models.py:156
msgid "Description"
msgstr "Beschreibung"
#: models.py:70 templates/events/event_archive.html:41
#: templates/events/event_detail.html:31 templates/events/event_detail.html:77
#: templates/events/event_list.html:15
msgid "Start"
msgstr "Beginn"
#: models.py:71 templates/events/event_detail.html:32
#: templates/events/event_detail.html:78
msgid "End"
msgstr "Ende"
#: models.py:72 models.py:159 templates/events/event_detail.html:37
#: templates/events/event_detail.html:73 templates/events/event_detail.html:79
msgid "Homepage"
msgstr "Homepage"
#: models.py:73 models.py:157
msgid "Image"
msgstr "Bild"
#: models.py:75
msgid "Tournament"
msgstr "Turnier"
#: models.py:76
msgid ""
"This event is a tournament, different rules apply for the kyu "
"ranking."
msgstr ""
"Diese Veranstaltung ist ein Turnier, es gelten andere Regeln für das Kyu "
"Ranking."
#: models.py:82
msgid ""
"Wenn dieser Event zu einer Veranstaltungsreihe gehört werden Ort, "
"Beschreibung, Bild und Homepage von dem hier angegebenen Event "
"übernommen."
msgstr ""
"Wenn dieser Termin zu einer Veranstaltungsreihe gehört werden Ort, "
"Beschreibung, Bild und Homepage von dem hier angegebenen Event übernommen."
#: models.py:88
msgid "Event"
msgstr "Termin"
#: models.py:89
msgid "Events"
msgstr "Termine"
#: models.py:160
msgid "Postal Code"
msgstr "Postleitzahl"
#: models.py:161
msgid "Street Address"
msgstr "Straße"
#: models.py:162
msgid "Locality"
msgstr "Ort"
#: models.py:163
msgid "Country"
msgstr "Land"
#: models.py:166
msgid "Venue"
msgstr "Veranstaltungsort"
#: models.py:167
msgid "Venues"
msgstr "Veranstaltungsorte"
#: views.py:101 templates/events/event_detail.html:110
#: templates/events/event_form.html:9
msgid "Edit Event"
msgstr "Termin bearbeiten"
#: views.py:103 templates/events/event_form.html:9
#: templates/events/page.html:15
msgid "Add Event"
msgstr "Neuer Termin"
#: views.py:199
msgid "Event does not exist"
msgstr "Veranstaltung gibt es nicht"
#: templates/events/event_archive.html:5 templates/events/event_archive.html:9
msgid "Event Archive"
msgstr "Veranstaltungsarchiv"
#: templates/events/event_archive.html:36 templates/events/event_list.html:11
msgid "Event Image"
msgstr "Veranstaltungsbild"
#: templates/events/event_archive.html:44 templates/events/event_list.html:18
msgid "from"
msgstr "von"
#: templates/events/event_archive.html:44 templates/events/event_list.html:18
msgid "to"
msgstr "bis"
#: templates/events/event_archive.html:52
#: templates/events/event_detail.html:35 templates/events/event_detail.html:65
#: templates/events/event_list.html:26
msgid "Location"
msgstr "Ort"
#: templates/events/event_archive.html:56
#: templates/events/event_archive.html:57 templates/events/event_list.html:30
msgid "Comments"
msgstr "Kommentare"
#: templates/events/event_archive.html:60
#: templates/events/event_archive.html:61
#: templates/events/event_detail.html:39 templates/events/event_detail.html:49
msgid "Photos"
msgstr "Fotos"
#: templates/events/event_archive.html:64
#: templates/events/event_archive.html:65
#: templates/events/event_archive.html:66
#: templates/events/event_detail.html:38 templates/events/event_detail.html:48
#: templates/events/event_detail.html:50
msgid "Hanchans"
msgstr "Hanchans"
#: templates/events/event_archive.html:70
msgid " Edit"
msgstr "Bearbeiten"
#: templates/events/event_detail.html:40
msgid "tourney"
msgstr "Turnier"
#: templates/events/event_detail.html:40
msgid "other rules apply here"
msgstr "hier gelten andere Regeln"
#: templates/events/event_detail.html:48
msgid "Info"
msgstr "Info"
#: templates/events/event_detail.html:51
msgid "Mai-Star Games"
msgstr "Mai-Star Spiele"
#: templates/events/event_detail.html:54
msgid "Tournament Ranking"
msgstr "Turnier Wertung"
#: templates/events/event_detail.html:75
msgid "Date"
msgstr "Datum"
#: templates/events/event_detail.html:91
msgid "Share on Google+"
msgstr "Auf Google+ teilen"
#: templates/events/event_detail.html:92
msgid "Share on Twitter"
msgstr "Auf Twitter teilen"
#: templates/events/event_detail.html:93
msgid "Share on Facebook"
msgstr "Auf Facebook teilen"
#: templates/events/event_detail.html:94
msgid "Show on Google Maps"
msgstr "Auf Google Maps zeigen"
#: templates/events/event_form.html:19
msgid "reset"
msgstr "Zurücksetzen"
#: templates/events/event_form.html:20
msgid "save"
msgstr "Speichern"
#: templates/events/event_list.html:4 templates/events/event_list.html.py:5
msgid "Upcoming Events"
msgstr "Bevorstehende Veranstaltungen"
#: templates/events/event_list.html:41
msgid " Upload"
msgstr "Hochladen"
#: templates/events/page.html:10
msgid "Add to Google Calendar"
msgstr "Zu Google Kalender hinzufügen"
#~ msgid "left"
#~ msgstr "Links"
#~ msgid "center"
#~ msgstr "Mitte"
#~ msgid "right"
#~ msgstr "Rechts"
#~ msgid "top"
#~ msgstr "Oben"
#~ msgid "middle"
#~ msgstr "Mitte"
#~ msgid "bottom"
#~ msgstr "Unten"
#~ msgid "horizontal Anchorpoint"
#~ msgstr "horizontaler Anker"
#~ msgid "vertical Anchorpoint"
#~ msgstr "vertikaler Anker"
#~ msgid "Startpage"
#~ msgstr "Startseite"
#~ msgid "Display this Photo on the Startpage Teaser"
#~ msgstr "Foto als Teaser auf der Startseite verwenden."
#~ msgid "Published on"
#~ msgstr "Veröffentlicht am"
#~ msgid "Number of views"
#~ msgstr "Wie oft gesehen"
#~ msgid "Event Images"
#~ msgstr "Veranstaltungsbilder"
#~ msgid "Cancel"
#~ msgstr "Abbrechen"
#~ msgid "Delete"
#~ msgstr "Löschen"
#~ msgid "previous"
#~ msgstr "Zurück"
#~ msgid "download"
#~ msgstr "Herunterladen"
#~ msgid "Photographer"
#~ msgstr "Fotograf"
#~ msgid "on"
#~ msgstr "am"
#~ msgid "Upload"
#~ msgstr "Hochladen"
#~ msgid "delete"
#~ msgstr "Löschen"
#~ msgid "upload"
#~ msgstr "hochladen"

181
src/events/models.py Normal file
View File

@@ -0,0 +1,181 @@
# -'- Encoding: utf-8 -*-
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 utils import COUNTRIES, OverwriteStorage
from gallery.models import Photo
from kasu import image_models
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)
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(image_models.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()
super(Event, self).save(**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(image_models.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)
models.signals.post_save.connect(image_models.regenerate_image_cache,
sender=Event)
models.signals.post_save.connect(image_models.regenerate_image_cache,
sender=Location)

View File

@@ -0,0 +1,76 @@
{% extends "events/page.html" %}
{% load i18n comments%}
{% block title %}
{% trans 'Event Archive' %} {% if month %}{{ month|date:'F Y' }} {% else %}{% if year %}{{year}}{% endif %}{% endif %}
{% endblock %}
{% block teaser %}
<h2>{% trans 'Event Archive' %} {% if month %}{{ month|date:'F Y' }}{% elif year %}{{year|date:'Y'}}{% endif %}</h2>
{% endblock %}
{% block redbox %}
<h2>Archive</h2>
<ul>
{% if year %}
{% for date in date_list %}
<li><a href="{% url 'event-archive' year|date:'Y' date|date:'m' %}">{{ date|date:'F' }}</a></li>
{% endfor %}
{% elif month %}
<li><a href="{% url 'event-archive' month|date:'Y' %}">{{ month|date:'Y' }}</a></li>
{% else %}
{% for date in date_list %}
<li><a href="{% url 'event-archive' date|date:'Y' %}">{{ date|date:'Y' }}</a></li>
{% endfor %}
{% endif %}
</ul>
{% endblock %}
{% block maincontent %}
{% regroup event_list by start|date:'F Y' as month_list %}
{% for month in month_list %}
<h3 class="grid_12">{{ month.grouper }}</h3>
{% for event in month.list %}
{% get_comment_count for event as comment_count %}
<a href="{{ event.get_absolute_url }}" class="grid_2"><img src="{{ event.get_thumbnail.url }}"
alt="{% trans 'Event Image' %}"
class="thumbnail"/></a>
<div class="grid_4">
<h4><a href="{{ event.get_absolute_url }}">{{ event.name }}</a></h4>
<p><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 %}
</p>
<p>{{event.description|truncatewords_html:20}}</p>
<ul class="info">
<li><img src="{{ STATIC_URL }}/icons/map.png" alt="{% trans 'Location' %}" title="{% trans 'Location' %}">
{{ event.location }}
</li>
<li><a href="{{event.get_absolute_url}}#comments"><img src="{{ STATIC_URL }}/icons/comments.png"
alt="{% trans 'Comments' %}"
title="{% trans 'Comments' %}"> {{ comment_count }}</a>
</li>
<li><a href="{% url 'event-photo-list' event.pk %}"><img src="{{ STATIC_URL }}/icons/camera.png"
alt="{% trans 'Photos' %}"
title="{% trans 'Photos' %}">
{{ event.photo_count }}</a></li>
<li><a href="{% url 'event-hanchan-list' event.pk %}"><img src="{{ STATIC_URL }}/icons/table.png"
alt="{% trans 'Hanchans' %}"
title="{% trans 'Hanchans' %}">
{{ event.hanchan_set.count }} {% trans 'Hanchans' %}</a></li>
</ul>
{% if perms.events.change_event %}
<p class="right"><a href="{{ event.get_edit_url }}" class="button"><img src="{{ STATIC_URL }}icons/page_edit.png"
alt="{%trans " Edit" %}"></a></p>
{% endif %}
</div>
{% if forloop.counter|divisibleby:2 %}<br class="clear"/>{% endif %}
{% endfor %}
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,144 @@
{% extends "events/page.html" %}
{% load i18n django_markdown comments %}
{% block title %}{{ event.name }}{% endblock %}
{% block opengraph %}
<meta property="og:type" content="activity" />
<meta property="og:title" content="{{event.name}}" />
<meta property="og:url" content="http://www.kasu.at{{event.get_absolute_url}}" />
<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 extra_head %}
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
{% endblock %}
{% block jumbotron_background %} {{ event.get_callout.url }} {% endblock %}
{% block teaser %}
<h1>{{event.name}}</h1>
{% if event.description %}
<div id="teaser_text">{{event.description|markdown|truncatewords_html:75}}</div>
{% endif %}
{% endblock %}
{% block redbox %}
<h2>Info</h2>
<p>&nbsp;</p>
<ul>
<li class="date"><strong>{% trans "Start" %}:</strong> {{ event.start }}</li>
{% if event.end %}<li class="date"><strong>{% trans "End" %}:</strong> {{ event.end }}</li>{% endif %}
<li class="location">
{% if event.location.url %}<a href="{{ event.location.url }}">{% else %}<a href="http://maps.google.com/maps?q={{event.location.address|urlencode}}&amp;z=16">{% endif %}
<strong>{% trans "Location" %}:</strong> {{event.location.name}}</a>
</li>
{% if event.url %}<li><a href="{{ event.url }}"><strong>{% trans "Homepage" %}:</strong> {{ event.url }}</a></li>{% endif %}
<li class="hanchan"><a href="{% url 'event-hanchan-list' event.pk %}" ><strong>{% trans "Hanchans" %}:</strong> {{ event.hanchan_set.count }}</a></li>
<li class="photo"><a href="{% url 'event-photo-list' event.pk %}"><strong>{% trans 'Photos' %}:</strong> {{ event.photo_count }}</a></li>
{% if event.is_tournament %}<li class="season"><a href="{% url 'event-ranking' event.pk %}"><strong>{% trans "tourney" %}:</strong> {% trans "other rules apply here" %}</a></li>{% endif%}
</ul>
{% endblock %}
{% block navigation %}
<code>{{ event. }}</code>
<ul id="navigation">
<li><a href="{{ event.get_absolute_url }}"><img src="{{ STATIC_URL }}icons/information.png" alt="{% trans 'Hanchans' %}" /> {% trans 'Info' %}</a></li>
<li><a href="{% url 'event-photo-list' event.pk %}"><img src="{{ STATIC_URL }}icons/camera.png" alt="{% trans 'Photos' %}" /> {{ event.photo_count }} {% trans 'Photos' %}</a></li>
<li><a href="{% url 'event-hanchan-list' event.pk %}" ><img src="{{ STATIC_URL }}icons/table.png" alt="{% trans 'Hanchans' %}" /> {{ event.hanchan_set.count }} {% trans "Hanchans" %}</a></li>
<li><a href="{% url 'maistar-game-list' event.pk %}" ><img src="{{ STATIC_URL }}icons/drink.png" alt="{% trans 'Mai-Star Games' %}" /> {{ event.maistargame_set.count }} {% trans "Mai-Star Games" %}</a></li>
{% if event.is_tournament %}
<li><a href="{% url 'event-ranking' event.id %}"><img src="{{ STATIC_URL }}icons/medal_gold_1.png" alt="{% trans "Tournament Ranking" %}" /> {% trans "Tournament Ranking" %}</a></li>
{% endif %}
</ul>
{% endblock %}
{% block maincontent %}
<div class="grid_6" id="google_maps">
&nbsp;
</div>
<div class="grid_6">
<h3>{% trans 'Location' %}</h3>
<strong>{{ event.location.name }}</strong>
<address>
{{event.location.street_address}}<br />
{{event.location.postal_code}} {{event.location.locality}} <br />
{{event.location.get_country_display}}
</address>
{% if event.location.url %}
<p><strong>{% trans "Homepage" %}:</strong> <a href="{{ event.location.url }}">{{ event.location.url }}</a></p>
{% endif %}
<h3>{% trans "Date" %}</h3>
<ul>
<li><strong>{% trans "Start" %}:</strong> {{ event.start }}</li>
{% if event.end %}<li><strong>{% trans "End" %}:</strong> {{ event.end }}</li>{% endif %}
{% if event.url %}<li><strong>{% trans "Homepage" %}:</strong> <a href="{{ event.url }}">{{ event.url }}</a></li>{% endif %}
</ul>
</div>
<div class="grid_12">
{% if event.description %}
{{event.description|markdown}}
{% else %}
{{event.location.description|markdown}}
{% endif %}
<p class="more_link">
<a href="https://plus.google.com/share?url=http%3A%2F%2Fwww.kasu.at{{event.get_absolute_url|urlencode}}" onclick="javascript:window.open(this.href,
'', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;"><img src="{{STATIC_URL}}img/google_plus.png" alt="Google+" title="{% trans 'Share on Google+'%}" width="39" height="39"/></a>
<a href="https://twitter.com/share?url=http%3A%2F%2Fwww.kasu.at{{event.get_absolute_url|urlencode}}" target='_blank'><img src="{{STATIC_URL}}img/twitter.png" alt="Twitter" title=" {% trans 'Share on Twitter' %}" width="39" height="39"/></a>
<a href="http://facebook.com/sharer.php?u=http%3A%2F%2Fwww.kasu.at{{event.get_absolute_url|urlencode}}" target="_blank"><img src="{{STATIC_URL}}img/facebook.png" alt="Facebook" title="{% trans 'Share on Facebook'%}" width="39" height="39"/></a>
<a href="http://maps.google.com/maps?q={{event.location.address|urlencode}}&amp;z=16" target="gmaps"><img src="{{ STATIC_URL }}img/google_maps.png" alt="Google Maps" title="{% trans 'Show on Google Maps' %}" width="39" height="39"/></a>
</p>
</div>
<br class="clear" />
{% block event_content %} {% endblock %}
{% endblock %}
{% block comments %}
{% render_comment_list for event %}
{% render_comment_form for event %}
{% endblock %}
{% block buttonbar %}
{% if perms.events.change_event %}
<a class="button" href="{{ event.get_edit_url }}"><img src="{{ STATIC_URL }}icons/page_edit.png" alt="" /> {% trans "Edit Event" %}</a>
{% endif %}
{% endblock %}
{% block javascript %}
var geocoder;
var map;
function codeAddress() {
var address = "{{ event.location.address }}";
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
});
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
}
function initialize() {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(-34.397, 150.644);
var mapOptions = {
zoom: 16,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById("google_maps"), mapOptions);
codeAddress();
}
initialize();
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends "events/page.html" %}
{% load i18n %}
{% block title %}{{ title }}{% endblock %}
{% block maincontent %}
<form method="post" enctype="multipart/form-data" class="grid_12">
{% csrf_token %}
<fieldset>
<legend>{% if event.pk %}{% trans "Edit Event"%}{% else %}{% trans "Add Event"%}{% endif %}</legend>
{% include "form.html" %}
{% if event.id and event.event_set.count %}
<p class="warning">
<strong>Achtung! Das ist eine Veranstaltungsreihe!</strong> Diese kann man im Moment nur im Admin-Interface vernünfig bearbeiten.<br />
Du bearbeitest hier den "Hauptevent" der Reihe ({{event.event_set.count}}). Alle Änderungen (abgesehen von Name, Start und Ende) werden von den darauf folgendem Veranstaltungen übernommen.
</strong>
</p>
{% endif %}
<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/disk.png" alt="{% trans 'save' %}" /> {% trans 'save' %}</button>
</p>
</fieldset>
</form>
{% endblock %}
{% block buttonbar %}{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends "events/page.html" %}
{% load i18n comments%}
{% block title %}{% trans "Upcoming Events" %}{% endblock %}
{% block teaser%}<h2>{% trans "Upcoming Events" %}</h2>{% endblock %}
{% block maincontent %}
{% for event in event_list %}
{% ifchanged %}<h3 class="grid_12">{{ event.start|date:'F Y' }}</h3>{% endifchanged %}
{% get_comment_count for event as comment_count %}
<a href="{{ event.get_absolute_url }}" class="grid_2"><img src="{{ event.get_thumbnail.url }}" alt=" {% trans 'Event Image' %}"
class="thumbnail"/></a>
<div class="grid_4">
<h4><a href="{{ event.get_absolute_url }}">{{ event.name }}</a></h4>
<p class="strong"><img src="{{ STATIC_URL }}/icons/date.png" alt="{% trans 'Start' %}:" title="{% trans 'Start' %}">
{{ event.start|date:'SHORT_DATE_FORMAT' }}
{% if event.end %}
{% trans "from" %} {{ event.start|time:'H:i' }} {% trans "to" %} {{ event.end|time:'H:i' }}
{% else %}
{{ event.start|time:'H:i' }}
{% endif %}
</p>
<p>{{event.description|truncatewords_html:20}}</p>
<ul class="info">
<li>
<img src="{{ STATIC_URL }}/icons/map.png" alt="{% trans 'Location' %}" title="{% trans 'Location' %}">
{{ event.location }}
</li>
<li>
<img src="{{ STATIC_URL }}/icons/comments.png" alt="{% trans 'Comments' %}" title="{% trans 'Comments' %}">
<a href="{{event.get_absolute_url}}#comments">{{ comment_count }}</a>
</li>
</ul>
<p class="right">
{% if perms.events.change_event %}
<a href="{{ event.get_edit_url }}" class="button"><img src="{{ STATIC_URL }}icons/page_edit.png" alt="{%trans "
Edit" %}"></a>
{% endif %}
{% 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>
{% endfor %}
{% if page_obj.has_other_pages %}{% include 'paginator.html' %}{% endif %}
{% endblock %}

View File

@@ -0,0 +1,23 @@
{% extends "base.html" %}
{% load i18n django_markdown%}
{% block title %}{{ event.name }}{% endblock %}
{% block opengraph %}
<meta property="og:type" content="activity" />
<meta property="og:title" content="{{event.name}}" />
<meta property="og:url" content="http://www.kasu.at{{event.get_absolute_url}}" />
<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 jumbotron_background %}{{ event.get_callout.url }}')}{% endblock %}
{% block teaser %}
<h2>{{event.name}}</h2>
<div id="teaser_text">
{% if event.description %}{{event.description|markdown}}{% else %}{{event.location.description|markdown}}{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% load comments i18n %}
{% block title %}{{page.title}}{% endblock %}
{% block sidebar %}
<a href="http://www.google.com/calendar/render?cid=http%3A%2F%2Fwww.kasu.at%2Fevents.ics"
target="_blank"><img src="http://www.google.com/calendar/images/ext/gc_button6.gif"
alt="{% trans 'Add to Google Calendar' %}"></a>
{% endblock %}
{% block additional_buttonbar %}
{% if perms.events.add_event %}
<a href="{% url 'event-form' %}" class="button"><img src="{{ STATIC_URL }}icons/calendar_add.png" alt=""/> {% trans 'Add Event' %}</a>
{% endif %}
{% endblock %}
{% block content %}{{ page.content }}{% endblock %}

16
src/events/tests.py Normal file
View File

@@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

19
src/events/urls.py Normal file
View File

@@ -0,0 +1,19 @@
# -*- encoding: utf-8 -*-
from django.conf.urls import patterns, url
from .views import *
urlpatterns = patterns(
'',
url(r'^$', UpcomingEvents.as_view(), name='upcoming-events'),
url(r'^(?P<year>[\d]{4})/$', EventArchiveYear.as_view(),
name='event-archive'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/$', EventArchiveMonth.as_view(),
name='event-archive'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/(?P<pk>[\d]+)/$',
EventDetail.as_view(), name='event-detail'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/(?P<pk>[\d]+)/edit/$',
EventForm.as_view(), name='event-form'),
url(r'^add/$', EventForm.as_view(), name='event-form'),
url(r'^archive/$', EventArchiveIndex.as_view(), name='event-archive'),
)

265
src/events/views.py Normal file
View File

@@ -0,0 +1,265 @@
# -*- 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
class EventDetailMixin(object):
def get_context_data(self, **kwargs):
context = super(EventDetailMixin, self).get_context_data(**kwargs)
if hasattr(self, 'event'):
context['event'] = self.event
else:
context['event'] = self.object.event
return context
class EventForm(PermissionRequiredMixin, generic.UpdateView):
form_class = forms.EventForm
permission_required = 'events.add_event'
def get_context_data(self, **kwargs):
context = super(EventForm, self).get_context_data(**kwargs)
if self.kwargs.get('pk'):
context['title'] = _("Edit Event")
else:
context['title'] = _("Add Event")
return context
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

1
src/gallery/__init__.py Normal file
View File

@@ -0,0 +1 @@
__author__ = 'christian'

42
src/gallery/forms.py Normal file
View File

@@ -0,0 +1,42 @@
"""
Created on 03.10.2011
@author: christian
"""
from django import forms
from django.utils.translation import ugettext as _
from django.contrib.auth import get_user_model
from . import models
from events.models import Event
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(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')

171
src/gallery/models.py Normal file
View File

@@ -0,0 +1,171 @@
# -'- Encoding: utf-8 -*-
import os
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.translation import ugettext as _
import pyexiv2
from utils import OverwriteStorage
from kasu import image_models
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, Photo):
return "events/%s/%s" % (instance.event.id, filename)
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(image_models.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=image_models.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=image_models.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('events.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:
get_latest_by = "created_date"
ordering = ["created_date"]
db_table = 'events_photo'
verbose_name = _('Event Image')
verbose_name_plural = _('Event Images')
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.
"""
super(Photo, self).save()
self.save_metadata()
def update_event(sender, instance=None, created=False, raw=False, **kwargs):
image_models.regenerate_image_cache(sender, instance=instance)
instance.event.save()
models.signals.post_save.connect(update_event, sender=Photo)
models.signals.post_delete.connect(update_event, sender=Photo)

View File

@@ -0,0 +1,57 @@
{% extends "gallery/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> &raquo; {{ 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>
<p class="grid_10 push_1">{{ photo.description }}</p>
<div class="grid_12 more_link">
<a href="https://m.google.com/app/plus/x/?v=compose&amp;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>
<ul class="info grid_12">
<li class="user"><strong>{% trans 'Photographer' %}: </strong>{{ photo.photographer }}</li>
<li class="date"><strong>{% trans 'on' %}</strong> {{ photo.created_date }}</li>
</ul>
{% endblock %}
{% block comment %}
{% render_comment_list for photo %}
{% render_comment_form for photo %}
{% endblock %}
{% block buttonbar %}
{% if perms.events.change_photo %}
<form method="post" enctype="multipart/form-data" class="grid_12">
{% csrf_token %}
<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>
</form>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends "base.html" %}
{% load i18n %}
{% 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 %}

View File

@@ -0,0 +1,53 @@
{% extends "base.html" %}
{% load i18n comments %}
{% block maincontent %}
{% 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 %}

18
src/gallery/urls.py Normal file
View File

@@ -0,0 +1,18 @@
# -*- encoding: utf-8 -*-
from django.conf.urls import patterns, url
from .views import *
urlpatterns = patterns(
'',
url(r'^$', EventGallery.as_view(), name='event-gallery'),
url(r'^(?P<event>[\d]+)/$', EventPhotoList.as_view(),
name='event-photo-list'),
url(r'^(?P<event>[\d]+)/upload/$', EventPhotoUpload.as_view(),
name='event-photo-upload'),
url(r'^(?P<event>[\d]+)/(?P<pk>[\d]+)/$', EventPhoto.as_view(),
name='event-photo'),
url(r'^delete/(?P<pk>[\d]+)/$', DeleteEventPhoto.as_view(),
name='delete-event-photo'),
url(r'^upload/$', EventPhotoUpload.as_view(), name='event-photo-upload'),
)

142
src/gallery/views.py Normal file
View File

@@ -0,0 +1,142 @@
# -*- 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 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
import pyexiv2
from events.models import Event
from .models import Photo
from . import forms
class DeleteEventPhoto(generic.DeleteView):
model = Photo
def get_success_url(self):
return reverse('event-photo-list', args=[self.object.event.id])
def get_context_data(self, **kwargs):
context = super(DeleteEventPhoto, self).get_context_data()
context['event'] = self.object.event
return context
@method_decorator(permission_required('events.delete_photo'))
def dispatch(self, *args, **kwargs):
return super(DeleteEventPhoto, self).dispatch(*args, **kwargs)
class EventGallery(generic.ListView):
template_name = 'gallery/photo_gallery.html'
queryset = Event.objects.filter(start__lt=timezone.now(), photo_count__gt=0)
paginate_by = 12
class EventPhoto(generic.UpdateView):
form_class = forms.EditPhotoForm
model = Photo
template_name = 'gallery/photo_detail.html'
def get_context_data(self, **kwargs):
context = super(EventPhoto, self).get_context_data()
context['event'] = self.object.event
return context
def post(self, request, *args, **kwargs):
if request.POST.get('rotate') and request.user.has_perm(
'events.change_photo'):
photo = 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 = Event.objects.get(id=self.kwargs['event'])
return Photo.objects.filter(event=self.event)
except Event.DoesNotExist:
raise Http404(_('Event does not exist'))
class EventPhotoUpload(generic.FormView):
form_class = forms.PhotoUploadForm
template_name = 'gallery/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'] = 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 = 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 = 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

0
src/kasu/__init__.py Normal file
View File

106
src/kasu/image_models.py Normal file
View File

@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
import os
from django.db import models
from django.utils.translation import ugettext as _
from pilkit.processors import ResizeToFill, ResizeToFit, SmartResize, Transpose
from imagekit.exceptions import MissingSource
from imagekit.models import ImageSpecField
import imagekit
CHOICES_HORIZONTAL = (
(0.00000001, _('left')),
(0.5, _('center')),
(1, _('right'))
)
CHOICES_VERTICAL = (
(0.00000001, _('top')),
(0.5, _('middle')),
(1, _('bottom'))
)
class ArticleImage(imagekit.ImageSpec):
format = 'PNG'
width = 200
height = 120
processors = [SmartResize(width=200, height=120)]
class CalloutImage(imagekit.ImageSpec):
format = 'JPEG'
width = 660
height = 300
@property
def processors(self):
model, field_name = imagekit.utils.get_field_info(self.source)
anchor = model.get_anchor()
if anchor:
return [Transpose(), ResizeToFill(
width=self.width,
height=self.height, anchor=anchor
)]
else:
return [Transpose(), SmartResize(
width=self.width,
height=self.height
)]
class DisplayImage(imagekit.ImageSpec):
format = 'PNG'
processors = [Transpose(),
ResizeToFit(width=940, height=940, upscale=False)]
class ThumbnailImage(CalloutImage):
format = 'PNG'
width = 140
height = 140
imagekit.register.generator('kasu:image:article', ArticleImage)
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):
article = ImageSpecField(source='image', id='kasu:image:article')
callout = ImageSpecField(source='image', id='kasu:image:callout')
display = ImageSpecField(source='image', id='kasu:image:display')
thumbnail = ImageSpecField(source='image', id='kasu:image:thumbnail')
class Meta:
abstract = True
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
def regenerate_image_cache(sender, instance=None, created=False, raw=False,
**kwargs):
"""
Reganerate the images.
"""
if instance.image:
print instance.image
print instance.display
print instance.callout
for cached_image in (instance.article, instance.callout, instance.display, instance.thumbnail):
try:
os.remove(cached_image.path)
except OSError:
pass

Binary file not shown.

File diff suppressed because it is too large Load Diff

34
src/kasu/logging.conf Normal file
View File

@@ -0,0 +1,34 @@
[loggers]
keys=root,doloto.pystories
[handlers]
keys=consoleHandler,rfileHandler
[formatters]
keys=simpleFormatter
[logger_doloto.pystories]
level=DEBUG
handlers=consoleHandler,rfileHandler
qualname=doloto
propagate=0
[logger_root]
level=DEBUG
handlers=consoleHandler,rfileHandler
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_rfileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=(%(log_path)s,'a',50000,5)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=

View File

@@ -0,0 +1,411 @@
@font-face {
font-family: 'Philosopher';
font-weight: normal;
font-style: normal;
src: url('../fonts/philosopher.woff') format('woff'), url('../fonts/philosopher.ttf') format('truetype');
}
@font-face {
font-family: 'Amerika Sans';
font-weight: normal;
font-style: normal;
src: url('../fonts/amerikasans.woff') format('woff'), url('../fonts/amerikasans.ttf') format('truetype');
}
@font-face {
font-family: 'Social Icons';
font-weight: normal;
font-style: normal;
src: url('../fonts/social.woff') format('woff'), url('../fonts/social.ttf') format('truetype');
}
.clear {
clear: both;
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
width: 0;
}
#display {position:relative;top:0px; text-align: center;}
.clearfix {
clear: both;
}
.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12
{
display: inline;
float: left;
margin: 0px 10px;
position: relative;
box-sizing: border-box;
}
.grid_1 {
width: 60px;
}
.grid_2 {
width: 140px;
}
.grid_3 {
width: 220px;
}
.grid_4 {
width: 300px;
}
.grid_5 {
width: 380px;
}
.more_link {
text-align: right;
clear: left;
}
.error, ul.errorlist li {
color: #a40000;
}
a:hover {
color: #a40000;
text-decoration: underline;
}
a:link {
color: #204a87;
font-weight: 700;
text-decoration: none;
}
a:visited {
color: #5c3566;
}
button, a.button {
display: inline-block;
color: #2e3436;
padding: 0.2em;
font: bold 12pt Philosopher, sans-serif;
margin: 0.2em;
border: 1px solid #d3d7cf;
border-radius: 5px;
background-color: #f9f9f9;
box-shadow: inset 0px 1px 0px 0px #ffffff;
text-decoration: none;
text-shadow: 1px 1px 0px #ffffff;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #f9f9f9), color-stop(1, #e9e9e9) );
background: -moz-linear-gradient(center top, #f9f9f9 5%, #e9e9e9 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f9f9f9', endColorstr='#e9e9e9');
}
a.button img, button img {
vertical-align: middle;
}
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
display: block;
}
h1 a:link, h2 a:link, h3 a:link, h4 a:link, h5 a:link, h6 a:link, h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {
color: #bc0a19;
font-weight: 400;
text-decoration: none;
}
h1, h2, h3, h4, h5, h6, .player {
color: #bc0a19;
font-family: 'Amerika Sans', sans-serif;
font-variant: small-caps;
font-weight: 400;
letter-spacing: -1px;
margin: 1em 0 0.5em 0;
text-shadow: 2px 2px 2px #888;
vertical-align: baseline;
}
.player {
margin:0
}
html, div, span, applet, object, iframe, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
border: 0;
margin: 0;
padding: 0;
vertical-align: baseline;
}
p {
margin-bottom: 0.5em
}
img.partner, img.posting_image {
border: 1px solid #babdb6;
display:block;
float: left;
height: 120px;
margin: 0 20px 0 0;
padding: 4px;
width: 200px;
}
img.partner:nth-of-type(odd) {
float: right;
}
input {font: normal 12pt Philosopher, sans-serif;}
input[type=number] {text-align: right;}
input[readonly="readonly"], input[readonly] {
border: none;
background: transparent;
color: #2e3436;
}
li {
margin-bottom: .2em;
}
li.category {
background: url(../icons/newspaper.png) left center no-repeat;
padding-left: 20px;
}
.thumbnail {
display: block;
position: relative;
float: left;
height: 140px;
width: 140px;
padding: 5px;
border: 0;
margin: 5px;
background: transparent url('../img/thumbnail-bg.png') top left no-repeat;
}
li.comment {
background: url(../icons/comment.png) left center no-repeat;
padding-left: 20px;
}
li.date, li.event {
background: url(../icons/date.png) left center no-repeat;
padding-left: 20px;
}
li.photo {
background: url(../icons/camera.png) left center no-repeat;
padding-left: 20px;
}
li.hanchan {
background: url(../icons/table.png) left center no-repeat;
padding-left: 20px;
}
li.time {
background: url(../icons/time.png) left center no-repeat;
padding-left: 20px;
}
li.django_view {
background: url(../icons/page_white_lightning.png) left center no-repeat;
padding-left: 20px;
}
li.html {
background: url(../icons/page_white_text.png) left center no-repeat;
padding-left: 20px;
}
li.location {
background: url(../icons/map.png) left center no-repeat;
padding-left: 20px;
}
li.pdf {
background: url(../icons/page_white_acrobat.png) left center no-repeat;
padding-left: 20px;
}
li.season {
background: url(../icons/star.png) left center no-repeat;
padding-left: 20px;
}
li.user {
background: url(../icons/user_red.png) left center no-repeat;
padding-left: 20px;
}
ol {
list-style: cjk-ideographic;
padding-left: 2em;
}
table {
border-collapse: collapse;
border-spacing: 0;
margin-bottom: 1em;
width: 100%;
}
table td {
border-bottom: 1px solid #d3d7cf;
border-top: 1px solid #d3d7cf;
padding: 2px;
vertical-align: middle;
}
table th {
background: #a40000;
color: #fff;
padding: 2px;
vertical-align: middle;
}
table th a:link, table th a:visited {
color: #FFF;
}
table tr:nth-child(2n+1) {
background-color: #eeeeec;
}
table tr:hover {
background-color: #eedcdc;
}
ul {
list-style: circle outside;
padding-left: 30px;
}
ul.comment_list {
font-size: small;
list-style: none;
padding: 0;
}
ul.event_list, ul.list {
list-style: none;
padding: 0;
}
ul.info {
list-style: none;
margin-bottom: 0.5em;
padding-left: 0;
}
ul.info li {
display: inline-block;
margin-right: 10px;
}
.buttonbar {
text-align: right;
border-radius: 10px;
background: #000000; /* Old browsers */
background: linear-gradient(to bottom, #45484d 0%,#000000 100%); /* W3C */
background: -moz-linear-gradient(top, #45484d 0%, #000000 100%); /* FF3.6+ */
background: -webkit-linear-gradient(top, #45484d 0%,#000000 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #45484d 0%,#000000 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #45484d 0%,#000000 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#45484d', endColorstr='#000000',GradientType=0 ); /* IE6-9 */
}
/** PAGINATOR **/
.pagination {
text-align:center
}
.pagination a, .pagination .current, .pagination .next, .pagination .previous {
display: inline-block;
text-decoration: none;
padding: 0 0.5em 0 0.5em;
}
.pagination .next {
float: right;
background: none;
}
.pagination .previous {
float: left;
background: none;
}
.center {
text-align: center;
}
.right {
text-align: right;
}
#redbox {
color: white;
/*background: #a40000 url('../img/red-swoop.png') top left no-repeat;*/
border-radius: 10px;
padding: 10px 5px 5px 5px;
background: #a90329; /* Old browsers */
background: -moz-linear-gradient(-45deg, #a90329 0%, #8f0222 44%, #6d0019 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#a90329), color-stop(44%,#8f0222), color-stop(100%,#6d0019)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(-45deg, #a90329 0%,#8f0222 44%,#6d0019 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(-45deg, #a90329 0%,#8f0222 44%,#6d0019 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(-45deg, #a90329 0%,#8f0222 44%,#6d0019 100%); /* IE10+ */
background: linear-gradient(135deg, #a90329 0%,#8f0222 44%,#6d0019 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#a90329', endColorstr='#6d0019',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}
#redbox h2:first-of-type {
margin: -1em 10px 0 0;
color: black;
}
#redbox h2, #redbox h3 {
color: white;
}
#redbox a:link, #redbox a:visited {
color: white;
font-weight: normal;
text-decoration: underline;
}
#redbox a.button:link, #redbox a.button:visited {
color: #2e3436;
font-weight: bold;
text-decoration: none;
}
ul.tabs {text-align: center}
ul.tabs li {display: inline-block}
ul.tabs li:after {content: " | "}
ul.tabs li:last-child:after {content: ""}
ul.tabs li a {color: #2e3436;}
ul.tabs li.active a {border-bottom: 3px solid #bc0a19; color: #bc0a19}
.comment {
display:table;
margin-bottom: 1em;
width: 100%;
padding: 0;
}
.social, .social:link {
display: inline-block;
font-family: "Social Icons";
font-style: normal;
font-weight: normal;
}
.facebook:before {content: "f";}
.google:before {content: "G";}
.twitter:before {content: "T";}
fieldset.comment {padding: 0}
fieldset.comment legend {margin-left: 15px}
fieldset.comment .buttonbar {margin: 0; width: 100%}
fieldset.comment .buttonbar {margin: 0; width: 100%}

View File

@@ -0,0 +1,562 @@
@media screen and (min-width: @min-desktop-width) {
#content {
width: 700px;
display: inline;
float: left;
margin: 0px 10px;
position: relative;
}
#display .next, #display .previous {
display: block;
position: absolute;
top: 0px;
width: 60px;
height: 100%;
margin: 0px;
padding: 0;
text-indent: 9999px;
overflow: hidden;
opacity: .5;
}
#display .next:hover, #display .previous:hover {
opacity: 1;
transition: all 0.2s ease-out;
}
#display .next {
background: transparent url(../img/right-arrow.png) no-repeat center center;
right: 10px;
z-index: 3;
}
#display .previous {
background: transparent url(../img/left-arrow.png) no-repeat center center;
left: 10px;
z-index: 2
}
#display img {
box-shadow: 1px 1px 5px 1px #444;
}
#sidebar {
width: 220px;
display: inline;
float: left;
margin: 0px 10px;
position: relative;
}
#footer {
margin: -170px auto 0;
position: relative;
top: 0;
width: 920px;
z-index: 30;
}
#footer p {
text-align: center;
}
#footer_bg {
background: url(../img/footer_bg.png) top center no-repeat;
height: 477px;
margin-top: -286px;
position: relative;
z-index: 2;
}
#siteheader {
height: 100px;
margin: 0 auto;
padding: 0;
position: relative;
width: 960px;
z-index: 50;
}
#header_bg {
position: absolute;
top: 0;
left: 0;
margin: 0;
padding: 0;
height: 400px;
width: 100%;
background: url(../img/header_bg.png) top center no-repeat;
text-align: center;
z-index: 1;
}
#maincontent {
margin: 0 auto;
min-height: 200px;
padding: 10px 0 50px 0;
position: relative;
width: 960px;
z-index: 19;
}
#mainnav {
left: 233px;
position: absolute;
top: 65px;
}
#mainnav a {
color: #000;
text-decoration: none;
font-weight: normal;
}
#mainnav a.active {
color: #bc0a19;
}
#mainnav a:hover {
color: #FFF;
}
#mainnav li {
display: inline-block;
font: normal small-caps 18px 'Amerika Sans', sans-serif;
min-width: 50px;
padding: 8px;
text-align: center;
text-shadow: 2px 2px 2px #2e3436;
}
#mainnav li:first-child {
padding-left: 0;
}
#mainnav li:last-child {
padding-right: 0;
}
ul.main_menu {
padding: 0px;
}
#messages {
clear: both;
margin: 0 auto;
padding: 8px 0 0 30px;
width: 920px;
}
#navigation, .pagination {
clear: both;
position: relative;
background: url(../img/navigation-bg.png) no-repeat left top;
height: 56px;
list-style: none;
margin: 0 auto;
padding: 8px 35px 0px 25px;
width: 900px;
z-index: 30;
}
#navigation a {
background: url(../img/navigation-separator.png) no-repeat right center;
color: #FFF;
display: block;
float: left;
font-weight: bold;
height: 33px;
padding: 17px 15px 0;
text-decoration: none;
}
#navigation a:hover, #navigation a.active {
background: url(../img/navigation-hover.png) repeat-x left top;
color: #3B3B3B;
}
#navigation li {
display: inline;
margin: 0;
}
#sitelogo {
background: url(../img/logo.png) no-repeat;
height: 110px;
left: 5px;
margin: 0;
padding: 0;
position: absolute;
text-indent: -9999px;
top: 5px;
width: 233px;
z-index: 99;
}
#sitelogo a {
display: block;
height: 110px;
left: 0;
position: absolute;
top: 0;
width: 233px;
}
#jumbotron {
clear: both;
position: relative;
margin: 20px auto 10px auto;
padding: 0 1px 0 0;
z-index: 5;
width: 940px; /* 620px */
min-height: 300px;
border: none;
border-radius: 10px;
background-repeat: no-repeat;
background-color: #333;
background-position: center left;
}
#jumbotron > h2, #jumbotron > h1 {
padding: 10px 5px;
font-size: 32pt;
color: #eff0ef;
position: absolute;
margin: 0;
left: 10px;
top: 33%;
max-width: 600px;
}
#teaser_text {
display: block;
position: absolute;
width: 620px;
min-height: 4em;
left: 0px;
bottom: 0px;
color: #FFF;
background: rgba(0, 0, 0, 0.5);;
font-size: 10pt;
padding: 5px 10px 5px 15px;
border-radius: 0px 0px 0px 10px;
}
#redbox {
position: absolute;
top: 0px;
right: 0px;
height: 280px;
width: 280px;
padding: 10px;
border-radius: 0px 10px 10px 0px;
}
#toggle, .toggle {
display: none;
}
#bottom_buttonbar {
position: absolute;
bottom: 0px;
left: -1px;
margin: 0;
width: 960px
}
#usernav {
background: url(../img/usernav-bg.png) top left no-repeat;
font-size: 14pt;
color: #FFF;
height: 50px;
padding: 5px 10px 4px 20px;
position: absolute;
right: 0;
text-align: right;
top: 0;
z-index: 50;
a {
color: #FFF;
}
}
#usernav img {
vertical-align: middle;
}
.clearfix {
zoom: 1px;
}
.clearfix:after {
clear: both;
}
.gallery {
display: inline;
float: left;
height: 200px;
margin: 10px;
overflow: hidden;
text-align: center;
width: 300px;
}
.gallery .thumbnail {
display: block;
float: none;
margin: 5px auto;
}
div.thumbnail a.delete_image {
position: absolute;
right: 4px;
bottom: 0px;
}
body {
background: #fff url(../img/background.png) repeat-y center top;
font: 12pt Philosopher, Georgia, serif;
line-height: 1;
margin: 0;
min-width: 960px;
padding: 0;
vertical-align: baseline;
}
fieldset {
border: none;
color: #2e3436;
border-radius: 10px;
margin: 10px 0 0 0;
padding: 0 10px 0 160px;
background: linear-gradient(135deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
background: -moz-linear-gradient(-45deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
background: -webkit-linear-gradient(-45deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
background: -o-linear-gradient(-45deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
background: -ms-linear-gradient(-45deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
vertical-align: top;
}
fieldset legend {
margin-top: -.1em;
margin-left: -150px;
color: #a40000;
font-family: 'Amerika Sans', sans-serif;
font-variant: small-caps;
font-weight: 400;
font-size: 16pt;
text-shadow: 2px 2px 2px #888;
}
fieldset div {
margin: 5px 0px;
}
fieldset .required {
font-weight: bold;
}
fieldset .buttonbar {
border-radius: 0px 0px 10px 10px;
margin: 0 -10px 0 -160px;
}
fieldset .help_text {
font-size: small;
}
fieldset .field_name {
text-align: right;
width: 140px;
margin: 0 20px 0 -160px;
padding-top: 3px;
display: inline-block;
clear: left;
vertical-align: top;
}
fieldset input, fieldset textarea {
border: 1px solid #999999;
border-radius: 5px;
padding: 2px;
margin: 0;
}
fieldset input[maxlength="255"], fieldset textarea {
box-sizing: border-box;
width: 100%;
max-width: 760px;
}
fieldset ul {
display: inline-block;
padding: 0;
}
fieldset ul li {
list-style: none;
display: inline;
}
fieldset table {
display: inline-table;
max-width: 760px;
}
img.partner, img.partner_right, img.posting_image {
border: 1px solid #babdb6;
float: left;
height: 120px;
margin: 0 20px 0 0;
padding: 4px;
width: 200px;
}
img.partner:nth-of-type(odd) {
float: right;
}
ul.tabs {
margin: 10px;
padding: 0;
border-radius: 10px;
background: #bc0a19 url(../img/buttonbar.gif) repeat-x;
text-align: left;
list-style: none;
}
ul.tabs li {
display: inline-block;
}
ul.tabs li:after {
content: ''
}
ul.tabs li a {
display: inline-block;
border: 1px solid #d3d7cf;
border-radius: 5px;
background-color: #f9f9f9;
color: #2e3436;
font: bold 14px Philosopher sans-serif;
box-shadow: inset 0px 1px 0px 0px #ffffff;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #f9f9f9), color-stop(1, #e9e9e9));
background: -moz-linear-gradient(center top, #f9f9f9 5%, #e9e9e9 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f9f9f9', endColorstr='#e9e9e9');
display: inline-block;
padding: 4px 10px 2px 10px;
margin: 3px 0px 0px 15px;
text-decoration: none;
text-shadow: 1px 1px 0px #ffffff;
}
ul.tabs li.active a {
background-color: #fa665a;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #fa665a), color-stop(1, #d34639));
background: -moz-linear-gradient(center top, #fa665a 5%, #d34639 100%);
color: #ffffff;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fa665a', endColorstr='#d34639');
border: 1px solid #d83526;
box-shadow: inset 0px 1px 0px 0px #ffffff;
text-shadow: 1px 1px 0px #98231a;
}
.pagination a, .pagination .current, .pagination .next, .pagination .previous {
display: inline-block;
padding: 2px;
color: #FFF;
font-weight: bold;
height: 33px;
text-decoration: none;
padding: 17px 0.5em 0 0.5em;
}
.pagination .current {
color: #a40000;
}
.pagination .disabled {
color: #ccc
}
img.avatar {
border: none;
box-shadow: 2px 2px 2px #888;
width: 60px;
height: 60px;
}
.comment_picture {
display: table-cell;
padding: 0px 10px;
width: 60px;
vertical-align: top;
}
.comment_header {
display: table-cell;
padding: 0px 10px;
width: 140px;
vertical-align: top;
}
.comment_header h3 {
margin: 0
}
.comment_text {
display: table-cell;
padding: 0px 10px;
width: auto;
max-width: 700px;
}
.userinfo {
width: 140px;
}
.grid_6 {
width: 460px;
}
.grid_7 {
width: 540px;
}
.grid_8 {
width: 620px;
}
.grid_9 {
width: 700px;
}
.grid_10 {
width: 780px;
}
.grid_11 {
width: 860px;
}
.grid_12 {
width: 940px;
}
#recaptcha_widget_div {
margin-top: -20px;
}
#google_maps {
position: relative;
top: 0px;
left: 0px;
height: 280px;
padding: 10px;
border-radius: 0px 10px 10px 0px;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
@min-desktop-width: 700px;
@max-mobile-with: 699px;
@import "common";
@import "desktop";
@import "mobile";
@import "print";

View File

@@ -0,0 +1,462 @@
@media screen and (max-width: @max-mobile-with) {
body {
background: url('../img/background_mobile.png') no-repeat top center;
font: 12pt "Philosopher", Georgia, serif;
-webkit-animation: bugfix infinite 1s;
}
#display .grid_10 {
margin: 0;
position: relative;
z-index: 1
}
#display .next, #display .previous {
display: block;
position: absolute;
top: 0px;
width: 45px;
height: 100%;
margin: 0px;
padding: 0;
text-indent: 9999px;
overflow: hidden;
opacity: .5;
}
#display .next:hover, #display .previous:hover {
opacity: .9;
transition: all 0.2s ease-out;
}
#display .next {
background: transparent url(../img/right-arrow.png) no-repeat center center;
right: 0px;
z-index: 3;
}
#display .previous {
background: transparent url(../img/left-arrow.png) no-repeat center center;
left: 0px;
z-index: 2
}
#footer_bg {
display: none;
}
#top_bg {
display: none;
}
#footer {
border-top: 1px solid black;
text-align: center;
}
#header {
position: relative;
min-height: 54px;
height: 100%;
padding: 0 20px;
}
img.posting_image, img.partner {
float: left;
width: 99px;
height: 59px;
padding: 2px;
margin: 1em 0.5em 0 0;
border: 1px solid #babdb6;
}
img {
max-width: 100%;
height: auto;
}
#topnav a {
display: inline-block;
color: #000;
font: 400 small-caps 24pt 'Amerika Sans', sans-serif;
min-width: 80px;
text-align: center;
text-decoration: none;
text-shadow: 2px 2px 2px #2e3436;
padding: 5px;
}
#topnav a.active {
color: #bc0a19;
}
#topnav a:hover {
color: #FFF;
}
#sitelogo {
background: url('../img/logo_mobile.png') no-repeat;
width: 114px;
height: 54px;
left: 5px;
margin: 0;
padding: 0;
text-indent: -9999px;
top: 5px;
z-index: 20;
float: left;
}
#teaser {
background: none;
margin-bottom: 1em;
}
#teaser_text {
background: rgba(255, 255, 255, 0.5);
}
#navigation {
margin: 10px 0;
padding: 0;
background: #45484d url("../img/navigation-mobile.png") top left repeat-x;
background-size: contain;
}
#navigation li {
display: inline-block;
padding: 0.5em 0.3em 0.5em 0.5em;
text-align: center;
border-left: 1px solid #ffffff;
margin: 0;
}
#navigation li:first-of-type {
border: none;
}
#navigation a {
font: bold 12px Arial;
color: #FFF;
text-decoration: none;
}
#sitelogo a {
display: block;
width: 114px;
height: 54px;
}
#siteheader:after {
content: ".";
clear: both;
display: block;
visibility: hidden;
height: 0px;
}
/* Dynamische Menü */
#mainnav {
display: block;
float: right
}
#toggle, .toggle {
display: none;
}
#toggle:checked ~ .main_menu {
display: block;
opacity: 1;
}
#toggle:checked ~ .toggle, .toggle:hover {
background: #45ABD6;
}
.comment_picture {
display: table-cell;
padding: 0px 10px;
width: 60px;
vertical-align: top;
}
.comment_header {
display: table-cell;
padding: 0px 10px;
width: 140px;
vertical-align: top;
}
.comment_header h3 {
margin: 0
}
.toggle {
z-index: 2;
display: block;
position: relative;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
.main_menu {
display: none;
opacity: 0;
width: 100%;
position: absolute;
z-index: 999;
right: 0;
background: black;
border-top: 1px solid #a40000;
margin: 0;
padding: 0;
box-shadow: outset 0px 1px 0px 0px #ffffff;
}
.main_menu > li {
height: auto;
list-style: none;
float: left;
z-index: 100;
background: #eeeeec;
display: block;
width: 100%;
margin: 0;
}
.main_menu > li > a {
display: block;
width: 100%;
height: auto;
text-decoration: none;
font: 400 small-caps 18px 'Amerika Sans', sans-serif;
color: black;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
float: right;
padding: 1em;
line-height: 1;
-webkit-transition: all 0.25s linear;
-moz-transition: all 0.25s linear;
-o-transition: all 0.25s linear;
transition: all 0.25s linear;
}
#jumbotron {
background: none !important;
}
#redbox {
margin-top: 1em;
display: block;
}
.main_menu > li > a:hover, .menu > li > a:focus {
box-shadow: inset 5px 0px #a40000;
color: #a40000;
}
.grid_6, .grid_7, .grid_8, .grid_9, .grid_10, .grid_11, .grid_12 {
width: 100%;
clear: both;
}
.player {
display: inline;
float: left;
margin-left: 10px;
margin-right: 10px;
position: relative;
box-sizing: border-box;
-moz-box-sizing: border-box;
min-width: 60px;
}
.toggle {
display: block;
width: 150px;
margin: 8px 0;
padding: 10px;
background: #a40000;
text-align: center;
color: #FFFFFF;
content: 'Main Menu';
border-radius: 2px;
box-sizing: border-box;
transition: all 0.5s linear;
-webkit-border-radius: 2px;
-webkit-transition: all 0.5s linear;
-webkit-box-sizing: border-box;
-moz-transition: all 0.5s linear;
-moz-box-sizing: border-box;
-o-transition: all 0.5s linear;
}
.thumbnail {
display: block;
position: relative;
float: left;
height: 70px;
padding: 0;
width: 70px;
margin: 5px;
box-shadow: 2px 2px 5px #888;
}
.thumbnail img {
height: 70px;
width: 70px;
}
.thumbnail a.delete_image {
display: none
}
img.thumbnail {
display: block;
float: left;
height: 70px;
width: 70px;
margin: 5px;
box-shadow: 2px 2px 5px #888;
}
fieldset {
border: none;
color: #2e3436;
border-radius: 10px;
margin: 10px 0 0 0;
padding: 0 10px 0 160px;
background: #f2f5f6; /* Old browsers */
background: linear-gradient(135deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
background: -moz-linear-gradient(-45deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%, #f2f5f6),
color-stop(37%, #e3eaed), color-stop(100%, #c8d7dc));
background: -webkit-linear-gradient(-45deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
background: -o-linear-gradient(-45deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
background: -ms-linear-gradient(-45deg, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
}
fieldset legend {
margin-top: -.1em;
margin-left: -150px;
color: #a40000;
font-family: 'Amerika Sans', sans-serif;
font-variant: small-caps;
font-weight: 400;
font-size: 16pt;
text-shadow: 2px 2px 2px #888;
}
fieldset .required {
font-weight: bold;
}
fieldset .error {
color: #a40000
}
fieldset .buttonbar {
border-radius: 0px 0px 10px 10px;
margin: 0 -10px 0 -160px;
}
fieldset .help_text {
font-size: small;
}
fieldset .field_name {
text-align: right;
width: 140px;
margin: 0 20px 0 -160px;
padding-top: 3px;
display: inline-block;
clear: left;
}
fieldset input, fieldset textarea {
border: 1px solid #999999;
border-radius: 5px;
padding: 2px;
margin: 0;
font-size: 12pt;
}
fieldset input[maxlength="255"], fieldset textarea {
width: 99%;
box-sizing: border-box;
}
fieldset ul {
display: inline-block;
padding: 0;
}
fieldset ul li {
list-style: none;
display: inline;
}
ul.tabs {
margin-top: 1em;
}
.pagination {
clear: both;
}
.pagination a, .pagination .current, .pagination .next, .pagination .previous {
display: inline-block;
text-decoration: none;
padding: 0 0.5em 0 0.5em;
}
.gallery {
float: left;
width: 150px;
height: 150px;
margin: 10px;
}
.gallery h3 {
font-size: 12pt;
}
.comment {
display: block
}
.comment_picture {
display: block;
float: left;
vertical-align: top;
width: 60px;
}
.comment_header {
display: block;
float: left;
padding: 0px 10px;
vertical-align: top;
width: 140px;
}
.comment_header h3 {
margin: 0
}
.comment_text {
border-top: 1px solid #45484d;
display: block;
margin-left: 0px 10px;
padding-top: 0.5em;
clear: both;
}
}

View File

@@ -0,0 +1,91 @@
@media print {
@page {
size: portrait;
margin: 0.5cm 0.5cm 0.5cm 1cm;
orphans: 3;
widows: 3;
}
nav, aside, #comment_form, #navigation, #mainnav, #usernav, #bottom_buttonbar, #footer > form {
display: none !important;
}
#footer {
width: 100%;
padding-top: 0.5em;
border-top: 1px solid black;
text-align: center;
}
* {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
a:link, a:visited {
color: black;
font-weight: bold;
}
#sitelogo {
background: url(../img/logo.png) top right no-repeat;
background-size: contain;
left: 0;
margin: 0;
padding: 0;
line-height: 1cm;
font-family: 'Amerika Sans', Helvetica;
font-size: 8pt;
top: 5px;
z-index: 99;
}
#jumbotron {
background: none !important;
}
body, article {
width: 100%;
margin: 0;
padding: 0;
color: #000;
background: #fff;
}
h1 {
font-size: 32pt;
}
h2, h3, h4, h5, h6 {
text-shadow: none;
page-break-after: avoid;
}
img {
max-width: 100% !important;
}
ul, img {
page-break-inside: avoid;
}
#comment_form, #comments, #footer, #navigation, #mainnav, #usernav, #bottom_buttonbar, #footer > form {
display: none;
}
.more_link {
display: none
}
#maincontent nav {
display: none
}
#maincontent aside {
display: none
}
.grid_6, grid_7, .grid_8, grid_9, .grid_10, .grid_11, .grid_12 {
width: 100%
}
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/kasu/static/icons/accept.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
src/kasu/static/icons/anchor.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Some files were not shown because too many files have changed in this diff Show More