Anpassungen für das Hosting bei Djangoeurope und damit verbundenen Versionen Django 1.8 und Python 2.7
1
src/aggregator/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
21
src/aggregator/admin.py
Normal 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
@@ -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
|
||||
1
src/aggregator/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -
|
||||
1
src/aggregator/management/commands/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
#
|
||||
21
src/aggregator/management/commands/update-feeds.py
Normal 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
@@ -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
|
||||
16
src/aggregator/sitemaps.py
Normal 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
|
||||
31
src/aggregator/templatetags/aggregator.py
Normal 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
64
src/content/admin.py
Normal 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)
|
||||
50
src/content/context_processors.py
Normal 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
@@ -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
@@ -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
|
||||
249
src/content/locale/de/LC_MESSAGES/django.po
Normal 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"
|
||||
1
src/content/management/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
#
|
||||
1
src/content/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
#!/usr/bin/python
|
||||
83
src/content/management/commands/importarticles.py
Normal 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)
|
||||
43
src/content/management/commands/importgalleries.py
Normal 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
@@ -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
@@ -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'),
|
||||
)
|
||||
93
src/content/templates/content/article_archive.html
Normal 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="»" /></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 %}
|
||||
9
src/content/templates/content/article_archive_month.html
Normal 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 %}
|
||||
20
src/content/templates/content/article_archive_year.html
Normal 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 %}
|
||||
54
src/content/templates/content/article_detail.html
Normal 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> </p>
|
||||
<div itemprop="articleBody">{{ article.content }}</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<p> </p>
|
||||
<img alt="{{article.category.name}}" src="{{article.posting_image.url}}" class="posting_image" itemprop="image"/>
|
||||
<p> </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 %}
|
||||
48
src/content/templates/content/article_form.html
Normal 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 %}
|
||||
27
src/content/templates/content/clear_page.html
Normal 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 %}
|
||||
38
src/content/templates/content/page.html
Normal 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 %}
|
||||
71
src/content/templates/content/page_form.html
Normal 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 %}
|
||||
66
src/content/templates/content/page_pdf.html
Normal 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>
|
||||
0
src/content/templatetags/__init__.py
Normal file
47
src/content/templatetags/fieldset_extras.py
Normal 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
@@ -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
@@ -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
@@ -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
50
src/events/admin.py
Normal 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)
|
||||
15
src/events/context_processors.py
Normal 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
@@ -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', )
|
||||
302
src/events/locale/de/LC_MESSAGES/django.po
Normal 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
@@ -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)
|
||||
76
src/events/templates/events/event_archive.html
Normal 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 %}
|
||||
144
src/events/templates/events/event_detail.html
Normal 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> </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}}&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">
|
||||
|
||||
</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}}&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 %}
|
||||
28
src/events/templates/events/event_form.html
Normal 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 %}
|
||||
49
src/events/templates/events/event_list.html
Normal 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 %}
|
||||
23
src/events/templates/events/event_site.html
Normal 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 %}
|
||||
|
||||
|
||||
24
src/events/templates/events/page.html
Normal 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
__author__ = 'christian'
|
||||
42
src/gallery/forms.py
Normal 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
@@ -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)
|
||||
57
src/gallery/templates/gallery/photo_detail.html
Normal 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> » {{ 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&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 %}
|
||||
|
||||
|
||||
17
src/gallery/templates/gallery/photo_gallery.html
Normal 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 %}
|
||||
|
||||
|
||||
53
src/gallery/templates/gallery/photo_upload.html
Normal 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
@@ -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
@@ -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
106
src/kasu/image_models.py
Normal 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
|
||||
1402
src/kasu/locale/de/LC_MESSAGES/django.po
Normal file
34
src/kasu/logging.conf
Normal 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=
|
||||
411
src/kasu/static/css/common.less
Normal 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%}
|
||||
562
src/kasu/static/css/desktop.less
Normal 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;
|
||||
}
|
||||
}
|
||||
1
src/kasu/static/css/kasu.css
Normal file
7
src/kasu/static/css/kasu.less
Normal file
@@ -0,0 +1,7 @@
|
||||
@min-desktop-width: 700px;
|
||||
@max-mobile-with: 699px;
|
||||
|
||||
@import "common";
|
||||
@import "desktop";
|
||||
@import "mobile";
|
||||
@import "print";
|
||||
462
src/kasu/static/css/mobile.less
Normal 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;
|
||||
}
|
||||
}
|
||||
91
src/kasu/static/css/print.less
Normal 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%
|
||||
}
|
||||
}
|
||||
BIN
src/kasu/static/fonts/amerikasans.eot
Normal file
1202
src/kasu/static/fonts/amerikasans.svg
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
src/kasu/static/fonts/amerikasans.ttf
Normal file
BIN
src/kasu/static/fonts/amerikasans.woff
Normal file
3396
src/kasu/static/fonts/philosopher.svg
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
src/kasu/static/fonts/philosopher.ttf
Normal file
BIN
src/kasu/static/fonts/philosopher.woff
Normal file
BIN
src/kasu/static/fonts/social.ttf
Normal file
BIN
src/kasu/static/fonts/social.woff
Normal file
BIN
src/kasu/static/icons/accept.png
Executable file
|
After Width: | Height: | Size: 781 B |
BIN
src/kasu/static/icons/add.png
Normal file
|
After Width: | Height: | Size: 733 B |
BIN
src/kasu/static/icons/anchor.png
Executable file
|
After Width: | Height: | Size: 523 B |
BIN
src/kasu/static/icons/application.png
Executable file
|
After Width: | Height: | Size: 464 B |
BIN
src/kasu/static/icons/application_add.png
Executable file
|
After Width: | Height: | Size: 619 B |
BIN
src/kasu/static/icons/application_cascade.png
Executable file
|
After Width: | Height: | Size: 524 B |
BIN
src/kasu/static/icons/application_delete.png
Executable file
|
After Width: | Height: | Size: 610 B |
BIN
src/kasu/static/icons/application_double.png
Executable file
|
After Width: | Height: | Size: 533 B |
BIN
src/kasu/static/icons/application_edit.png
Executable file
|
After Width: | Height: | Size: 703 B |
BIN
src/kasu/static/icons/application_error.png
Executable file
|
After Width: | Height: | Size: 656 B |
BIN
src/kasu/static/icons/application_form.png
Executable file
|
After Width: | Height: | Size: 467 B |
BIN
src/kasu/static/icons/application_form_add.png
Executable file
|
After Width: | Height: | Size: 592 B |
BIN
src/kasu/static/icons/application_form_delete.png
Executable file
|
After Width: | Height: | Size: 605 B |
BIN
src/kasu/static/icons/application_form_edit.png
Executable file
|
After Width: | Height: | Size: 714 B |
BIN
src/kasu/static/icons/application_form_magnify.png
Executable file
|
After Width: | Height: | Size: 612 B |
BIN
src/kasu/static/icons/application_get.png
Executable file
|
After Width: | Height: | Size: 581 B |
BIN
src/kasu/static/icons/application_go.png
Executable file
|
After Width: | Height: | Size: 634 B |
BIN
src/kasu/static/icons/application_home.png
Executable file
|
After Width: | Height: | Size: 685 B |
BIN
src/kasu/static/icons/application_key.png
Executable file
|
After Width: | Height: | Size: 670 B |
BIN
src/kasu/static/icons/application_lightning.png
Executable file
|
After Width: | Height: | Size: 656 B |
BIN
src/kasu/static/icons/application_link.png
Executable file
|
After Width: | Height: | Size: 701 B |
BIN
src/kasu/static/icons/application_osx.png
Executable file
|
After Width: | Height: | Size: 487 B |
BIN
src/kasu/static/icons/application_osx_terminal.png
Executable file
|
After Width: | Height: | Size: 525 B |