migration more code into git
This commit is contained in:
parent
042b5ffd98
commit
ea5649a2eb
|
@ -0,0 +1,15 @@
|
||||||
|
* Person:
|
||||||
|
* nick
|
||||||
|
* Vorname
|
||||||
|
* Nachname
|
||||||
|
* birthday
|
||||||
|
* bezahlt
|
||||||
|
|
||||||
|
* Ernährung
|
||||||
|
* carnivor, vegetarisch, vegan
|
||||||
|
|
||||||
|
Buchung:
|
||||||
|
* Frühstück
|
||||||
|
* Schlafplatz
|
||||||
|
* anwesende Tage
|
||||||
|
* Extrawürste (anmerkungen, Wünsche)
|
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='TracBooking', version='0.1',
|
||||||
|
packages=['tracbooking',],
|
||||||
|
entry_points = """
|
||||||
|
[trac.plugins]
|
||||||
|
booking = tracbooking
|
||||||
|
""",
|
||||||
|
package_data={'tracbooking': ['templates/*.html',
|
||||||
|
'htdocs/css/*.css',
|
||||||
|
'htdocs/images/*']},
|
||||||
|
author = "Stefan Kögl",
|
||||||
|
author_email = "hotshelf@trac.ctdo.de",
|
||||||
|
description = "a plugin for event booking",
|
||||||
|
license = "GPL",
|
||||||
|
keywords = "register, events",
|
||||||
|
url = "http://trac.ctdo.de/dev/", # project home page, if any
|
||||||
|
)
|
|
@ -0,0 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import model
|
||||||
|
import web_ui
|
||||||
|
import admin
|
||||||
|
import macros
|
|
@ -0,0 +1,586 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
from os.path import join
|
||||||
|
from re import match
|
||||||
|
|
||||||
|
from trac.admin import IAdminPanelProvider
|
||||||
|
from trac.core import *
|
||||||
|
from trac.web.chrome import add_stylesheet, add_warning, add_ctxtnav, add_script, add_notice
|
||||||
|
from trac.web import RequestDone
|
||||||
|
from trac.util.translation import _, N_, gettext
|
||||||
|
from trac.util.datefmt import utc
|
||||||
|
from trac.util.text import to_unicode
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from tracbooking.model import *
|
||||||
|
from tracbooking.report import create_report
|
||||||
|
from tracbooking.utils import validate_id, time_parse, date_parse, get_tz, get_option_count, validate_email
|
||||||
|
from tracbooking.web_ui import UserUploadComponent
|
||||||
|
|
||||||
|
def get_actions(myactions):
|
||||||
|
actions = []
|
||||||
|
for action in myactions:
|
||||||
|
if isinstance(action, tuple):
|
||||||
|
actions.append(action[0])
|
||||||
|
else:
|
||||||
|
actions.append(action)
|
||||||
|
return actions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BookingAdminPanel(Component):
|
||||||
|
|
||||||
|
implements(IAdminPanelProvider)
|
||||||
|
|
||||||
|
# IAdminPanelProvider methods
|
||||||
|
|
||||||
|
def get_admin_panels(self, req):
|
||||||
|
if 'BOOKING_ADMIN' in req.perm:
|
||||||
|
yield ('booking', _('Booking System'), 'reminder',
|
||||||
|
'Erinnerungen verwalten')
|
||||||
|
yield ('booking', _('Booking System'), 'events',
|
||||||
|
'Events verwalten')
|
||||||
|
yield ('booking', _('Booking System'), 'options',
|
||||||
|
'Optionen verwalten')
|
||||||
|
yield ('booking', _('Booking System'), 'attendees',
|
||||||
|
'Teilnehmer')
|
||||||
|
yield ('booking', _('Booking System'), 'options2events',
|
||||||
|
u'Optionen mit Events verknüpfen')
|
||||||
|
yield ('booking', _('Booking System'), 'einkauf',
|
||||||
|
u'Bestellungen einsehen')
|
||||||
|
|
||||||
|
def render_admin_panel(self, req, cat, page, path_info):
|
||||||
|
req.perm.require('BOOKING_ADMIN')
|
||||||
|
print "render", req, cat, page, path_info
|
||||||
|
if page == "events":
|
||||||
|
return self._render_events(req, cat, page, path_info)
|
||||||
|
if page == "attendees":
|
||||||
|
return self._render_attendees(req, cat, page, path_info)
|
||||||
|
if page == "options":
|
||||||
|
return self._render_options(req, cat, page, path_info)
|
||||||
|
if page == "options2events":
|
||||||
|
return self._render_options2events(req, cat, page, path_info)
|
||||||
|
if page == "reminder":
|
||||||
|
return self._render_reminder(req, cat, page, path_info)
|
||||||
|
if page == "einkauf":
|
||||||
|
return self._render_einkauf(req, cat, page, path_info)
|
||||||
|
|
||||||
|
#return self._render_admin_panel(req, cat, page, path_info)
|
||||||
|
|
||||||
|
def _render_reminder(self, req, cat, page, bookingtype):
|
||||||
|
print "BookingReminderManager._render_admin_panel()"
|
||||||
|
add_stylesheet (req, 'hw/css/booking.css')
|
||||||
|
key = req.path_info
|
||||||
|
m = match(r'/admin/booking/reminder/(\d+)$', key)
|
||||||
|
if not m:
|
||||||
|
if key == "/admin/booking/reminder":
|
||||||
|
return "admin_reminder.html", {"events" : Event.fetch_all(self.env), "reminders" : None}
|
||||||
|
e_id = int(m.group(1))
|
||||||
|
event = Event.fetch_one(self.env, e_id)
|
||||||
|
if req.method == "POST":
|
||||||
|
if req.args.get("add") and \
|
||||||
|
req.args.has_key("text") and \
|
||||||
|
req.args.has_key("notify_on"):
|
||||||
|
text = req.args.get("text")
|
||||||
|
notify_on = req.args.get("notify_on")
|
||||||
|
notify_on = time_parse(notify_on)
|
||||||
|
reminder = BookingReminder(self.env, 0, e_id, text, notify_on)
|
||||||
|
reminder.commit()
|
||||||
|
elif req.args.has_key("save") and req.args.has_key("sel"):
|
||||||
|
sel = req.args.get('sel')
|
||||||
|
if isinstance(sel, basestring):
|
||||||
|
sel = [sel,]
|
||||||
|
for key in sel:
|
||||||
|
BookingReminder.delete(self.env, int(key))
|
||||||
|
return "admin_reminder.html", {"reminders" : BookingReminder.fetch_by_event(self.env, e_id), "eventname" : event.name}
|
||||||
|
|
||||||
|
def _render_events(self, req, cat, page, bookingtype):
|
||||||
|
|
||||||
|
add_stylesheet (req, 'hw/css/booking.css')
|
||||||
|
add_stylesheet (req, 'hw/css/ui.all.css')
|
||||||
|
add_script (req, 'hw/script/jquery-ui-1.6.custom.min.js')
|
||||||
|
key = req.path_info
|
||||||
|
data = {}
|
||||||
|
e_id = None
|
||||||
|
|
||||||
|
m = match(r'/admin/booking/events/(\d+)$', key)
|
||||||
|
if m:
|
||||||
|
e_id = int(m.group(1))
|
||||||
|
event = Event.fetch_one(self.env, e_id)
|
||||||
|
account = EventAccount.fetch_by_event(self.env, e_id)
|
||||||
|
else:
|
||||||
|
event = Event(self.env, 0, "", "", datetime.now(utc), datetime.now(utc))
|
||||||
|
account = EventAccount(self.env, 0, 0, "", "", "", "", "", "")
|
||||||
|
|
||||||
|
if req.method == "POST":
|
||||||
|
if req.args.get("add") and \
|
||||||
|
req.args.has_key("name") and \
|
||||||
|
req.args.has_key("date_begin") and \
|
||||||
|
req.args.has_key("date_end") and \
|
||||||
|
req.args.has_key("time_begin") and \
|
||||||
|
req.args.has_key("time_end"):
|
||||||
|
name = req.args.get("name")
|
||||||
|
|
||||||
|
date_begin = date_parse(req.args.get("date_begin"))
|
||||||
|
time_begin = time_parse(req.args.get("time_begin"))
|
||||||
|
dt_begin = datetime.combine(date_begin, time_begin)
|
||||||
|
dt_begin = dt_begin.replace(tzinfo=utc)
|
||||||
|
|
||||||
|
date_end = date_parse(req.args.get("date_end"))
|
||||||
|
time_end = time_parse(req.args.get("time_end"))
|
||||||
|
dt_end = datetime.combine(date_end, time_end)
|
||||||
|
dt_end = dt_end.replace(tzinfo=utc)
|
||||||
|
|
||||||
|
date_begin = date_parse(req.args.get("edit_deadline_date"))
|
||||||
|
time_begin = time_parse(req.args.get("edit_deadline_time"))
|
||||||
|
edit_deadline = datetime.combine(date_begin, time_begin)
|
||||||
|
edit_deadline = edit_deadline.replace(tzinfo=utc)
|
||||||
|
|
||||||
|
payment_deadline_date = date_parse(req.args.get("payment_deadline_date"))
|
||||||
|
payment_deadline_time = time_parse(req.args.get("payment_deadline_time"))
|
||||||
|
payment_deadline = datetime.combine(payment_deadline_date, payment_deadline_time)
|
||||||
|
payment_deadline = payment_deadline.replace(tzinfo=utc)
|
||||||
|
|
||||||
|
account_owner = req.args.get("account_owner")
|
||||||
|
account_no = req.args.get("account_no")
|
||||||
|
bank_name = req.args.get("bank_name")
|
||||||
|
bank_no = req.args.get("bank_no")
|
||||||
|
first_reason = req.args.get("first_reason")
|
||||||
|
#second_reason = req.args.get("second_reason")
|
||||||
|
|
||||||
|
description = req.args.get("description", "")
|
||||||
|
|
||||||
|
event.name = name
|
||||||
|
event.description = description
|
||||||
|
event.time_begin = dt_begin
|
||||||
|
event.time_end = dt_end
|
||||||
|
event.edit_deadline = edit_deadline
|
||||||
|
event.payment_deadline = payment_deadline
|
||||||
|
account.account_owner = account_owner
|
||||||
|
account.account_no = account_no
|
||||||
|
account.bank_name = bank_name
|
||||||
|
account.bank_no = bank_no
|
||||||
|
account.first_reason = first_reason
|
||||||
|
#account.second_reason = second_reason
|
||||||
|
|
||||||
|
|
||||||
|
if not e_id:
|
||||||
|
event.commit()
|
||||||
|
account.commit()
|
||||||
|
else:
|
||||||
|
event.update()
|
||||||
|
account.update()
|
||||||
|
elif req.args.has_key("save") and req.args.has_key("rsel"):
|
||||||
|
sel = req.args.get('rsel')
|
||||||
|
if isinstance(sel, basestring):
|
||||||
|
sel = [sel,]
|
||||||
|
for key in sel:
|
||||||
|
e_id = int(key)
|
||||||
|
Event.delete(self.env, e_id)
|
||||||
|
elif req.args.has_key("save") and req.args.has_key("copy"):
|
||||||
|
e_id = int(req.args.get('copy'))
|
||||||
|
event = Event.copy(self.env, e_id)
|
||||||
|
elif req.args.has_key("save") and req.args.has_key("finish"):
|
||||||
|
e_id = int(req.args.get('finish'))
|
||||||
|
attendees = Attendee.fetch_all(self.env, e_id)
|
||||||
|
for attendee in attendees:
|
||||||
|
attendee.finished = True
|
||||||
|
attendee.update()
|
||||||
|
event = Event.fetch_one(self.env, e_id)
|
||||||
|
event.name += " (Abgeschlossen)"
|
||||||
|
event.update()
|
||||||
|
req.redirect(req.href("/admin/booking/events", e_id))
|
||||||
|
|
||||||
|
data["event"] = event
|
||||||
|
data["event_account"] = account
|
||||||
|
data["events"] = Event.fetch_all(self.env)
|
||||||
|
return "admin_events.html", data
|
||||||
|
|
||||||
|
def _render_options(self, req, cat, page, bookingtype):
|
||||||
|
print "AvailableOptionsManager._render_admin_panel()"
|
||||||
|
add_stylesheet (req, 'hw/css/booking.css')
|
||||||
|
data = {}
|
||||||
|
key = req.path_info
|
||||||
|
m = match(r'/admin/booking/options/(\d+)$', key)
|
||||||
|
option_id = None
|
||||||
|
option = AvailableOption(self.env, 0, "", "", 0.0, 0.0, 0, 0, 0, 0, 0)
|
||||||
|
if m:
|
||||||
|
option_id = int(m.group(1))
|
||||||
|
option = AvailableOption.fetch_one(self.env, option_id)
|
||||||
|
if req.method == "POST":
|
||||||
|
if req.args.get("add") and \
|
||||||
|
req.args.has_key("name") and \
|
||||||
|
req.args.has_key("price"):
|
||||||
|
name = req.args.get("name")
|
||||||
|
desc = req.args.get("desc")
|
||||||
|
price = float(req.args.get("price"))
|
||||||
|
minc = int(req.args.get("min_count", 0))
|
||||||
|
maxc = int(req.args.get("max_count", 0))
|
||||||
|
supplier_id = int(req.args.get("supplier_id", 0))
|
||||||
|
ext_id = req.args.get("ext_id", None)
|
||||||
|
stock_count = int(req.args.get("stock_count", 0))
|
||||||
|
option.name = name
|
||||||
|
option.price = price
|
||||||
|
option.description = desc
|
||||||
|
option.min_count = minc
|
||||||
|
option.max_count = maxc
|
||||||
|
option.supplier_id = supplier_id
|
||||||
|
option.ext_id = ext_id
|
||||||
|
option.stock_count = stock_count
|
||||||
|
if option_id:
|
||||||
|
option.update()
|
||||||
|
else:
|
||||||
|
option.commit()
|
||||||
|
req.redirect(req.href.admin("booking", "options"))
|
||||||
|
elif req.args.has_key("save"):
|
||||||
|
if req.args.has_key("rsel"):
|
||||||
|
sel = req.args.get('rsel')
|
||||||
|
if isinstance(sel, basestring):
|
||||||
|
sel = [sel,]
|
||||||
|
for key in sel:
|
||||||
|
try:
|
||||||
|
ao_id = int(key)
|
||||||
|
except ValueError:
|
||||||
|
raise TracError("wrong value")
|
||||||
|
AvailableOption.delete(self.env, ao_id)
|
||||||
|
a_ids = set()
|
||||||
|
if req.args.has_key("actives"):
|
||||||
|
sel = req.args.get('actives')
|
||||||
|
if isinstance(sel, basestring):
|
||||||
|
sel = [sel,]
|
||||||
|
for key in sel:
|
||||||
|
try:
|
||||||
|
a_ids.add(int(key))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
options = AvailableOption.fetch_all(self.env)
|
||||||
|
for option in options:
|
||||||
|
if option.active:
|
||||||
|
if not option.ao_id in a_ids:
|
||||||
|
option.active = 0
|
||||||
|
option.update()
|
||||||
|
elif option.ao_id in a_ids:
|
||||||
|
option.active = 1
|
||||||
|
option.update()
|
||||||
|
data["options"] = AvailableOption.fetch_all(self.env)
|
||||||
|
data["suppliers"] = Supplier.fetch_all(self.env)
|
||||||
|
data["option"] = option
|
||||||
|
return "admin_options.html", data
|
||||||
|
|
||||||
|
def _render_attendees(self, req, cat, page, bookingtype):
|
||||||
|
|
||||||
|
print "RegistrationOverview._render_admin_panel()"
|
||||||
|
|
||||||
|
add_stylesheet (req, 'hw/css/booking.css')
|
||||||
|
key = req.path_info
|
||||||
|
m1 = match(r'/admin/booking/attendees/(\d+)/(\d+)$', key)
|
||||||
|
m2 = match(r'/admin/booking/attendees/(\d+)$', key)
|
||||||
|
print "data", req.args, cat, page, bookingtype
|
||||||
|
print "matches", m1, m2
|
||||||
|
if m1:
|
||||||
|
notice = "Bestellung erfolgreich gespeichert."
|
||||||
|
if req.session.has_key("notice"):
|
||||||
|
add_notice(req, req.session["notice"])
|
||||||
|
del req.session["notice"]
|
||||||
|
req.session.save()
|
||||||
|
|
||||||
|
event_id = int(m1.group(1))
|
||||||
|
a_id = m1.group(2)
|
||||||
|
attendee = Attendee.fetch_one(self.env, a_id, e_id=event_id, fetch_options=True)
|
||||||
|
add_ctxtnav(req, 'Back to Overview', req.href.admin("booking", "attendees", event_id))
|
||||||
|
data = {"attendee" : attendee,
|
||||||
|
"bank_name" : self.config.get("booking", "bank_name"),
|
||||||
|
"bank_no" : self.config.get("booking", "bank_no"),
|
||||||
|
"account_owner" : self.config.get("booking", "account_owner"),
|
||||||
|
"account" : self.config.get("booking", "account"),
|
||||||
|
"first_reason" : self.config.get("booking", "first_reason")}
|
||||||
|
print 1
|
||||||
|
if req.method == "POST":
|
||||||
|
print "post", attendee.finished
|
||||||
|
|
||||||
|
if not attendee.finished:
|
||||||
|
print "not finished"
|
||||||
|
failure = False
|
||||||
|
if req.args.has_key("email"):
|
||||||
|
email = req.args["email"]
|
||||||
|
if not validate_email(email):
|
||||||
|
add_warning(req, u"email nicht gültig")
|
||||||
|
attendee.email = email
|
||||||
|
attendee.update()
|
||||||
|
req.session["notice"] = "Daten erfolgreich aktualisiert."
|
||||||
|
elif req.args.has_key("unregister"):
|
||||||
|
UserUploadComponent(self.env).clean_userdir(attendee)
|
||||||
|
Attendee.delete(self.env, attendee.a_id)
|
||||||
|
print "redirect to", req.href.admin("booking", "attendees", event_id)
|
||||||
|
req.redirect(req.href.admin("booking", "attendees", event_id))
|
||||||
|
elif req.args.has_key("finish"):
|
||||||
|
attendee.finished = True
|
||||||
|
attendee.update()
|
||||||
|
print "redirect to", req.href.admin("booking", "attendees", event_id, a_id)
|
||||||
|
req.redirect(req.href.admin("booking", "attendees", event_id, a_id))
|
||||||
|
elif req.args.has_key("unfinish"):
|
||||||
|
attendee.finished = False
|
||||||
|
attendee.update()
|
||||||
|
print "redirect to", req.href.admin("booking", "attendees", event_id, a_id)
|
||||||
|
req.redirect(req.href.admin("booking", "attendees", event_id, a_id))
|
||||||
|
else:
|
||||||
|
args = req.args
|
||||||
|
for arg in args:
|
||||||
|
if arg.startswith("count"):
|
||||||
|
try:
|
||||||
|
prefix, ao_id = arg.split("_", 1)
|
||||||
|
ao_id = int(ao_id)
|
||||||
|
count = int(args[arg])
|
||||||
|
validate_id(count)
|
||||||
|
validate_id(ao_id)
|
||||||
|
except ValueError:
|
||||||
|
add_warning(req, u"Bitte für Anzahlfelder nur positive Zahen eingeben.")
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
aoption = AvailableOption.fetch_one(self.env, ao_id, fetch_variations=False)
|
||||||
|
|
||||||
|
if not aoption:
|
||||||
|
add_warning(req, u"Artikel %r nicht gefunden" % ao_id)
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
elif not aoption.active:
|
||||||
|
add_warning(req, u"Artikel %r nicht aktiviert" % ao_id)
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if count < aoption.min_count:
|
||||||
|
add_warning(req, u"Artikel '%s' kann minimal '%d' Mal bestellt werden;-)" % (aoption.name, aoption.min_count))
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
elif aoption.max_count and count > aoption.max_count:
|
||||||
|
add_warning(req, u"Artikel '%s' kann maximal '%d' Mal bestellt werden;-)" % (aoption.name, aoption.max_count))
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not count:
|
||||||
|
BookingOption.delete(self.env, attendee.a_id, ao_id)
|
||||||
|
else:
|
||||||
|
opt = BookingOption.fetch_one(self.env, attendee.a_id, ao_id)
|
||||||
|
if not opt:
|
||||||
|
opt = BookingOption(self.env, 0, attendee.a_id, ao_id, count)
|
||||||
|
opt.commit()
|
||||||
|
else:
|
||||||
|
opt.count = count
|
||||||
|
opt.update()
|
||||||
|
#elif arg.startswith("var"):
|
||||||
|
#prefix, variation_id = arg.split("_", 1)
|
||||||
|
#try:
|
||||||
|
#variation_id = int(variation_id)
|
||||||
|
#validate_id(variation_id)
|
||||||
|
#value = int(args[arg])
|
||||||
|
#validate_id(value)
|
||||||
|
#except (ValueError,):
|
||||||
|
#add_warning(req, u"Bitte eine Zahl eingeben;-)")
|
||||||
|
#failure = True
|
||||||
|
#continue
|
||||||
|
|
||||||
|
#variation = BookingOptionVariation.fetch_one(self.env, attendee.a_id, variation_id)
|
||||||
|
#if not variation:
|
||||||
|
#b = BookingOptionVariation(self.env, attendee.a_id, variation_id, value)
|
||||||
|
#b.commit()
|
||||||
|
#else:
|
||||||
|
#BookingOptionVariation.update(self.env, attendee.a_id, variation_id, value)
|
||||||
|
|
||||||
|
if not failure:
|
||||||
|
req.session["notice"] = notice
|
||||||
|
print "before redirect", req.href.admin("booking", "attendees", event_id, a_id)
|
||||||
|
req.redirect(req.href.admin("booking", "attendees", event_id, a_id))
|
||||||
|
elif req.args.has_key("download_invoice"):
|
||||||
|
e_id = req.args["event_id"]
|
||||||
|
event = Event.fetch_one(self.env, e_id)
|
||||||
|
session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
|
||||||
|
|
||||||
|
data = create_attendee_report(self.env, event, attendee, selected_tz)
|
||||||
|
data_len = len(data)
|
||||||
|
|
||||||
|
req.send_response(200)
|
||||||
|
req.send_header("Content-Type", "text/pdf;charset=utf-8")
|
||||||
|
req.send_header("Content-Length", data_len)
|
||||||
|
req.send_header("Content-Disposition", 'filename=%s.pdf' % event.name.replace("/", "_").replace(u" ", u"_"))
|
||||||
|
req.end_headers()
|
||||||
|
req.write(data)
|
||||||
|
raise RequestDone
|
||||||
|
elif req.args.has_key("unfinish"):
|
||||||
|
attendee.finished = False
|
||||||
|
attendee.update()
|
||||||
|
print "redirect to", req.href.admin("booking", "attendees", event_id, a_id)
|
||||||
|
req.redirect(req.href.admin("booking", "attendees", event_id, a_id))
|
||||||
|
else:
|
||||||
|
attendee = Attendee.fetch_one(self.env, a_id, e_id=event_id, fetch_options=True)
|
||||||
|
event = Event.fetch_one(self.env, e_id=event_id, fetch_options=True, attendee_id=attendee.a_id)
|
||||||
|
for i in event.options:
|
||||||
|
get_option_count(attendee, i)
|
||||||
|
data.update({"event" : event, "attendee" : attendee})
|
||||||
|
return 'admin_attendee_status_edit.html', data
|
||||||
|
if not m2:
|
||||||
|
#if key == "/admin/booking/attendees":
|
||||||
|
return "admin_attendees.html", {"events" : Event.fetch_all(self.env), "attendees" : None, "event" : None}
|
||||||
|
|
||||||
|
else:
|
||||||
|
add_ctxtnav(req, 'Back to Overview', req.href.admin("booking", "attendees"))
|
||||||
|
|
||||||
|
e_id = int(m2.group(1))
|
||||||
|
event = Event.fetch_one(self.env, e_id)
|
||||||
|
if req.method == "POST":
|
||||||
|
if req.args.has_key("remove_empty"):
|
||||||
|
attendees = Attendee.fetch_all(self.env, e_id=e_id, fetch_options=True)
|
||||||
|
|
||||||
|
for attendee in attendees:
|
||||||
|
if not attendee.options:
|
||||||
|
attendee.delete(self.env, attendee.a_id)
|
||||||
|
req.redirect(req.href.admin("booking", "attendees", e_id))
|
||||||
|
elif req.args.has_key("download_report"):
|
||||||
|
session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
|
||||||
|
data = create_report(self.env, e_id, selected_tz)
|
||||||
|
data_len = len(data)
|
||||||
|
req.send_response(200)
|
||||||
|
req.send_header("Content-Type", "text/pdf;charset=utf-8")
|
||||||
|
req.send_header("Content-Length", data_len)
|
||||||
|
req.send_header("Content-Disposition", 'filename=%s.pdf' % event.name.replace("/", "_").replace(" ", "_"))
|
||||||
|
req.end_headers()
|
||||||
|
req.write(data)
|
||||||
|
raise RequestDone
|
||||||
|
if req.args.has_key("sel"):
|
||||||
|
sel = req.args.get('sel')
|
||||||
|
if isinstance(sel, basestring):
|
||||||
|
sel = [sel,]
|
||||||
|
for key in sel:
|
||||||
|
try:
|
||||||
|
a_id = int(key)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
UserUploadComponent(self.env).clean_userdir(Attendee.fetch_one(self.env, a_id))
|
||||||
|
Attendee.delete(self.env, a_id)
|
||||||
|
attendees = Attendee.fetch_all(self.env, e_id, True)
|
||||||
|
a_ids = set()
|
||||||
|
if req.args.has_key("has_paid"):
|
||||||
|
sel = req.args.get('has_paid')
|
||||||
|
if isinstance(sel, basestring):
|
||||||
|
sel = [sel,]
|
||||||
|
for key in sel:
|
||||||
|
try:
|
||||||
|
a_ids.add(int(key))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
for attendee in attendees:
|
||||||
|
if attendee.has_paid:
|
||||||
|
if not attendee.a_id in a_ids:
|
||||||
|
attendee.has_paid = 0
|
||||||
|
attendee.update()
|
||||||
|
elif attendee.a_id in a_ids:
|
||||||
|
attendee.has_paid = 1
|
||||||
|
attendee.update()
|
||||||
|
actual_amount = req.args.get('actual_amount_%d' % attendee.a_id)
|
||||||
|
if actual_amount:
|
||||||
|
try:
|
||||||
|
attendee.actual_amount = float(actual_amount)
|
||||||
|
attendee.update()
|
||||||
|
except TypeError:
|
||||||
|
add_warning(req, u"Das Attribut 'Eingezahlter Betrag' muss ein float Wert sein")
|
||||||
|
else:
|
||||||
|
attendee.actual_amount = None
|
||||||
|
attendee.update()
|
||||||
|
|
||||||
|
return "admin_attendees.html", {"attendees" : Attendee.fetch_all(self.env, e_id, True), "event" : event, "events" : Event.fetch_all(self.env)}
|
||||||
|
|
||||||
|
def _render_options2events(self, req, cat, page, bookingtype):
|
||||||
|
print "ComponentOptions2EventsManager._render_admin_panel()"
|
||||||
|
add_stylesheet (req, 'hw/css/booking.css')
|
||||||
|
data = {}
|
||||||
|
if req.method == "POST":
|
||||||
|
if req.args.get("add"):
|
||||||
|
option = req.args.get("option")
|
||||||
|
event = req.args.get("event")
|
||||||
|
option = AvailableOption.fetch_one(self.env, name=option)
|
||||||
|
event = Event.fetch_one(self.env, name=event)
|
||||||
|
if not option or not event:
|
||||||
|
raise TracError(_(u'Nicht genügend Informationen'))
|
||||||
|
o2e = Option2Event.fetch_one(self.env, option.ao_id, event.e_id)
|
||||||
|
if o2e:
|
||||||
|
add_warning(req, _(u'Option bereits zum Event hinzugefügt'))
|
||||||
|
data.update({"options" : AvailableOption.fetch_all(self.env),
|
||||||
|
"events" : Event.fetch_all(self.env, fetch_options=True)})
|
||||||
|
return "admin_options_to_events.html", data
|
||||||
|
o2e = Option2Event(self.env, option.ao_id, event.e_id)
|
||||||
|
o2e.commit()
|
||||||
|
elif req.args.has_key("save") and req.args.has_key("sel"):
|
||||||
|
sel = req.args.get('sel')
|
||||||
|
if isinstance(sel, basestring):
|
||||||
|
sel = [sel,]
|
||||||
|
for key in sel:
|
||||||
|
ao_id, e_id = key.split(":", 1)
|
||||||
|
Option2Event.delete(self.env, int(ao_id), int(e_id))
|
||||||
|
data.update({"options" : AvailableOption.fetch_all(self.env),
|
||||||
|
"events" : Event.fetch_all(self.env, fetch_options=True)})
|
||||||
|
return "admin_options_to_events.html", data
|
||||||
|
|
||||||
|
|
||||||
|
def _render_einkauf(self, req, cat, page, bookingtype):
|
||||||
|
|
||||||
|
print "ComonentEinkauf._render_admin_panel()"
|
||||||
|
add_stylesheet (req, 'hw/css/booking.css')
|
||||||
|
key = req.path_info
|
||||||
|
m = match(r'/admin/booking/einkauf/(\d+)$', key)
|
||||||
|
if not m:
|
||||||
|
if key == "/admin/booking/einkauf":
|
||||||
|
return "admin_einkauf.html", {"events" : Event.fetch_all(self.env), "items" : None}
|
||||||
|
e_id = int(m.group(1))
|
||||||
|
e = Event.fetch_one(self.env, e_id)
|
||||||
|
if req.method == "POST":
|
||||||
|
if req.args.has_key("remove_empty"):
|
||||||
|
attendees = Attendee.fetch_all(self.env, e_id=e_id, fetch_options=True)
|
||||||
|
attendees = sorted(attendees, key=lambda x: x.nick)
|
||||||
|
|
||||||
|
for attendee in attendees:
|
||||||
|
if not attendee.options:
|
||||||
|
attendee.delete(self.env, attendee.a_id)
|
||||||
|
req.redirect(req.href.admin("booking", "einkauf", e_id))
|
||||||
|
elif req.args.has_key("download_report"):
|
||||||
|
session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
|
||||||
|
data = create_report(self.env, e_id, selected_tz)
|
||||||
|
data_len = len(data)
|
||||||
|
req.send_response(200)
|
||||||
|
req.send_header('Content-Type', "text/pdf;charset=utf-8")
|
||||||
|
req.send_header('Content-Length', data_len)
|
||||||
|
req.send_header('Content-Disposition', 'filename=%s_bestellung.pdf' % e.name.replace("/", "_").replace(" ", "_"))
|
||||||
|
req.end_headers()
|
||||||
|
req.write(data)
|
||||||
|
raise RequestDone
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
query_old = "SELECT booking_option.ao_id,booking_available_option.name,booking_available_option.price," \
|
||||||
|
"SUM(booking_option.count),booking_available_option.price * SUM(booking_option.count)" \
|
||||||
|
"FROM booking_available_option,booking_option,option_to_event " \
|
||||||
|
"WHERE booking_available_option.ao_id = booking_option.ao_id AND " \
|
||||||
|
"booking_available_option.ao_id = option_to_event.ao_id AND " \
|
||||||
|
"option_to_event.e_id=%s GROUP BY booking_option.ao_id;"
|
||||||
|
|
||||||
|
query = "select " \
|
||||||
|
"booking_option.ao_id, " \
|
||||||
|
"booking_available_option.name, " \
|
||||||
|
"booking_available_option.price, " \
|
||||||
|
"SUM(booking_option.count), " \
|
||||||
|
"SUM(booking_option.count) * booking_available_option.price " \
|
||||||
|
"from " \
|
||||||
|
"booking_option,booking_available_option " \
|
||||||
|
"where " \
|
||||||
|
"booking_option.a_id IN (select a_id from attendee where e_id=%s) AND " \
|
||||||
|
"booking_option.ao_id = booking_available_option.ao_id " \
|
||||||
|
"group by " \
|
||||||
|
"booking_option.ao_id;"
|
||||||
|
|
||||||
|
db = self.env.get_db_cnx()
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute(query, (e_id,))
|
||||||
|
items = cursor.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
return "admin_einkauf.html", {"events" : Event.fetch_all(self.env), "items" : items, "eventname": e.name}
|
|
@ -0,0 +1,2 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
.booking-block, fieldset, fieldset.register {text-align:center;display:inline-block;border:2px solid #333;-moz-border-radius:17px;-khtml-border-radius:17px;-webkit-border-radius:17px;margin:10px;}
|
||||||
|
.admin-block {border:2px solid #333;-moz-border-radius:17px;-khtml-border-radius:17px;-webkit-border-radius:17px;margin:10px;padding:10px;}
|
||||||
|
#booking-bank, #booking-options, #event_details, #booking-attendee {padding:10px;display:block;text-align:left;}
|
||||||
|
#booking-options table {border:1px solid #000;margin-bottom:10px;margin:0px!;padding:0px!;}
|
||||||
|
#booking-options td, #booking-options th {border:1px solid #000;margin:0px!;padding:0px!;}
|
||||||
|
#booking-options th {font-weight:bold;padding:0px!;margin:0px!;}
|
||||||
|
#booking-options td {text-align:left;padding:5px;}
|
||||||
|
#payment-deadline-data, #edit-deadline-data {font-weight:bold;color:red;}
|
|
@ -0,0 +1,609 @@
|
||||||
|
/*
|
||||||
|
* jQuery UI screen structure and presentation
|
||||||
|
* This CSS file was generated by ThemeRoller, a Filament Group Project for jQuery UI
|
||||||
|
* Author: Scott Jehl, scott@filamentgroup.com, http://www.filamentgroup.com
|
||||||
|
* Visit ThemeRoller.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: If your ThemeRoller settings have a font size set in ems, your components will scale according to their parent element's font size.
|
||||||
|
* As a rule of thumb, set your body's font size to 62.5% to make 1em = 10px.
|
||||||
|
* body {font-size: 62.5%;}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*UI accordion*/
|
||||||
|
.ui-accordion {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.ui-accordion-group {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.ui-accordion-header {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #f6f6f6;
|
||||||
|
}
|
||||||
|
.ui-accordion-header a {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
display: block;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: .5em .5em .5em 1.7em;
|
||||||
|
color: #1c94c4;
|
||||||
|
background: url(images/ef8c08_7x7_arrow_right.gif) .5em 50% no-repeat;
|
||||||
|
}
|
||||||
|
.ui-accordion-header a:hover {
|
||||||
|
background: url(images/ef8c08_7x7_arrow_right.gif) .5em 50% no-repeat;
|
||||||
|
color: #c77405;
|
||||||
|
}
|
||||||
|
.ui-accordion-header:hover {
|
||||||
|
background: #fdf5ce;
|
||||||
|
color: #c77405;
|
||||||
|
}
|
||||||
|
.selected .ui-accordion-header {background: #004;}
|
||||||
|
|
||||||
|
.selected .ui-accordion-header a, .selected .ui-accordion-header a:hover {
|
||||||
|
color: #eb8f00;
|
||||||
|
background: url(images/ef8c08_7x7_arrow_down.gif) .5em 50% no-repeat;
|
||||||
|
}
|
||||||
|
.ui-accordion-content {
|
||||||
|
/* background: #eeeeee url(images/eeeeee_40x100_textures_03_highlight_soft_100.png) 0 0 repeat-x; */
|
||||||
|
background: #eeeeee;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
.ui-accordion-content p {
|
||||||
|
padding: 1em 1.7em 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*UI tabs*/
|
||||||
|
.ui-tabs-nav {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
font-size: 1.1em;
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
border-right: 1px solid #cccccc;
|
||||||
|
bottom: -1px;
|
||||||
|
}
|
||||||
|
.ui-tabs-nav ul {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
}
|
||||||
|
.ui-tabs-nav li {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
float: left;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.ui-tabs-nav li a {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
float: left;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: .5em 1.7em;
|
||||||
|
color: #1c94c4;
|
||||||
|
background: #f6f6f6 url(images/f6f6f6_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
}
|
||||||
|
.ui-tabs-nav li a:hover {
|
||||||
|
background: #fdf5ce url(images/fdf5ce_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
color: #c77405;
|
||||||
|
}
|
||||||
|
.ui-tabs-nav li.ui-tabs-selected {
|
||||||
|
border-bottom-color: #ffffff;
|
||||||
|
}
|
||||||
|
.ui-tabs-nav li.ui-tabs-selected a, .ui-tabs-nav li.ui-tabs-selected a:hover {
|
||||||
|
background: #ffffff url(images/ffffff_40x100_textures_02_glass_65.png) 0 50% repeat-x;
|
||||||
|
color: #eb8f00;
|
||||||
|
}
|
||||||
|
.ui-tabs-panel {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
clear:left;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
background: #eeeeee url(images/eeeeee_40x100_textures_03_highlight_soft_100.png) 0 0 repeat-x;
|
||||||
|
color: #333333;
|
||||||
|
padding: 1.5em 1.7em;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.ui-tabs-hide {
|
||||||
|
display: none;/* for accessible hiding: position: absolute; left: -99999999px*/;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*slider*/
|
||||||
|
.ui-slider {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
font-size: 1.1em;
|
||||||
|
background: #eeeeee url(images/eeeeee_40x100_textures_03_highlight_soft_100.png) 0 0 repeat-x;
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
height: .8em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ui-slider-handle {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: -3px;
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
background: #f6f6f6 url(images/f6f6f6_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
}
|
||||||
|
.ui-slider-handle:hover {
|
||||||
|
background: #fdf5ce url(images/fdf5ce_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
border: 1px solid #fbcb09;
|
||||||
|
}
|
||||||
|
.ui-slider-handle-active, .ui-slider-handle-active:hover {
|
||||||
|
background: #ffffff url(images/ffffff_40x100_textures_02_glass_65.png) 0 50% repeat-x;
|
||||||
|
border: 1px solid #fbd850;
|
||||||
|
}
|
||||||
|
.ui-slider-range {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
height: .8em;
|
||||||
|
background: #fdf5ce url(images/fdf5ce_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
|
top: -1px;
|
||||||
|
z-index: 1;
|
||||||
|
opacity:.7;
|
||||||
|
filter:Alpha(Opacity=70);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*dialog*/
|
||||||
|
.ui-dialog {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
font-size: 1.1em;
|
||||||
|
background: #eeeeee url(images/eeeeee_40x100_textures_03_highlight_soft_100.png) 0 0 repeat-x;
|
||||||
|
color: #333333;
|
||||||
|
border: 4px solid #dddddd;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ui-resizable-handle {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 0.1px;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
.ui-resizable .ui-resizable-handle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
body .ui-resizable-disabled .ui-resizable-handle { display: none; } /* use 'body' to make it more specific (css order) */
|
||||||
|
body .ui-resizable-autohide .ui-resizable-handle { display: none; } /* use 'body' to make it more specific (css order) */
|
||||||
|
.ui-resizable-n {
|
||||||
|
cursor: n-resize;
|
||||||
|
height: 7px;
|
||||||
|
width: 100%;
|
||||||
|
top: -5px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
.ui-resizable-s {
|
||||||
|
cursor: s-resize;
|
||||||
|
height: 7px;
|
||||||
|
width: 100%;
|
||||||
|
bottom: -5px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
.ui-resizable-e {
|
||||||
|
cursor: e-resize;
|
||||||
|
width: 7px;
|
||||||
|
right: -5px;
|
||||||
|
top: 0px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.ui-resizable-w {
|
||||||
|
cursor: w-resize;
|
||||||
|
width: 7px;
|
||||||
|
left: -5px;
|
||||||
|
top: 0px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.ui-resizable-se {
|
||||||
|
cursor: se-resize;
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
background: url(images/222222_11x11_icon_resize_se.gif) no-repeat 0 0;
|
||||||
|
}
|
||||||
|
.ui-resizable-sw {
|
||||||
|
cursor: sw-resize;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
left: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
.ui-resizable-nw {
|
||||||
|
cursor: nw-resize;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
.ui-resizable-ne {
|
||||||
|
cursor: ne-resize;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
right: 0px;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
.ui-dialog-titlebar {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
padding: .5em 1.5em .5em 1em;
|
||||||
|
color: #1c94c4;
|
||||||
|
background: #f6f6f6 url(images/f6f6f6_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
border-bottom: 1px solid #cccccc;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ui-dialog-title {}
|
||||||
|
.ui-dialog-titlebar-close {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
background: url(images/ef8c08_11x11_icon_close.gif) 0 0 no-repeat;
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: .7em;
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.ui-dialog-titlebar-close-hover, .ui-dialog-titlebar-close:hover {
|
||||||
|
background: url(images/ef8c08_11x11_icon_close.gif) 0 0 no-repeat;
|
||||||
|
}
|
||||||
|
.ui-dialog-titlebar-close:active {
|
||||||
|
background: url(images/ef8c08_11x11_icon_close.gif) 0 0 no-repeat;
|
||||||
|
}
|
||||||
|
.ui-dialog-titlebar-close span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ui-dialog-content {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
color: #333333;
|
||||||
|
padding: 1.5em 1.7em;
|
||||||
|
}
|
||||||
|
.ui-dialog-buttonpane {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-top: 1px solid #dddddd;
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
.ui-dialog-buttonpane button {
|
||||||
|
margin: .5em 0 .5em 8px;
|
||||||
|
color: #1c94c4;
|
||||||
|
background: #f6f6f6 url(images/f6f6f6_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
font-size: 1em;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: .2em .6em .3em .6em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
.ui-dialog-buttonpane button:hover {
|
||||||
|
color: #c77405;
|
||||||
|
background: #fdf5ce url(images/fdf5ce_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
border: 1px solid #fbcb09;
|
||||||
|
}
|
||||||
|
.ui-dialog-buttonpane button:active {
|
||||||
|
color: #eb8f00;
|
||||||
|
background: #ffffff url(images/ffffff_40x100_textures_02_glass_65.png) 0 50% repeat-x;
|
||||||
|
border: 1px solid #fbd850;
|
||||||
|
}
|
||||||
|
/* This file skins dialog */
|
||||||
|
.ui-dialog.ui-draggable .ui-dialog-titlebar,
|
||||||
|
.ui-dialog.ui-draggable .ui-dialog-titlebar {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*datepicker*/
|
||||||
|
/* Main Style Sheet for jQuery UI date picker */
|
||||||
|
.ui-datepicker-div, .ui-datepicker-inline, #ui-datepicker-div {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
background: #eeeeee url(images/eeeeee_40x100_textures_03_highlight_soft_100.png) 0 0 repeat-x;
|
||||||
|
font-size: 1.1em;
|
||||||
|
border: 4px solid #dddddd;
|
||||||
|
width: 15.5em;
|
||||||
|
padding: 2.5em .5em .5em .5em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ui-datepicker-div, #ui-datepicker-div {
|
||||||
|
z-index: 9999; /*must have*/
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ui-datepicker-inline {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.ui-datepicker-control {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ui-datepicker-current {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ui-datepicker-next, .ui-datepicker-prev {
|
||||||
|
position: absolute;
|
||||||
|
left: .5em;
|
||||||
|
top: .5em;
|
||||||
|
background: #f6f6f6 url(images/f6f6f6_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
}
|
||||||
|
.ui-datepicker-next {
|
||||||
|
left: 14.6em;
|
||||||
|
}
|
||||||
|
.ui-datepicker-next:hover, .ui-datepicker-prev:hover {
|
||||||
|
background: #fdf5ce url(images/fdf5ce_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
}
|
||||||
|
.ui-datepicker-next a, .ui-datepicker-prev a {
|
||||||
|
text-indent: -999999px;
|
||||||
|
width: 1.3em;
|
||||||
|
height: 1.4em;
|
||||||
|
display: block;
|
||||||
|
font-size: 1em;
|
||||||
|
background: url(images/ef8c08_7x7_arrow_left.gif) 50% 50% no-repeat;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ui-datepicker-next a {
|
||||||
|
background: url(images/ef8c08_7x7_arrow_right.gif) 50% 50% no-repeat;
|
||||||
|
}
|
||||||
|
.ui-datepicker-prev a:hover {
|
||||||
|
background: url(images/ef8c08_7x7_arrow_left.gif) 50% 50% no-repeat;
|
||||||
|
}
|
||||||
|
.ui-datepicker-next a:hover {
|
||||||
|
background: url(images/ef8c08_7x7_arrow_right.gif) 50% 50% no-repeat;
|
||||||
|
}
|
||||||
|
.ui-datepicker-prev a:active {
|
||||||
|
background: url(images/ef8c08_7x7_arrow_left.gif) 50% 50% no-repeat;
|
||||||
|
}
|
||||||
|
.ui-datepicker-next a:active {
|
||||||
|
background: url(images/ef8c08_7x7_arrow_right.gif) 50% 50% no-repeat;
|
||||||
|
}
|
||||||
|
.ui-datepicker-header select {
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
color: #1c94c4;
|
||||||
|
background: #f6f6f6;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
position: absolute;
|
||||||
|
top: .5em;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
.ui-datepicker-header option:focus, .ui-datepicker-header option:hover {
|
||||||
|
background: #fdf5ce;
|
||||||
|
}
|
||||||
|
.ui-datepicker-header select.ui-datepicker-new-month {
|
||||||
|
width: 7em;
|
||||||
|
left: 2.2em;
|
||||||
|
}
|
||||||
|
.ui-datepicker-header select.ui-datepicker-new-year {
|
||||||
|
width: 5em;
|
||||||
|
left: 9.4em;
|
||||||
|
}
|
||||||
|
table.ui-datepicker {
|
||||||
|
width: 15.5em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
table.ui-datepicker td a {
|
||||||
|
padding: .1em .3em .1em 0;
|
||||||
|
display: block;
|
||||||
|
color: #1c94c4;
|
||||||
|
background: #f6f6f6 url(images/f6f6f6_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #eeeeee;
|
||||||
|
}
|
||||||
|
table.ui-datepicker td a:hover {
|
||||||
|
border: 1px solid #fbcb09;
|
||||||
|
color: #c77405;
|
||||||
|
background: #fdf5ce url(images/fdf5ce_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
}
|
||||||
|
table.ui-datepicker td a:active {
|
||||||
|
border: 1px solid #fbd850;
|
||||||
|
color: #eb8f00;
|
||||||
|
background: #ffffff url(images/ffffff_40x100_textures_02_glass_65.png) 0 50% repeat-x;
|
||||||
|
}
|
||||||
|
table.ui-datepicker .ui-datepicker-title-row td {
|
||||||
|
padding: .3em 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: .9em;
|
||||||
|
color: #333333;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
table.ui-datepicker .ui-datepicker-title-row td a {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.ui-datepicker-cover {
|
||||||
|
display: none;
|
||||||
|
display/**/: block;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
filter: mask();
|
||||||
|
top: -4px;
|
||||||
|
left: -4px;
|
||||||
|
width: 193px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generic ThemeRoller Classes
|
||||||
|
>> Make your jQuery Components ThemeRoller-Compatible!
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*component global class*/
|
||||||
|
.ui-component {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
/*component content styles*/
|
||||||
|
.ui-component-content {
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
background: #eeeeee url(images/eeeeee_40x100_textures_03_highlight_soft_100.png) 0 0 repeat-x;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.ui-component-content a {
|
||||||
|
color: #333333;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
/*component states*/
|
||||||
|
.ui-default-state {
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
background: #f6f6f6 url(images/f6f6f6_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1c94c4 !important;
|
||||||
|
}
|
||||||
|
.ui-default-state a {
|
||||||
|
color: #1c94c4;
|
||||||
|
}
|
||||||
|
.ui-default-state:hover, .ui-hover-state {
|
||||||
|
border: 1px solid #fbcb09;
|
||||||
|
background: #fdf5ce url(images/fdf5ce_40x100_textures_02_glass_100.png) 0 50% repeat-x;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #c77405 !important;
|
||||||
|
}
|
||||||
|
.ui-hover-state a {
|
||||||
|
color: #c77405;
|
||||||
|
}
|
||||||
|
.ui-default-state:active, .ui-active-state {
|
||||||
|
border: 1px solid #fbd850;
|
||||||
|
background: #ffffff url(images/ffffff_40x100_textures_02_glass_65.png) 0 50% repeat-x;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #eb8f00 !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.ui-active-state a {
|
||||||
|
color: #eb8f00;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
/*icons*/
|
||||||
|
.ui-arrow-right-default {background: url(images/ef8c08_7x7_arrow_right.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-right-default:hover, .ui-arrow-right-hover {background: url(images/ef8c08_7x7_arrow_right.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-right-default:active, .ui-arrow-right-active {background: url(images/ef8c08_7x7_arrow_right.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-right-content {background: url(images/222222_7x7_arrow_right.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-arrow-left-default {background: url(images/ef8c08_7x7_arrow_left.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-left-default:hover, .ui-arrow-left-hover {background: url(images/ef8c08_7x7_arrow_left.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-left-default:active, .ui-arrow-left-active {background: url(images/ef8c08_7x7_arrow_left.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-left-content {background: url(images/222222_7x7_arrow_left.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-arrow-down-default {background: url(images/ef8c08_7x7_arrow_down.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-down-default:hover, .ui-arrow-down-hover {background: url(images/ef8c08_7x7_arrow_down.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-down-default:active, .ui-arrow-down-active {background: url(images/ef8c08_7x7_arrow_down.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-down-content {background: url(images/222222_7x7_arrow_down.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-arrow-up-default {background: url(images/ef8c08_7x7_arrow_up.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-up-default:hover, .ui-arrow-up-hover {background: url(images/ef8c08_7x7_arrow_up.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-up-default:active, .ui-arrow-up-active {background: url(images/ef8c08_7x7_arrow_up.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrow-up-content {background: url(images/222222_7x7_arrow_up.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-close-default {background: url(images/ef8c08_11x11_icon_close.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-close-default:hover, .ui-close-hover {background: url(images/ef8c08_11x11_icon_close.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-close-default:active, .ui-close-active {background: url(images/ef8c08_11x11_icon_close.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-close-content {background: url(images/ef8c08_11x11_icon_close.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-folder-closed-default {background: url(images/ef8c08_11x11_icon_folder_closed.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-folder-closed-default:hover, .ui-folder-closed-hover {background: url(images/ef8c08_11x11_icon_folder_closed.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-folder-closed-default:active, .ui-folder-closed-active {background: url(images/ef8c08_11x11_icon_folder_closed.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-folder-closed-content {background: url(images/ef8c08_11x11_icon_folder_closed.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-folder-open-default {background: url(images/ef8c08_11x11_icon_folder_open.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-folder-open-default:hover, .ui-folder-open-hover {background: url(images/ef8c08_11x11_icon_folder_open.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-folder-open-default:active, .ui-folder-open-active {background: url(images/ef8c08_11x11_icon_folder_open.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-folder-open-content {background: url(images/ef8c08_11x11_icon_folder_open.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-doc-default {background: url(images/ef8c08_11x11_icon_doc.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-doc-default:hover, .ui-doc-hover {background: url(images/ef8c08_11x11_icon_doc.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-doc-default:active, .ui-doc-active {background: url(images/ef8c08_11x11_icon_doc.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-doc-content {background: url(images/222222_11x11_icon_doc.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-arrows-leftright-default {background: url(images/ef8c08_11x11_icon_arrows_leftright.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrows-leftright-default:hover, .ui-arrows-leftright-hover {background: url(images/ef8c08_11x11_icon_arrows_leftright.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrows-leftright-default:active, .ui-arrows-leftright-active {background: url(images/ef8c08_11x11_icon_arrows_leftright.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrows-leftright-content {background: url(images/222222_11x11_icon_arrows_leftright.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-arrows-updown-default {background: url(images/ef8c08_11x11_icon_arrows_updown.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrows-updown-default:hover, .ui-arrows-updown-hover {background: url(images/ef8c08_11x11_icon_arrows_updown.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrows-updown-default:active, .ui-arrows-updown-active {background: url(images/ef8c08_11x11_icon_arrows_updown.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-arrows-updown-content {background: url(images/222222_11x11_icon_arrows_updown.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-minus-default {background: url(images/ef8c08_11x11_icon_minus.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-minus-default:hover, .ui-minus-hover {background: url(images/ef8c08_11x11_icon_minus.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-minus-default:active, .ui-minus-active {background: url(images/ef8c08_11x11_icon_minus.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-minus-content {background: url(images/222222_11x11_icon_minus.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
.ui-plus-default {background: url(images/ef8c08_11x11_icon_plus.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-plus-default:hover, .ui-plus-hover {background: url(images/ef8c08_11x11_icon_plus.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-plus-default:active, .ui-plus-active {background: url(images/ef8c08_11x11_icon_plus.gif) no-repeat 50% 50%;}
|
||||||
|
.ui-plus-content {background: url(images/222222_11x11_icon_plus.gif) no-repeat 50% 50%;}
|
||||||
|
|
||||||
|
/*hidden elements*/
|
||||||
|
.ui-hidden {
|
||||||
|
display: none;/* for accessible hiding: position: absolute; left: -99999999px*/;
|
||||||
|
}
|
||||||
|
.ui-accessible-hidden {
|
||||||
|
position: absolute; left: -99999999px;
|
||||||
|
}
|
||||||
|
/*reset styles*/
|
||||||
|
.ui-reset {
|
||||||
|
/*resets*/margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none;
|
||||||
|
}
|
||||||
|
/*clearfix class*/
|
||||||
|
.ui-clearfix:after {
|
||||||
|
content: ".";
|
||||||
|
display: block;
|
||||||
|
height: 0;
|
||||||
|
clear: both;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.ui-clearfix {display: inline-block;}
|
||||||
|
/* Hides from IE-mac \*/
|
||||||
|
* html .ui-clearfix {height: 1%;}
|
||||||
|
.ui-clearfix {display: block;}
|
||||||
|
/* End hide from IE-mac */
|
||||||
|
|
||||||
|
/* Note: for resizable styles, use the styles listed above in the dialog section */
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script language="JavaScript">
|
||||||
|
function countdown(year, month, day, hour, minute, second)
|
||||||
|
{
|
||||||
|
var end_date = new Date(year, month-1, day, hour, minute, second);
|
||||||
|
start_date = new Date();
|
||||||
|
|
||||||
|
if (end_date < end_date)
|
||||||
|
{
|
||||||
|
var years = 0, months = 0, days = 0, hours = 0, minutes = 0, seconds = 0;
|
||||||
|
|
||||||
|
// Jahre
|
||||||
|
while(end_date<end_date)
|
||||||
|
{
|
||||||
|
years++;
|
||||||
|
end_date.setFullYear(end_date.getFullYear()+1);
|
||||||
|
}
|
||||||
|
end_date.setFullYear(end_date.getFullYear()-1);
|
||||||
|
years--;
|
||||||
|
|
||||||
|
// Monate
|
||||||
|
while(end_date<end_date)
|
||||||
|
{
|
||||||
|
months++;
|
||||||
|
end_date.setMonth(end_date.getMonth()+1);
|
||||||
|
}
|
||||||
|
end_date.setMonth(end_date.getMonth()-1);
|
||||||
|
months--;
|
||||||
|
|
||||||
|
// Tage
|
||||||
|
while(end_date.getTime()+(24*60*60*1000)<end_date)
|
||||||
|
{
|
||||||
|
days++;
|
||||||
|
end_date.setTime(end_date.getTime()+(24*60*60*1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stunden
|
||||||
|
hours=Math.floor((end_date-end_date)/(60*60*1000));
|
||||||
|
end_date.setTime(end_date.getTime()+hours*60*60*1000);
|
||||||
|
|
||||||
|
// Minuten
|
||||||
|
minutes=Math.floor((end_date-end_date)/(60*1000));
|
||||||
|
end_date.setTime(end_date.getTime()+minutes*60*1000);
|
||||||
|
|
||||||
|
// Sekunden
|
||||||
|
seconds=Math.floor((end_date-end_date)/1000);
|
||||||
|
|
||||||
|
// Anzeige formatieren
|
||||||
|
(years!=1)?years=years+" Jahre, ":years=years+" Jahr, ";
|
||||||
|
(months!=1)?months=months+" Monate, ":months=months+" Monat, ";
|
||||||
|
(days!=1)?days=days+" Tage, ":days=days+" Tag, ";
|
||||||
|
(hours!=1)?hours=hours+" Stunden, ":hours=hours+" Stunde, ";
|
||||||
|
(minutes!=1)?minutes=minutes+" Minuten und ":minutes=minutes+" Minute und ";
|
||||||
|
if(seconds<10) seconds="0"+seconds;
|
||||||
|
(seconds!=1)?seconds=seconds+" Sekunden":seconds=seconds+" Sekunde";
|
||||||
|
|
||||||
|
document.countdownform.countdowninput.value= years+months+days+hours+minutes+seconds;
|
||||||
|
setTimeout('countdown()',200);
|
||||||
|
}
|
||||||
|
// Anderenfalls alles auf Null setzen
|
||||||
|
else
|
||||||
|
document.countdownform.countdowninput.value= "0 Jahre, 0 Monate, 0 Tage, 0 Stunden, 0 Minuten und 00 Sekunden";
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script type='text/javascript'>
|
||||||
|
<!--
|
||||||
|
var now = new Date();
|
||||||
|
var year = now.getYear();
|
||||||
|
if (year < 1900) {
|
||||||
|
year += 1900;
|
||||||
|
}
|
||||||
|
var end = new Date("July 17, " + year + " 23:00:00 GMT");
|
||||||
|
|
||||||
|
document.write('<div style="text-align: center">');
|
||||||
|
document.write(' <span id="c1" style="FONT: bold 30px arial; COLOR: green"></span><br />');
|
||||||
|
document.write(' <span id="c2" style="font: bold 25px arial; color: blue">;</span><br />');
|
||||||
|
document.write(' ...bis zum 31. Dezember ' + year);
|
||||||
|
document.write('</div>');
|
||||||
|
|
||||||
|
function toSt2(n) {
|
||||||
|
s = "";
|
||||||
|
if (n < 10) s += "0";
|
||||||
|
return (s + n).toString();
|
||||||
|
}
|
||||||
|
function toSt3(n) {
|
||||||
|
s = "";
|
||||||
|
if (n < 10) s += "00";
|
||||||
|
else if (n < 100) s += "0";
|
||||||
|
return (s + n).toString();
|
||||||
|
}
|
||||||
|
function countdown() {
|
||||||
|
d = new Date();
|
||||||
|
count = Math.floor(end.getTime() - d.getTime());
|
||||||
|
if(count > 0) {
|
||||||
|
miliseconds = toSt3(count%1000); count = Math.floor(count/1000);
|
||||||
|
seconds = toSt2(count%60); count = Math.floor(count/60);
|
||||||
|
minutes = toSt2(count%60); count = Math.floor(count/60);
|
||||||
|
hours = toSt2(count%24); count = Math.floor(count/24);
|
||||||
|
days = count;
|
||||||
|
document.getElementById('c1').innerHTML = days + ' TAGE';
|
||||||
|
document.getElementById('c2').innerHTML = hours + ':' + minutes + ':' + seconds + '.' + miliseconds + '';
|
||||||
|
setTimeout("countdown()", 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//-->
|
||||||
|
</script>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,68 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from genshi.builder import tag
|
||||||
|
from genshi import Markup
|
||||||
|
from trac.wiki.macros import WikiMacroBase
|
||||||
|
from trac.util.translation import _
|
||||||
|
from trac.web.chrome import add_script
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pkg_resources import resource_filename
|
||||||
|
|
||||||
|
class CountDownMacro(WikiMacroBase):
|
||||||
|
|
||||||
|
"""Renders an overview of active RendezVouses"""
|
||||||
|
|
||||||
|
revision = "$Rev$"
|
||||||
|
url = "$URL$"
|
||||||
|
def expand_macro(self, formatter, name, content):
|
||||||
|
try:
|
||||||
|
event_datetime = datetime(*tuple(time.strptime("2010, 07, 16", "%Y, %m, %d"))[:3])
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
dt = event_datetime - datetime.now()
|
||||||
|
hours = "%d:%d" % (dt.seconds / 3600, (dt.seconds % 3600) / 60)
|
||||||
|
days = str(dt.days)
|
||||||
|
return Markup("""
|
||||||
|
<script type='text/javascript'>
|
||||||
|
<!--
|
||||||
|
var now = new Date();
|
||||||
|
var year = now.getYear();
|
||||||
|
if (year < 1900) {
|
||||||
|
year += 1900;
|
||||||
|
}
|
||||||
|
var end = new Date(2009, 6, 17, 17, 0, 0);
|
||||||
|
|
||||||
|
function toSt2(n) {
|
||||||
|
s = "";
|
||||||
|
if (n < 10) s += "0";
|
||||||
|
return (s + n).toString();
|
||||||
|
}
|
||||||
|
function toSt3(n) {
|
||||||
|
s = "";
|
||||||
|
if (n < 10) s += "00";
|
||||||
|
else if (n < 100) s += "0";
|
||||||
|
return (s + n).toString();
|
||||||
|
}
|
||||||
|
function countdown() {
|
||||||
|
d = new Date();
|
||||||
|
count = Math.floor(end.getTime() - d.getTime());
|
||||||
|
if(count > 0) {
|
||||||
|
miliseconds = toSt3(count%%1000); count = Math.floor(count/1000);
|
||||||
|
seconds = toSt2(count%%60); count = Math.floor(count/60);
|
||||||
|
minutes = toSt2(count%%60); count = Math.floor(count/60);
|
||||||
|
hours = toSt2(count%%24); count = Math.floor(count/24);
|
||||||
|
days = count;
|
||||||
|
document.getElementById('c1').innerHTML = days + ' TAGE';
|
||||||
|
document.getElementById('c2').innerHTML = hours + ':' + minutes + ':' + seconds + '.' + miliseconds + '';
|
||||||
|
setTimeout("countdown()", 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//-->
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="border:2px solid #fff;padding:10px;-moz-border-radius:17px;-khtml-border-radius:17px;-webkit-border-radius:17px;text-align:center;display:inline-block;">
|
||||||
|
<h2>CountDown</h2>
|
||||||
|
<span id="c1" style="font: bold 30px arial; border:1px solid #000; color:#fff;margin:5px;"> %s Tage</span>
|
||||||
|
<span id="c2" style="font: bold 25px arial; border:1px solid #000; color:#fff;margin:5px;"> %s </span>
|
||||||
|
<script language="javascript" type="text/javascript">countdown();</script>
|
||||||
|
</div>""" % (days, hours))
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,180 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from trac.util.datefmt import utc
|
||||||
|
|
||||||
|
from tracbooking.model import Attendee, Event, EventAccount
|
||||||
|
from tracbooking.utils import get_option_count
|
||||||
|
|
||||||
|
|
||||||
|
def get_file():
|
||||||
|
import codecs
|
||||||
|
return codecs.open(u"/tmp/printout.txt", "wr", "utf-8")
|
||||||
|
|
||||||
|
def add_doc(global_event, selected_tz, f):
|
||||||
|
#f.write(u".. header::\n")
|
||||||
|
#f.write(u" ###Page###\n")
|
||||||
|
#f.write(u" ###Title###\n")
|
||||||
|
dt = selected_tz.fromutc(datetime.now(utc)).strftime("%d.%m.%Y %H:%M")
|
||||||
|
d = u"Erstellt am: %s\n\n" % dt
|
||||||
|
f.write(d)
|
||||||
|
|
||||||
|
f.write(u"%s\n" % global_event.name)
|
||||||
|
f.write(u"%s\n\n" % ("=" * len(global_event.name)))
|
||||||
|
|
||||||
|
|
||||||
|
def create_table(items, f):
|
||||||
|
l = list()
|
||||||
|
l.append((u"Artikel", u"Einzelpreis", u"Anzahl", u"Gesamtpreis"))
|
||||||
|
for i in items:
|
||||||
|
l.append((u"%s" % i[1], u"%.2f" % float(i[2]), u"%s" % i[3], u"%.2f" % float(i[4])))
|
||||||
|
|
||||||
|
widths = list()
|
||||||
|
for cell in xrange(4):
|
||||||
|
tmp = list()
|
||||||
|
for row in l:
|
||||||
|
tmp.append(len(row[cell]))
|
||||||
|
widths.append(max(tmp))
|
||||||
|
|
||||||
|
head = list()
|
||||||
|
for ix, j in enumerate(l[0]):
|
||||||
|
head.append(u" %s " % j.ljust(widths[ix]))
|
||||||
|
trenner = [u"=%s=" % (u"=" * i)
|
||||||
|
for i in widths]
|
||||||
|
|
||||||
|
trenner = u" ".join(trenner) + "\n"
|
||||||
|
f.write(trenner)
|
||||||
|
f.write(u" ".join(head) + "\n")
|
||||||
|
f.write(trenner)
|
||||||
|
for i in l[1:]:
|
||||||
|
t = list()
|
||||||
|
for ix, j in enumerate(i):
|
||||||
|
t.append(u" " + j.ljust(widths[ix]) + " ")
|
||||||
|
f.write(u" ".join(t) + u"\n")
|
||||||
|
indent = sum(widths[:3]) + 10
|
||||||
|
f.write(u"%s %s\n" % (
|
||||||
|
" Gesamt ".center(indent),
|
||||||
|
str("%.2f" % float(sum([item[4] for item in items]))).ljust(widths[3])))
|
||||||
|
f.write(u"%s %s\n\n" % (u"=" * indent, u"=" * (widths[3] + 2)))
|
||||||
|
f.write(u"\n")
|
||||||
|
|
||||||
|
|
||||||
|
def add_summary(env, cursor, global_event, selected_tz, f):
|
||||||
|
|
||||||
|
|
||||||
|
query_old = "SELECT booking_option.ao_id,booking_available_option.name,booking_available_option.price," \
|
||||||
|
"SUM(booking_option.count),booking_available_option.price * SUM(booking_option.count)" \
|
||||||
|
"FROM booking_available_option,booking_option,option_to_event " \
|
||||||
|
"WHERE booking_available_option.ao_id = booking_option.ao_id AND " \
|
||||||
|
"booking_available_option.ao_id = option_to_event.ao_id AND " \
|
||||||
|
"option_to_event.e_id=%s GROUP BY booking_option.ao_id;"
|
||||||
|
|
||||||
|
query = "select " \
|
||||||
|
"booking_option.ao_id, " \
|
||||||
|
"booking_available_option.name, " \
|
||||||
|
"booking_available_option.price, " \
|
||||||
|
"SUM(booking_option.count), " \
|
||||||
|
"SUM(booking_option.count) * booking_available_option.price " \
|
||||||
|
"from " \
|
||||||
|
"booking_option,booking_available_option " \
|
||||||
|
"where " \
|
||||||
|
"booking_option.a_id IN (select a_id from attendee where e_id=%s) AND " \
|
||||||
|
"booking_option.ao_id = booking_available_option.ao_id " \
|
||||||
|
"group by " \
|
||||||
|
"booking_option.ao_id;"
|
||||||
|
|
||||||
|
|
||||||
|
cursor.execute(query, (global_event.e_id,))
|
||||||
|
items = cursor.fetchall()
|
||||||
|
create_table(items, f)
|
||||||
|
|
||||||
|
def add_payment_info(attendee, account, f):
|
||||||
|
txt = "Unser Konto"
|
||||||
|
f.write(u"%s\n%s\n\n" % (txt, u"-" * len(txt)))
|
||||||
|
|
||||||
|
f.write(u" :Kontoinhaber: %s\n" % account.account_owner)
|
||||||
|
f.write(u" :Kontonummer: %s\n" % account.account_no)
|
||||||
|
f.write(u" :Blz: %s\n" % account.bank_no)
|
||||||
|
f.write(u" :Bank: %s\n" % account.bank_name)
|
||||||
|
f.write(u" :Betrag: %s\n" % attendee.calculate_fee())
|
||||||
|
f.write(u" :1. Überweisungszweck: %s\n" % account.first_reason)
|
||||||
|
f.write(u" :2. Überweisungszweck: %s\n" % ("%X" % attendee.a_id).rjust(4, "0"))
|
||||||
|
f.write(u"\n")
|
||||||
|
|
||||||
|
|
||||||
|
def add_internal_attendee_data(attendee, f):
|
||||||
|
actual_amount = attendee.actual_amount and attendee.actual_amount or 0.0
|
||||||
|
f.write(u" :ID: %s\n" % ("%X" % attendee.a_id).rjust(4, "0"))
|
||||||
|
f.write(u" :Email: %s\n" % attendee.email)
|
||||||
|
f.write(u" :Soll-Betrag: %s\n" % attendee.calculate_fee())
|
||||||
|
f.write(u" :Ist-Betrag: %f\n" % actual_amount)
|
||||||
|
f.write(u" :Bezahlt: %s\n" % (u"Ja" if bool(attendee.has_paid) else u"Nein"))
|
||||||
|
f.write(u"\n")
|
||||||
|
|
||||||
|
|
||||||
|
def add_attendee(global_event, attendee, f, internal=False):
|
||||||
|
global_event.add_options(attendee.a_id)
|
||||||
|
|
||||||
|
f.write(u"%s\n" % attendee.nick.replace("_", "\_"))
|
||||||
|
f.write(u"%s\n" % ("-" * len(attendee.nick)))
|
||||||
|
|
||||||
|
if internal:
|
||||||
|
add_internal_attendee_data(attendee, f)
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for option in global_event.options:
|
||||||
|
get_option_count(attendee, option)
|
||||||
|
if not option.count:
|
||||||
|
continue
|
||||||
|
|
||||||
|
items.append([option.ao_id, option.name, option.price, option.count, option.price * option.count])
|
||||||
|
|
||||||
|
if items:
|
||||||
|
create_table(items, f)
|
||||||
|
|
||||||
|
|
||||||
|
def add_attendees(env, cursor, global_event, selected_tz, f, internal=False):
|
||||||
|
|
||||||
|
attendees = Attendee.fetch_all(env, e_id=global_event.e_id, fetch_options=True)
|
||||||
|
attendees = sorted(attendees, key=lambda x: x.nick)
|
||||||
|
|
||||||
|
for attendee in attendees:
|
||||||
|
add_attendee(global_event, attendee, f, internal)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_pdf(f):
|
||||||
|
import rst2pdf.createpdf
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
rst2pdf.createpdf.main(["/tmp/printout.txt", "-o" , "/tmp/printout.pdf"])
|
||||||
|
os.remove("/tmp/printout.txt")
|
||||||
|
tmp = open("/tmp/printout.pdf").read()
|
||||||
|
os.remove("/tmp/printout.pdf")
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
def create_report(env, e_id, selected_tz):
|
||||||
|
db = env.get_db_cnx()
|
||||||
|
cursor = db.cursor()
|
||||||
|
event = Event.fetch_one(env, e_id=e_id)
|
||||||
|
f = get_file()
|
||||||
|
add_doc(event, selected_tz, f)
|
||||||
|
|
||||||
|
add_summary(env, cursor, event, selected_tz, f)
|
||||||
|
add_attendees(env, cursor, event, selected_tz, f, True)
|
||||||
|
return generate_pdf(f)
|
||||||
|
|
||||||
|
def create_attendee_report(env, event, attendee, selected_tz):
|
||||||
|
db = env.get_db_cnx()
|
||||||
|
cursor = db.cursor()
|
||||||
|
|
||||||
|
f = get_file()
|
||||||
|
add_doc(event, selected_tz, f)
|
||||||
|
|
||||||
|
my_attendee = Attendee.fetch_one(env, a_id=attendee.a_id, e_id=event.e_id, fetch_options=True)
|
||||||
|
account = EventAccount.fetch_by_event(env, event.e_id)
|
||||||
|
add_payment_info(my_attendee, account, f)
|
||||||
|
add_attendee(event, my_attendee, f)
|
||||||
|
return generate_pdf(f)
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<!DOCTYPE htm
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
|
<xi:include href="admin.html" />
|
||||||
|
<head>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('input[name^="count"], input[name="email"]').change(function() {
|
||||||
|
$(this).closest("form").submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<title>Anmeldungsstatus für ${event.name}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content" py:with="fee = attendee.calculate_fee()">
|
||||||
|
<div id="booking-attendee" class="booking-block">
|
||||||
|
<h2>Anmeldungsdaten von ${attendee.nick}</h2>
|
||||||
|
<form name="options" uri="#anmeldungsdaten" method="post" mime-type="text/plain" action="">
|
||||||
|
<table class="properties" cellspacing="10px">
|
||||||
|
<tr>
|
||||||
|
<th id="h_author">Nick</th>
|
||||||
|
<td headers="h_author">${attendee.nick}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="h_email">Email</th>
|
||||||
|
<td headers="h_email"><input name="email" size="50" value="${attendee.email}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="h_paid">Bezahlt</th>
|
||||||
|
<td headers="h_paid">${bool(attendee.has_paid) and "Ja" or "Nein"}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="mybuttons">
|
||||||
|
<input type="reset" value="Reset"/>
|
||||||
|
<input py:if="attendee.finished" type="submit" name="download_invoice" value="Abrechnung herunterladen"/>
|
||||||
|
<input py:if="not attendee.finished" type="submit" name="finish" value="Bestellung abschliessen"/>
|
||||||
|
<input py:if="attendee.finished" type="submit" name="unfinish" value="Bestellung wieder freischalten"/>
|
||||||
|
<input py:if="not attendee.finished" type="submit" name="unregister" value="Komplette Bestellung löschen"/>
|
||||||
|
<input type="submit" name="attendee-save" value="Speichern"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id='booking-options' class="booking-block">
|
||||||
|
<py:choose test="">
|
||||||
|
<py:when test="not attendee.finished">
|
||||||
|
<h2 id="available-options">Verfügbare Artikel</h2>
|
||||||
|
<form id="options" name="options" method="post" mime-type="text/plain" action="">
|
||||||
|
<table cellspacing="0px" cellpadding="0xp">
|
||||||
|
<tr>
|
||||||
|
<th>Artikel</th><th>Beschreibung</th><th>Preis in € inkl. MwSt.</th><th>Anzahl</th>
|
||||||
|
</tr>
|
||||||
|
<tr py:for="option in event.options">
|
||||||
|
<td>${option.name}</td>
|
||||||
|
<td>${wiki_to_html(context, option.description)}</td>
|
||||||
|
<td id="price">${"%.2f" % option.price}</td>
|
||||||
|
<td>
|
||||||
|
<input id="count_${option.ao_id}" type="text" size="5" maxlength="4" name="count_${option.ao_id}" value="${option.count and option.count or 0}"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td colspan="3" align="left"><b>Summe:</b></td><td>€ ${fee}</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="mybuttons">
|
||||||
|
<input type="reset" value="Reset"/>
|
||||||
|
<input type="submit" name="save" value="Speichern"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</py:when>
|
||||||
|
<py:otherwise>
|
||||||
|
<h2 id="available-options">Bestellte Artikel</h2>
|
||||||
|
<table cellspacing="0px" cellpadding="0xp">
|
||||||
|
<tr>
|
||||||
|
<th>Artikel</th><th>Beschreibung</th><th>Preis in € inkl. MwSt.</th><th>Anzahl</th>
|
||||||
|
</tr>
|
||||||
|
<tr py:for="option in event.options">
|
||||||
|
<td>${option.name}</td>
|
||||||
|
<td>${wiki_to_html(context, option.description)}</td>
|
||||||
|
<td id="price">${"%.2f" % option.price}</td>
|
||||||
|
<td>${option.count and option.count or 0}</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td colspan="3" align="left"><b>Summe:</b></td><td>€ ${fee}</td></tr>
|
||||||
|
</table>
|
||||||
|
</py:otherwise>
|
||||||
|
</py:choose>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="admin.html" />
|
||||||
|
<head>
|
||||||
|
<title>Teilnehmer verwalten für ${event.name}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py:choose test="">
|
||||||
|
<py:when test="event != None">
|
||||||
|
<h2>Teilnehmer verwalten für ${event.name}</h2>
|
||||||
|
<form method="post" action="">
|
||||||
|
<table class="listing">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Nick</th><th>Email</th><th>Verwendungszweck 1</th><th>Verwendungszweck 2</th><th>Soll-Betrag</th><th>Eingezahlter Betrag</th><th>Bezahlt</th><th>Registrierungsdatum</th><th>Abgeschlossen</th><th>Löschen</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr py:for="ax, attendee in enumerate(attendees)"
|
||||||
|
class="${ax % 2 and 'odd' or 'even'}">
|
||||||
|
<td><a href="${req.href('admin/booking/attendees', event.e_id, attendee.a_id)}">${attendee.nick}</a></td>
|
||||||
|
<td>${attendee.email}</td>
|
||||||
|
<td></td>
|
||||||
|
<td>${("%X" % attendee.a_id).rjust(4, "0")}</td>
|
||||||
|
<td>${"%s" % attendee.calculate_fee()}</td>
|
||||||
|
<td><input type="text" name="actual_amount_${attendee.a_id}" value="${attendee.actual_amount and attendee.actual_amount or None}"/></td>
|
||||||
|
<td><input type="checkbox" name="has_paid" value="${attendee.a_id}" checked="${attendee.has_paid and 'checked' or None}"/></td>
|
||||||
|
<td>${attendee.time}</td>
|
||||||
|
<td><input type="checkbox" name="finished" disabled="disabled" value="${attendee.a_id}" checked="${attendee.finished and 'checked' or None}"/></td>
|
||||||
|
<td><input type="checkbox" name="sel" value="${attendee.a_id}"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><b>Registered Attendees: ${len(attendees)}</b></p>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="save" value="Speichern" />
|
||||||
|
<input type="submit" name="remove_empty" value="Lösche Kunden ohne Bestellungen" />
|
||||||
|
<input type="submit" name="download_report" value="download Bestellungsreport" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</py:when>
|
||||||
|
<py:otherwise>
|
||||||
|
<h2>Bestellung auswählen</h2>
|
||||||
|
<div><ul><li py:for="e in events"><a href="${req.href('admin/booking/attendees', e.e_id)}">${e.name}</a></li></ul></div>
|
||||||
|
</py:otherwise>
|
||||||
|
</py:choose>
|
||||||
|
<p class="help">
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="admin.html" />
|
||||||
|
<head>
|
||||||
|
<title>Bestellungen einsehen für ${eventname}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py:choose test="">
|
||||||
|
<py:when test="items != None">
|
||||||
|
<div id='rendezvous-main'>
|
||||||
|
<table class="listing">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Name</th><th>Einzelpreis €</th><th>Anzahl</th><th>Zusammen</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tr py:for="ix, item in enumerate(items)" class="${ix%2 and 'even' or 'odd'}">
|
||||||
|
<td>${item[1]}</td><td>${"%.2f" % float(item[2])}</td><td>${item[3]}</td><td>${"%.2f" % item[4]}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p><b>Gesamt Preis: ${sum([item[4] for item in items])}</b></p>
|
||||||
|
<form method="post" action="">
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="remove_empty" value="Lösche Kunden ohne Bestellungen" />
|
||||||
|
<input type="submit" name="download_report" value="download report" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</py:when>
|
||||||
|
<py:otherwise>
|
||||||
|
<h2>Bestellung auswählen</h2>
|
||||||
|
<div><ul><li py:for="e in events"><a href="${req.href('admin/booking/einkauf', e.e_id)}">${e.name}</a></li></ul></div>
|
||||||
|
</py:otherwise>
|
||||||
|
</py:choose>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="admin.html" />
|
||||||
|
<head>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* <![CDATA[ */
|
||||||
|
jQuery(document).ready(function($) {
|
||||||
|
$("#gp_date_begin").datepicker({"dateFormat" : "dd.mm.yy"});
|
||||||
|
$("#gp_date_end").datepicker({"dateFormat" : "dd.mm.yy"});
|
||||||
|
$("#gp_edit_deadline").datepicker({"dateFormat" : "dd.mm.yy"});
|
||||||
|
$("#gp_payment_deadline").datepicker({"dateFormat" : "dd.mm.yy"});
|
||||||
|
});
|
||||||
|
/* ]]> */
|
||||||
|
</script>
|
||||||
|
<title>Events verwalten</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Events verwalten</h2>
|
||||||
|
<form method="post" action="">
|
||||||
|
<table class="listing">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Name</th><th>Von</th><th>Bis</th><th>Löschen</th><th>Duplizieren</th><th>Abschliessen</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr py:for="idx, event in enumerate(events)"
|
||||||
|
class="${idx % 2 and 'odd' or 'even'}">
|
||||||
|
<td><a href="${req.href.admin('booking', 'events', event.e_id)}">${event.name}</a></td>
|
||||||
|
<td><a href="${req.href.admin('booking', 'events', event.e_id)}">${event.time_begin}</a></td>
|
||||||
|
<td><a href="${req.href.admin('booking', 'events', event.e_id)}">${event.time_end}</a></td>
|
||||||
|
<td><input type="checkbox" name="rsel" value="${event.e_id}"/></td>
|
||||||
|
<td><input type="radio" name="copy" value="${event.e_id}"/></td>
|
||||||
|
<td><input type="radio" name="finish" value="${event.e_id}"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="save" value="Speichern" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form id="addoption" method="post" action="">
|
||||||
|
<input py:if="event" type="hidden" name="e_id" value="${event.e_id}"/>
|
||||||
|
<fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>${event and "Event editieren" or "Neues Event"}</legend>
|
||||||
|
<table>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_subject">Name:</label></th>
|
||||||
|
<td ><input id="gp_subject" type="text" name="name" value="${event and event.name or None}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_date_begin">Datum Begin:</label></th>
|
||||||
|
<td><input id="gp_date_begin" type="text" name="date_begin" size="10" maxlength="10" value="${event and event.time_begin.strftime('%d.%m.%Y') or None}"/>
|
||||||
|
<input id="gp_time_begin" type="text" name="time_begin" size="5" maxlength="5" value="${event and event.time_begin.strftime('%H:%M') or None}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_date_end">Datum Ende:</label></th>
|
||||||
|
<td><input id="gp_date_end" type="text" name="date_end" size="10" maxlength="10" value="${event and event.time_end.strftime('%d.%m.%Y') or None}"/>
|
||||||
|
<input type="text" name="time_end" size="5" maxlength="5" value="${event and event.time_end.strftime('%H:%M') or None}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_edit_deadline">Edit Deadline:</label></th>
|
||||||
|
<td><input id="gp_edit_deadline" type="text" size="10" maxlength="10" name="edit_deadline_date" value="${event and event.edit_deadline.strftime('%d.%m.%Y') or None}"/>
|
||||||
|
<input type="text" name="edit_deadline_time" size="5" maxlength="5" value="${event and event.edit_deadline.strftime('%H:%M') or None}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_payment_deadline">Bezahl Deadline:</label></th>
|
||||||
|
<td><input id="gp_payment_deadline" type="text" size="10" maxlength="10" name="payment_deadline_date" value="${event and event.payment_deadline.strftime('%d.%m.%Y') or None}"/>
|
||||||
|
<input type="text" name="payment_deadline_time" size="5" maxlength="5" value="${event and event.payment_deadline.strftime('%H:%M') or None}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_desc">Beschreibung:</label></th>
|
||||||
|
<td><textarea id="gp_desc" rows="20" cols="80" name="description">${event and event.description or None}</textarea></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>${event_account and "Konto editieren" or "Neues Konto"}</legend>
|
||||||
|
<table>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_account_owner">Kontoinhaber:</label></th>
|
||||||
|
<td><input id="gp_account_owner" type="text" name="account_owner" value="${event_account and event_account.account_owner or 'Chaostreff Dortmund'}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_account_no">Kontonummer:</label></th>
|
||||||
|
<td><input id="gp_account_no" type="text" name="account_no" value="${event_account and event_account.account_no or '4009368600'}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_bank_no">Geldinstitut:</label></th>
|
||||||
|
<td><input id="gp_bank_no" type="text" name="bank_name" value="${event_account and event_account.bank_name or 'GLS-Bank'}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_bank_no">BLZ:</label></th>
|
||||||
|
<td><input id="gp_bank_no" type="text" name="bank_no" value="${event_account and event_account.bank_no or '43060967'}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_first_reason">1. Überweisungszweck:</label></th>
|
||||||
|
<td><input id="gp_first_reason" type="text" name="first_reason" value="${event_account and event_account.first_reason or 'Mate'}"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
<div class="buttons"><input type="submit" name="add" value="Speichern" /></div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,70 @@
|
||||||
|
<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="admin.html" />
|
||||||
|
<head>
|
||||||
|
<title>Optionen verwalten</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Optionen verwalten</h2>
|
||||||
|
<form py:if="options" method="post" action="">
|
||||||
|
<table class="listing">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Name</th><th>Beschreibung</th><th>Preis</th><th>Aktiv</th><th>Min Anzahl</th><th>Max Anzahl</th><th>Löschen</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr py:for="idx, op in enumerate(options)"
|
||||||
|
class="${idx % 2 and 'odd' or 'even'}">
|
||||||
|
<td><a href="${req.href.admin('booking', 'options', op.ao_id)}">${op.name}</a></td>
|
||||||
|
<td>${op.description}</td>
|
||||||
|
<td>${"%.2f" % op.price}</td>
|
||||||
|
<td><input type="checkbox" name="actives" value="${op.ao_id}" checked="${op.active==1 and 'checked' or None}"/></td>
|
||||||
|
<td>${op.min_count}</td>
|
||||||
|
<td>${op.max_count}</td>
|
||||||
|
<td><input type="checkbox" name="rsel" value="${op.ao_id}"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="save" value="speichern" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form id="addoption" method="post" action="">
|
||||||
|
<fieldset>
|
||||||
|
<legend>${option.ao_id and "Editiere Option" or "Neue Option"}:</legend>
|
||||||
|
<table>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_subject">Name:</label></th>
|
||||||
|
<td><input id="gp_subject" type="text" size="40" name="name" value="${option.name}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_price">Preis:</label></th>
|
||||||
|
<td><input id="gp_price" type="text" name="price" value="${option.price}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_min_count">Min Anzahl:</label></th>
|
||||||
|
<td><input id="gp_min_count" type="text" name="min_count" value="${option.min_count}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_max_count">Max Anzahl:</label></th>
|
||||||
|
<td><input id="gp_max_count" type="text" name="max_count" value="${option.max_count}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_stock_count">Lagerbestand:</label></th>
|
||||||
|
<td><input id="gp_stock_count" type="text" name="stock_count" value="${option.stock_count}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_desc">Beschreibung:</label></th>
|
||||||
|
<td><textarea id="gp_desc" rows="10" cols="100" name="desc">${option.description}</textarea></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="add" value="Speichern"/>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="admin.html" />
|
||||||
|
<head>
|
||||||
|
<title>$label_plural</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Manage RendezVousTypes</h2>
|
||||||
|
|
||||||
|
<form id="addsubj" class="addoption" method="post" action="">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Verknüpfe Option mit Event:</legend>
|
||||||
|
<table>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="event">Option:</label></th>
|
||||||
|
<td><select id="option" name="option">
|
||||||
|
<option py:for="option in sorted(options)">${option.name}</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="event">Event:</label></th>
|
||||||
|
<td><select id="event" name="event">
|
||||||
|
<option py:for="event in sorted(events)">${event.name}</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p class="help">
|
||||||
|
Grant permission to a RendezVousType.
|
||||||
|
</p>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="add" value=" Add " />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="">
|
||||||
|
<table class="listing" id="permlist">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Event</th><th>Default</th><th>Optionen</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr py:for="idx, event in enumerate(events)" py:if="event.options"
|
||||||
|
class="${idx % 2 and 'odd' or 'even'}">
|
||||||
|
<td>${event.name}</td>
|
||||||
|
<td><input type="radio" name="default" value="${event.e_id}" checked=""/></td>
|
||||||
|
<td>
|
||||||
|
<div py:for="option in event.options">
|
||||||
|
<input type="checkbox" id="${event.e_id}:${option.ao_id}" name="sel" value="${event.e_id}:${option.ao_id}"/>
|
||||||
|
<label for="${event.e_id}:${option.ao_id}">${option.name}</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="save" value="Save Changes" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<p class="help">
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="admin.html" />
|
||||||
|
<head>
|
||||||
|
<title>Benachrichtigungen verwalten</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py:choose test="">
|
||||||
|
<py:when test="reminders != None">
|
||||||
|
<h2>Benachrichtigungen verwalten für $eventname</h2>
|
||||||
|
<div class="admin-block">
|
||||||
|
<form method="post" action="">
|
||||||
|
<table class="listing">
|
||||||
|
<thead>
|
||||||
|
<tr><th>ID</th><th>Text</th><th>Versendedatum</th><th>Wurde gesendet</th><th>Löschen</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr py:for="ax, reminder in enumerate(reminders)" class="${ax % 2 and 'odd' or 'even'}">
|
||||||
|
<td><a href="${req.href.admin('reminder', 'edit', reminder.reminder_id)}">${reminder.reminder_id}</a></td>
|
||||||
|
<td>${wiki_to_html(context, reminder.text < 30 and reminder.text or reminder.text[:30] + "...")}</td>
|
||||||
|
<td>${reminder.notify_on}</td>
|
||||||
|
<td>${reminder.was_send_on}</td>
|
||||||
|
<td><input type="checkbox" name="sel" value="${reminder.a_id}"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><b>Registrierte Benachrichtigungen: ${len(reminders)}</b></p>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="save" value="speichern" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<form id="addoption" method="post" action="">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Neuer Reminder:</legend>
|
||||||
|
<table>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_notify">Datum und Uhrzeit:</label></th>
|
||||||
|
<td><input id="gp_notify" type="text" name="notify_on" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field">
|
||||||
|
<th><label for="gp_text">Nachricht:</label></th>
|
||||||
|
<td><textarea id="gp_text" rows="20" cols="100" name="text" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p class="help">Erstelle neuen Reminder.</p>
|
||||||
|
<div class="buttons"><input type="submit" name="add" value="Speichern" /></div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</py:when>
|
||||||
|
<py:otherwise>
|
||||||
|
<h2>Event wählen</h2>
|
||||||
|
<div><ul><li py:for="e in events"><a href="${req.href('admin/booking/reminder', e.e_id)}">${e.name}</a></li></ul></div>
|
||||||
|
</py:otherwise>
|
||||||
|
</py:choose>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE htm
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
|
<xi:include href="layout.html" />
|
||||||
|
<head>
|
||||||
|
<title>Bitte bestätigen</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<h2>$query</h2>
|
||||||
|
<form method="post">
|
||||||
|
<input type="submit" name="$query_false" value="Abbrechen"/>
|
||||||
|
<input type="submit" name="$query_true" value="Fortfahren"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE htm
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
|
<xi:include href="layout.html" />
|
||||||
|
<head>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* <![CDATA[ */
|
||||||
|
jQuery(document).ready(function($) {$("#nr_nick").get(0).focus()});
|
||||||
|
/* ]]> */
|
||||||
|
</script>
|
||||||
|
<title>Übersicht Sammelbestellungen</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<h1>Sammelbestellungen</h1>
|
||||||
|
<div id='rendezvous-main'>
|
||||||
|
<ul>
|
||||||
|
<li py:for="event in events"><a href="${req.href.booking(event.e_id)}">${event.name}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!DOCTYPE htm
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
|
<xi:include href="layout.html" />
|
||||||
|
<head>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* <![CDATA[ */
|
||||||
|
jQuery(document).ready(function($) {$("#nr_nick").get(0).focus()});
|
||||||
|
/* ]]> */
|
||||||
|
</script>
|
||||||
|
<title>Anmeldung für ${event.name}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<h1>Teilnahme an '${event.name}'</h1>
|
||||||
|
<!--<table>
|
||||||
|
<tr>
|
||||||
|
<th><label for=""></label></th><td id=""></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for=""></label></th><td id=""></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for=""></label></th><td id=""></td>
|
||||||
|
</tr>
|
||||||
|
</table>-->
|
||||||
|
<div>${wiki_to_html(context, event.description, escape_newlines=preserve_newlines)}</div>
|
||||||
|
<div py:if="event.edit_deadline > now">Editierbar bis ${event.edit_deadline.strftime('%d.%m.%Y %H:%M')} Uhr.</div>
|
||||||
|
<div>Der Betrag deiner Bestellung muss spätestens am ${event.payment_deadline.strftime('%d.%m.%Y %H:%M')} Uhr auf unserem Konto eingegangen sein!!!</div>
|
||||||
|
<div id='booking-attendee'>
|
||||||
|
<form name="register" uri="" method="post" mime-type="text/plain" action="">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Wichtige Daten</legend>
|
||||||
|
<table class="register">
|
||||||
|
<tr><th><label for="nr_nick">Nick</label></th><td>${authname}</td></tr>
|
||||||
|
<tr><th><label for="nr_email">Email (optional):</label></th><td><input id="nr_email" type="text" size="30" maxlength="100" name="email" /></td></tr>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
<div class="mybuttons">
|
||||||
|
<input type="reset" value="Reset"/>
|
||||||
|
<input type="submit" name="register" value="Ich will mitbestellen!!!!"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,162 @@
|
||||||
|
<!DOCTYPE htm
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
|
<xi:include href="layout.html" />
|
||||||
|
<head>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('input[name^="count"], input[name="email"]').change(function() {
|
||||||
|
$(this).closest("form").submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<title>Deine Artikel für ${event.name}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content" py:with="fee = attendee.calculate_fee()">
|
||||||
|
<div id="event_details" class="booking-block">
|
||||||
|
<h1>${event.name}</h1>
|
||||||
|
<div>${wiki_to_html(context, event.description, escape_newlines=preserve_newlines)}</div>
|
||||||
|
<table class="properties" cellspacing="10px">
|
||||||
|
<!--<py:if test="event.edit_deadline < now or register_deadline < now"> -->
|
||||||
|
<!--<tr>
|
||||||
|
<th id="timeframe">Bestellung Zeitraum:</th>
|
||||||
|
<td headers="timeframe">${event.time_begin.strftime('%d.%m.%Y %H:%M')} - ${event.time_end.strftime('%d.%m.%Y %H:%M')}</td>
|
||||||
|
</tr>-->
|
||||||
|
<!--<tr>
|
||||||
|
<th id="register_deadline" py:if="event.register_deadline > now">Registrierungs-Zeitraum:</th>
|
||||||
|
<td headers="register_deadline">${event.register_deadline.strftime('%d.%m.%Y %H:%M')} Uhr.</td>
|
||||||
|
</tr>-->
|
||||||
|
<tr>
|
||||||
|
<th id="edit-deadline">Bestellungen können verändert werden bis zum:</th>
|
||||||
|
<td headers="edit-deadline" id="edit-deadline-data">${event.edit_deadline.strftime('%d.%m.%Y %H:%M')} Uhr.</td>
|
||||||
|
</tr>
|
||||||
|
<!--</py:if> -->
|
||||||
|
<tr>
|
||||||
|
<th id="payment-deadline">Geld bitte auf unten angebenes Konto einzahlen bis zum:</th>
|
||||||
|
<td headers="payment-deadline" id="payment-deadline-data">${event.payment_deadline.strftime('%d.%m.%Y %H:%M')} Uhr.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">Der Liefertermin wird per Mail bekannt gegeben, sofern Du eine Mailaddresse angegeben hast.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">Du kannst Deine Registrierung unten komplett löschen.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">Wenn Du javascript angeschaltet hast, werden Änderungen beim Verlassen des Eingabefeldes automatisch gespeichert.
|
||||||
|
Wenn Du javascript blockiert hast, musst Du nach Änderungen manuell auf den Speichern Button klicken.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">Wenn Du mit Deiner Bestellung zufrieden bist, und die Bestellung abschliessen möchtest, dann drücke bitte
|
||||||
|
auf Abschliessen. Dann findest Du unter "Deine Daten" einen Link, mit dem Du eine Zusammenfassung Deiner Bestellung und alle relevanten
|
||||||
|
Daten zum bequemen Bezahlen Deines Anteiles als pdf herunterladen kannst.</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="booking-attendee" class="booking-block">
|
||||||
|
<h1 id="anmeldungsdaten">Deine Daten</h1>
|
||||||
|
<form name="options" uri="#anmeldungsdaten" method="post" mime-type="text/plain" action="">
|
||||||
|
<table class="properties" cellspacing="10px">
|
||||||
|
<tr>
|
||||||
|
<th id="h_author">Nick</th>
|
||||||
|
<td headers="h_author">${attendee.nick}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="h_email">Email</th>
|
||||||
|
<td headers="h_email"><input name="email" size="50" value="${attendee.email}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="h_paid">Bezahlt</th>
|
||||||
|
<td headers="h_paid">${bool(attendee.has_paid) and "Ja" or "Nein"}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="mybuttons">
|
||||||
|
<input type="reset" value="Reset"/>
|
||||||
|
<input py:if="attendee.finished" type="submit" name="download_invoice" value="Abrechnung herunterladen"/>
|
||||||
|
<input py:if="not attendee.finished" type="submit" name="finish" value="Bestellung abschliessen"/>
|
||||||
|
<input py:if="not attendee.finished" type="submit" name="unregister" value="Komplette Bestellung löschen"/>
|
||||||
|
<input type="submit" name="attendee-save" value="Speichern"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div py:if="attendee.finished" id="booking-bank" class="booking-block">
|
||||||
|
<h1 id="kontodaten">Bezahlvorgang per Überweisung</h1>
|
||||||
|
<table class="properties" cellspacing="10px">
|
||||||
|
<tr>
|
||||||
|
<th id="name">Kontoinhaber</th>
|
||||||
|
<td headers="name">${account_data.account_owner}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="konto">Konto</th>
|
||||||
|
<td headers="konto">${account_data.account_no}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="blz">Blz</th>
|
||||||
|
<td headers="blz">${account_data.bank_no}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="betrag">Betrag</th>
|
||||||
|
<td headers="betrag">€ ${fee}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="bank">Bank</th>
|
||||||
|
<td headers="bank">${account_data.bank_name}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="h_extid">1. Überweisungszweck</th>
|
||||||
|
<td headers="h_extid">${account_data.first_reason}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th id="h_extid">2. Überweisungszweck</th>
|
||||||
|
<td headers="h_extid">${("%X" % attendee.a_id).rjust(4, "0")}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id='booking-options' class="booking-block">
|
||||||
|
<py:choose test="">
|
||||||
|
<py:when test="not attendee.finished">
|
||||||
|
<h1 id="available-options">Verfügbare Artikel</h1>
|
||||||
|
<form id="options" name="options" method="post" mime-type="text/plain" action="">
|
||||||
|
<table cellspacing="0px" cellpadding="0xp">
|
||||||
|
<tr>
|
||||||
|
<th>Artikel</th><th>Beschreibung</th><th>Preis in € inkl. MwSt.</th><th>Anzahl</th>
|
||||||
|
</tr>
|
||||||
|
<tr py:for="option in event.options">
|
||||||
|
<td>${option.name}</td>
|
||||||
|
<td>${wiki_to_html(context, option.description)}</td>
|
||||||
|
<td id="price">${"%.2f" % option.price}</td>
|
||||||
|
<td>
|
||||||
|
<input id="count_${option.ao_id}" type="text" size="5" maxlength="4" name="count_${option.ao_id}" value="${option.count and option.count or 0}"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td colspan="3" align="left"><b>Summe:</b></td><td>€ ${fee}</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="mybuttons">
|
||||||
|
<input type="reset" value="Reset"/>
|
||||||
|
<input type="submit" name="save" value="Speichern"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</py:when>
|
||||||
|
<py:otherwise>
|
||||||
|
<h1 id="available-options">Bestellte Artikel</h1>
|
||||||
|
<table cellspacing="0px" cellpadding="0xp">
|
||||||
|
<tr>
|
||||||
|
<th>Artikel</th><th>Beschreibung</th><th>Preis in € inkl. MwSt.</th><th>Anzahl</th>
|
||||||
|
</tr>
|
||||||
|
<tr py:for="option in event.options">
|
||||||
|
<td>${option.name}</td>
|
||||||
|
<td>${wiki_to_html(context, option.description)}</td>
|
||||||
|
<td id="price">${"%.2f" % option.price}</td>
|
||||||
|
<td>${option.count and option.count or 0}</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td colspan="3" align="left"><b>Summe:</b></td><td>€ ${fee}</td></tr>
|
||||||
|
</table>
|
||||||
|
</py:otherwise>
|
||||||
|
</py:choose>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE htm
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
|
<xi:include href="layout.html" />
|
||||||
|
<head>
|
||||||
|
<title>Registrierung gelöscht!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<h2>Deine Registrierung und alle dazugehörigen Daten sind erfolgreich gelöscht worden!</h2>
|
||||||
|
<h3><a href="${req.href.wiki()}">Zurück zum wiki</a></h3>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="admin.html" />
|
||||||
|
<head>
|
||||||
|
<title>Accounts: Configuration</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>Manage Site Files</h2>
|
||||||
|
|
||||||
|
<py:if test="'USER_UPLOAD' in perm(context.resource)">
|
||||||
|
<form id="addfile" action="" class="addnew" method="post" enctype="multipart/form-data">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Upload File:</legend>
|
||||||
|
<div class="field">
|
||||||
|
<label>File: <input type="file" name="site_file"
|
||||||
|
disabled="${readonly and 'disabled' or None}" /></label>
|
||||||
|
</div>
|
||||||
|
<p class="help" py:choose="True">
|
||||||
|
<py:when test="readonly">
|
||||||
|
The web server does not have sufficient permissions to
|
||||||
|
store files in your home directory.
|
||||||
|
</py:when>
|
||||||
|
<py:otherwise>
|
||||||
|
Upload a file to your home directory.
|
||||||
|
</py:otherwise>
|
||||||
|
</p>
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="upload" value="Upload"
|
||||||
|
disabled="${readonly and 'disabled' or None}" />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</py:if>
|
||||||
|
<form method="post">
|
||||||
|
<table class="listing" id="sitelist">
|
||||||
|
<thead>
|
||||||
|
<tr><th colspan="3" style="text-align:center;">Content of ${project.name}/users/${authname}</th></tr>
|
||||||
|
<tr><th class="sel">delete</th><th>Filename</th><th>Size</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr py:for="file in files" >
|
||||||
|
<td><input type="checkbox" name="sel" value="${file.name}"/></td>
|
||||||
|
<td>${file.link}</td>
|
||||||
|
<td>${file.size}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<py:if test="'USER_UPLOAD_MANAGE' in perm(context.resource)">
|
||||||
|
<div class="buttons">
|
||||||
|
<input type="submit" name="delete" value="Delete selected files"
|
||||||
|
disabled="${readonly and 'disabled' or None}" />
|
||||||
|
</div>
|
||||||
|
</py:if>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,103 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from sys import maxint
|
||||||
|
from datetime import datetime, date, time
|
||||||
|
from trac.util.datefmt import utc, to_timestamp, localtz, format_time, get_timezone, timezone
|
||||||
|
from re import match, compile as re_compile
|
||||||
|
|
||||||
|
class ValidationError(ValueError):
|
||||||
|
def __str__(self):
|
||||||
|
return "ValidationError: value out of bounds!"
|
||||||
|
|
||||||
|
def validate_id(value):
|
||||||
|
if (0 > value > maxint):
|
||||||
|
raise ValidationError("invalid argument")
|
||||||
|
|
||||||
|
def make_hash():
|
||||||
|
from random import sample
|
||||||
|
population = '123456789ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||||
|
return "".join(sample(population, 4))
|
||||||
|
|
||||||
|
def date_parse(foo):
|
||||||
|
groups = match("^(\d{1,2}).(\d{1,2}).(\d{4})$", foo).groups()
|
||||||
|
return date(int(groups[2]),int(groups[1]),int(groups[0]))
|
||||||
|
|
||||||
|
def time_parse(foo):
|
||||||
|
timeM = re_compile("^(\d{1,2}):?(\d{2})$")
|
||||||
|
res = timeM.match(foo)
|
||||||
|
if not res:
|
||||||
|
raise ValueError("not valid time format")
|
||||||
|
h,m = res.groups()
|
||||||
|
return time(int(h),int(m))
|
||||||
|
|
||||||
|
def validate_email(addr):
|
||||||
|
rfc822_specials = '()<>@,;:\\"[]'
|
||||||
|
# First we validate the name portion (name@domain)
|
||||||
|
c = 0
|
||||||
|
while c < len(addr):
|
||||||
|
if addr[c] == '"' and (not c or addr[c - 1] == '.' or addr[c - 1] == '"'):
|
||||||
|
c = c + 1
|
||||||
|
while c < len(addr):
|
||||||
|
if addr[c] == '"':
|
||||||
|
break
|
||||||
|
if addr[c] == '\\' and addr[c + 1] == ' ':
|
||||||
|
c = c + 2
|
||||||
|
continue
|
||||||
|
if ord(addr[c]) < 32 or ord(addr[c]) >= 127:
|
||||||
|
return 0
|
||||||
|
c = c + 1
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if addr[c] == '@':
|
||||||
|
break
|
||||||
|
if addr[c] != '.':
|
||||||
|
return False
|
||||||
|
c = c + 1
|
||||||
|
continue
|
||||||
|
if addr[c] == '@':
|
||||||
|
break
|
||||||
|
if ord(addr[c]) <= 32 or ord(addr[c]) >= 127:
|
||||||
|
return False
|
||||||
|
if addr[c] in rfc822_specials:
|
||||||
|
return False
|
||||||
|
c = c + 1
|
||||||
|
if not c or addr[c - 1] == '.':
|
||||||
|
return False
|
||||||
|
# Next we validate the domain portion (name@domain)
|
||||||
|
domain = c = c + 1
|
||||||
|
if domain >= len(addr):
|
||||||
|
return False
|
||||||
|
count = 0
|
||||||
|
while c < len(addr):
|
||||||
|
if addr[c] == '.':
|
||||||
|
if c == domain or addr[c - 1] == '.':
|
||||||
|
return False
|
||||||
|
count = count + 1
|
||||||
|
if ord(addr[c]) <= 32 or ord(addr[c]) >= 127:
|
||||||
|
return False
|
||||||
|
if addr[c] in rfc822_specials:
|
||||||
|
return False
|
||||||
|
c = c + 1
|
||||||
|
return count >= 1
|
||||||
|
|
||||||
|
def get_tz(session_tzname):
|
||||||
|
if session_tzname == 'UTC':
|
||||||
|
selected_tz = utc
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
selected_tz = timezone(session_tzname)
|
||||||
|
except Exception, e:
|
||||||
|
print e
|
||||||
|
selected_tz = utc
|
||||||
|
return session_tzname, selected_tz
|
||||||
|
|
||||||
|
def get_option_count(a, aopt):
|
||||||
|
aopt.count = 0
|
||||||
|
print a, a.options
|
||||||
|
for i in a.options:
|
||||||
|
if i.ao_id == aopt.ao_id:
|
||||||
|
i.name = aopt.name
|
||||||
|
aopt.count = i.count
|
||||||
|
return
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,507 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from genshi.builder import tag
|
||||||
|
from model import *
|
||||||
|
from os import mkdir
|
||||||
|
import os.path
|
||||||
|
from pkg_resources import resource_filename
|
||||||
|
from re import match, sub
|
||||||
|
from trac.admin import IAdminPanelProvider
|
||||||
|
from trac.config import *
|
||||||
|
from trac.core import Component, implements, TracError
|
||||||
|
import shutil
|
||||||
|
import stat
|
||||||
|
import time
|
||||||
|
from threading import Thread, Lock
|
||||||
|
|
||||||
|
from trac.perm import PermissionError, IPermissionRequestor
|
||||||
|
from trac.util.datefmt import utc
|
||||||
|
from trac.util.html import html
|
||||||
|
from trac.util import Markup, pretty_size
|
||||||
|
from trac.util.translation import _
|
||||||
|
from trac.util.datefmt import utc, to_timestamp
|
||||||
|
from trac.web import RequestDone
|
||||||
|
|
||||||
|
from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_script, add_warning, add_ctxtnav, add_notice
|
||||||
|
from trac.web import IRequestHandler
|
||||||
|
|
||||||
|
from tracbooking.report import create_attendee_report
|
||||||
|
from tracbooking.utils import validate_id, make_hash, get_option_count, get_tz, validate_email
|
||||||
|
|
||||||
|
__all__ = ['BookingComponent', 'UserUploadComponent']
|
||||||
|
|
||||||
|
|
||||||
|
class BookingComponent(Component):
|
||||||
|
|
||||||
|
'''The web ui frontend or the rendezvous system'''
|
||||||
|
|
||||||
|
implements(INavigationContributor,
|
||||||
|
IRequestHandler,
|
||||||
|
IPermissionRequestor,
|
||||||
|
ITemplateProvider)
|
||||||
|
|
||||||
|
Option("booking", "account_owner", u"Chaostreff Dortmund")
|
||||||
|
Option("booking", "account", u"4009368600 ")
|
||||||
|
Option("booking", "bank_no", u"43060967 ")
|
||||||
|
Option("booking", "bank_name", u"GLS-Bank")
|
||||||
|
Option("booking", "first_reason", u"BBQ2010")
|
||||||
|
|
||||||
|
# IPermissionRequestor methods
|
||||||
|
def get_permission_actions(self):
|
||||||
|
'''returns all permissions this component provides'''
|
||||||
|
return ["BOOKING_VIEW", ("BOOKING_ADMIN", ("BOOKING_VIEW"))]
|
||||||
|
|
||||||
|
# INavigationContributor methods
|
||||||
|
def get_active_navigation_item(self, req):
|
||||||
|
return 'booking'
|
||||||
|
|
||||||
|
def get_navigation_items(self, req):
|
||||||
|
if not "BOOKING_VIEW" in req.perm:
|
||||||
|
return
|
||||||
|
yield ('mainnav', 'booking', html.A('Sammelbestellungen', href= req.href.booking()))
|
||||||
|
|
||||||
|
def match_request(self, req):
|
||||||
|
|
||||||
|
key = req.path_info
|
||||||
|
if key == '/booking':
|
||||||
|
return True
|
||||||
|
m = match(r'/booking/(\d+)$', key)
|
||||||
|
if m:
|
||||||
|
req.args['event_id'] = int(m.group(1))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def process_request(self, req):
|
||||||
|
req.perm.require("BOOKING_VIEW")
|
||||||
|
query = req.path_info
|
||||||
|
add_stylesheet (req, 'hw/css/booking.css')
|
||||||
|
if not req.args.has_key("event_id"):
|
||||||
|
return self._display_overview(req)
|
||||||
|
|
||||||
|
e_id = req.args["event_id"]
|
||||||
|
a = Attendee.fetch_one(self.env, nick=req.authname, e_id=e_id)
|
||||||
|
if a:
|
||||||
|
return self._process_status(req, a)
|
||||||
|
else:
|
||||||
|
if self.env.config.get("booking", "autoregister"):
|
||||||
|
event = Event.fetch_one(self.env, e_id)
|
||||||
|
nick = req.authname
|
||||||
|
rd = Attendee(self.env, 0, event.e_id, 0, nick, None, 0, 0, datetime.now(utc), 0)
|
||||||
|
rd.commit()
|
||||||
|
return self._process_status(req, rd)
|
||||||
|
return self._process_register(req)
|
||||||
|
|
||||||
|
# ITemplateProvider methods
|
||||||
|
# Used to add the plugin's templates and htdocs
|
||||||
|
def get_templates_dirs(self):
|
||||||
|
return [resource_filename(__name__, 'templates'),]
|
||||||
|
|
||||||
|
def get_htdocs_dirs(self):
|
||||||
|
"""Return a list of directories with static resources (such as style
|
||||||
|
sheets, images, etc.)
|
||||||
|
|
||||||
|
Each item in the list must be a `(prefix, abspath)` tuple. The
|
||||||
|
`prefix` part defines the path in the URL that requests to these
|
||||||
|
resources are prefixed with.
|
||||||
|
|
||||||
|
The `abspath` is the absolute path to the directory containing the
|
||||||
|
resources on the local file system.
|
||||||
|
"""
|
||||||
|
return [('hw', resource_filename(__name__, 'htdocs'))]
|
||||||
|
|
||||||
|
def _process_register(self, req):
|
||||||
|
|
||||||
|
'''process add,change,delete actions for dates'''
|
||||||
|
data = {"now" : datetime.now(utc)}
|
||||||
|
#a = Attendee.fetch_one(self.env, nick=req.authname)
|
||||||
|
#if a:
|
||||||
|
#req.redirect(req.href.booking("status"))
|
||||||
|
e_id = req.args["event_id"]
|
||||||
|
event = Event.fetch_one(self.env, e_id)
|
||||||
|
if not event:
|
||||||
|
raise TracError("Event konnte nicht gefunden werden")
|
||||||
|
data["event"] = event
|
||||||
|
if req.method == "POST":
|
||||||
|
if req.args.has_key("register"):
|
||||||
|
nick = req.authname
|
||||||
|
if req.args.has_key("email"):
|
||||||
|
email = req.args.get("email")
|
||||||
|
if email and not validate_email(email):
|
||||||
|
add_warning(req, u"email ungültig")
|
||||||
|
return "booking_register.html", data, None
|
||||||
|
rd = Attendee(self.env, 0, event.e_id, 0, nick, email, 0, 0, datetime.now(utc), 0)
|
||||||
|
rd.commit()
|
||||||
|
req.redirect(req.href.booking(e_id))
|
||||||
|
return 'booking_register.html', data, None
|
||||||
|
|
||||||
|
def _display_overview(self, req):
|
||||||
|
return "booking_events.html", {"events": Event.fetch_all(self.env)}, None
|
||||||
|
|
||||||
|
def _process_status(self, req, attendee):
|
||||||
|
|
||||||
|
'''display the status if already registered and optional features'''
|
||||||
|
|
||||||
|
add_stylesheet (req, 'hw/css/booking.css')
|
||||||
|
notice = "Bestellung erfolgreich gespeichert."
|
||||||
|
if req.session.has_key("notice"):
|
||||||
|
add_notice(req, req.session["notice"])
|
||||||
|
del req.session["notice"]
|
||||||
|
req.session.save()
|
||||||
|
#attendee.finished = False
|
||||||
|
#attendee.update()
|
||||||
|
if req.method == "POST":
|
||||||
|
if not attendee.finished:
|
||||||
|
failure = False
|
||||||
|
if req.args.has_key("unregister"):
|
||||||
|
return "booking_accept.html", {"query": u"Möchtest Du wirklich Deine Bestellung komplett löschen?", "query_true" : "unregister_true", "query_false" : "unregister_false"}, None
|
||||||
|
elif req.args.has_key("unregister_true"):
|
||||||
|
UserUploadComponent(self.env).clean_userdir(attendee)
|
||||||
|
Attendee.delete(self.env, attendee.a_id)
|
||||||
|
req.redirect(req.href.wiki())
|
||||||
|
elif req.args.has_key("unregister_false") or req.args.has_key("finish_false"):
|
||||||
|
pass
|
||||||
|
elif req.args.has_key("finish"):
|
||||||
|
return "booking_accept.html", {"query": u"Möchtest Du wirklich Deine Bestellung abschliessen?", "query_true" : "finish_true", "query_false" : "finish_false"}, None
|
||||||
|
elif req.args.has_key("finish_true"):
|
||||||
|
attendee.finished = True
|
||||||
|
attendee.update()
|
||||||
|
req.redirect(req.href.booking(req.args["event_id"]))
|
||||||
|
elif req.args.has_key("attendee-save"):
|
||||||
|
email = req.args["email"]
|
||||||
|
if not validate_email(email):
|
||||||
|
add_warning(req, u"email nicht gültig")
|
||||||
|
attendee.email = email
|
||||||
|
attendee.update()
|
||||||
|
req.session["notice"] = "Daten erfolgreich aktualisiert."
|
||||||
|
else:
|
||||||
|
args = req.args
|
||||||
|
for arg in args:
|
||||||
|
if arg.startswith("count"):
|
||||||
|
try:
|
||||||
|
prefix, ao_id = arg.split("_", 1)
|
||||||
|
ao_id = int(ao_id)
|
||||||
|
count = int(args[arg])
|
||||||
|
validate_id(count)
|
||||||
|
validate_id(ao_id)
|
||||||
|
except ValueError:
|
||||||
|
add_warning(req, u"Bitte für Anzahlfelder nur positive Zahen eingeben.")
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
aoption = AvailableOption.fetch_one(self.env, ao_id, fetch_variations=False)
|
||||||
|
|
||||||
|
if not aoption:
|
||||||
|
add_warning(req, u"Artikel %r nicht gefunden" % ao_id)
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
elif not aoption.active:
|
||||||
|
add_warning(req, u"Artikel %r nicht aktiviert" % ao_id)
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if count < aoption.min_count:
|
||||||
|
add_warning(req, u"Artikel '%s' kann minimal '%d' Mal bestellt werden;-)" % (aoption.name, aoption.min_count))
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
elif aoption.max_count and count > aoption.max_count:
|
||||||
|
add_warning(req, u"Artikel '%s' kann maximal '%d' Mal bestellt werden;-)" % (aoption.name, aoption.max_count))
|
||||||
|
failure = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not count:
|
||||||
|
BookingOption.delete(self.env, attendee.a_id, ao_id)
|
||||||
|
else:
|
||||||
|
opt = BookingOption.fetch_one(self.env, attendee.a_id, ao_id)
|
||||||
|
if not opt:
|
||||||
|
opt = BookingOption(self.env, 0, attendee.a_id, ao_id, count)
|
||||||
|
opt.commit()
|
||||||
|
else:
|
||||||
|
opt.count = count
|
||||||
|
opt.update()
|
||||||
|
#elif arg.startswith("var"):
|
||||||
|
#prefix, variation_id = arg.split("_", 1)
|
||||||
|
#try:
|
||||||
|
#variation_id = int(variation_id)
|
||||||
|
#validate_id(variation_id)
|
||||||
|
#value = int(args[arg])
|
||||||
|
#validate_id(value)
|
||||||
|
#except (ValueError,):
|
||||||
|
#add_warning(req, u"Bitte eine Zahl eingeben;-)")
|
||||||
|
#failure = True
|
||||||
|
#continue
|
||||||
|
|
||||||
|
#variation = BookingOptionVariation.fetch_one(self.env, attendee.a_id, variation_id)
|
||||||
|
#if not variation:
|
||||||
|
#b = BookingOptionVariation(self.env, attendee.a_id, variation_id, value)
|
||||||
|
#b.commit()
|
||||||
|
#else:
|
||||||
|
#BookingOptionVariation.update(self.env, attendee.a_id, variation_id, value)
|
||||||
|
print "before redirect"
|
||||||
|
if failure:
|
||||||
|
req.redirect(req.href.booking(req.args["event_id"]))
|
||||||
|
else:
|
||||||
|
req.session["notice"] = notice
|
||||||
|
|
||||||
|
req.redirect(req.href.booking(req.args["event_id"]))
|
||||||
|
elif req.args.has_key("download_invoice"):
|
||||||
|
e_id = req.args["event_id"]
|
||||||
|
event = Event.fetch_one(self.env, e_id)
|
||||||
|
session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None))
|
||||||
|
|
||||||
|
data = create_attendee_report(self.env, event, attendee, selected_tz)
|
||||||
|
data_len = len(data)
|
||||||
|
|
||||||
|
req.send_response(200)
|
||||||
|
req.send_header("Content-Type", "text/pdf;charset=utf-8")
|
||||||
|
req.send_header("Content-Length", data_len)
|
||||||
|
req.send_header("Content-Disposition", 'filename=%s.pdf' % event.name.replace("/", "_").replace(u" ", u"_"))
|
||||||
|
req.end_headers()
|
||||||
|
req.write(data)
|
||||||
|
raise RequestDone
|
||||||
|
else:
|
||||||
|
raise Exception("unhandled state")
|
||||||
|
else:
|
||||||
|
attendee = Attendee.fetch_one(self.env, nick=req.authname, e_id=req.args["event_id"], fetch_options=True)
|
||||||
|
event = Event.fetch_one(self.env, e_id=req.args["event_id"], fetch_options=True, only_active=True, attendee_id=attendee.a_id)
|
||||||
|
if event.options:
|
||||||
|
for i in event.options:
|
||||||
|
get_option_count(attendee, i)
|
||||||
|
data = {"event" : event,
|
||||||
|
"attendee" : attendee,
|
||||||
|
"now" : datetime.now(utc),
|
||||||
|
"account_data" : EventAccount.fetch_by_event(self.env, event.e_id)}
|
||||||
|
return 'booking_status.html', data, None
|
||||||
|
|
||||||
|
|
||||||
|
class UserUploadComponent(Component):
|
||||||
|
implements(IRequestHandler, IPermissionRequestor)
|
||||||
|
|
||||||
|
def match_request(self, req):
|
||||||
|
key = req.path_info
|
||||||
|
m = match(r'/upload/(\d+)$', key)
|
||||||
|
if m:
|
||||||
|
req.args['event_id'] = int(m.group(1))
|
||||||
|
return True
|
||||||
|
m = match(r'/showupload/(\d+)$', key)
|
||||||
|
if m:
|
||||||
|
req.args['attendee_id'] = int(m.group(1))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def process_request(self, req):
|
||||||
|
req.perm.require('USER_UPLOAD')
|
||||||
|
a = Attendee.fetch_one(self.env, e_id=req.args["attendee_id"], nick=req.authname)
|
||||||
|
if not a:
|
||||||
|
req.redirect(req, req.href.booking(req.args["event_id"]))
|
||||||
|
target_path = os.path.join(self.env.path, 'htdocs', 'attendees', a.ext_id)
|
||||||
|
readonly = False
|
||||||
|
if not os.path.exists(os.path.join(self.env.path, 'htdocs', 'attendees')):
|
||||||
|
os.mkdir(os.path.join(self.env.path, 'htdocs', 'attendees'))
|
||||||
|
if not os.path.exists(target_path):
|
||||||
|
os.mkdir(target_path)
|
||||||
|
if not (os.path.isdir(target_path) and os.access(target_path, os.F_OK + os.W_OK)):
|
||||||
|
readonly = True
|
||||||
|
if req.method == 'POST':
|
||||||
|
if req.args.has_key('delete'):
|
||||||
|
self._do_delete(req, target_path)
|
||||||
|
elif req.args.has_key('upload'):
|
||||||
|
self._do_upload(req, target_path)
|
||||||
|
else:
|
||||||
|
self.log.warning('Unknown POST request: %s', req.args)
|
||||||
|
req.redirect(self.env.href.upload(req.args["event_id"]))
|
||||||
|
|
||||||
|
data = {'readonly' : readonly}
|
||||||
|
self._render_view(req, data, target_path)
|
||||||
|
return 'userupload.html', data, None
|
||||||
|
|
||||||
|
def clean_userdir(self, attendee):
|
||||||
|
if not attendee:
|
||||||
|
return
|
||||||
|
target_path = os.path.join(self.env.path, 'htdocs', 'attendees', attendee.ext_id)
|
||||||
|
shutil.rmtree(target_path, True)
|
||||||
|
|
||||||
|
# IPermissionRequestor
|
||||||
|
def get_permission_actions(self):
|
||||||
|
return ['USER_UPLOAD',]
|
||||||
|
|
||||||
|
def _render_view(self, req, data, target_path):
|
||||||
|
"""Display list of files in trac env htdocs dir"""
|
||||||
|
|
||||||
|
filelist = []
|
||||||
|
if os.path.exists(target_path) and os.path.isdir(target_path):
|
||||||
|
dlist = os.listdir(target_path)
|
||||||
|
for f in dlist:
|
||||||
|
fsize = os.stat(os.path.join(target_path, f))[stat.ST_SIZE]
|
||||||
|
filelist.append({'name' : f,
|
||||||
|
'link' : Markup('<a href="%s">%s</a>') % (self.env.href.showupload(req.authname), f),
|
||||||
|
'size' : pretty_size(fsize)})
|
||||||
|
continue
|
||||||
|
data.update({'files' : filelist})
|
||||||
|
return
|
||||||
|
|
||||||
|
def _do_delete(self, req, target_path):
|
||||||
|
"""Delete a file from htdocs"""
|
||||||
|
err_list = []
|
||||||
|
sel = req.args.get('sel')
|
||||||
|
sel = isinstance(sel, list) and sel or [sel]
|
||||||
|
for key in sel:
|
||||||
|
try:
|
||||||
|
os.unlink(os.path.join(target_path, key))
|
||||||
|
except OSError:
|
||||||
|
err_list.append(key)
|
||||||
|
continue
|
||||||
|
if err_list:
|
||||||
|
errmsg = "Unable to delete the following files:\n"
|
||||||
|
errmsg += '\n'.join(err_list)
|
||||||
|
raise TracError, errmsg
|
||||||
|
|
||||||
|
def _do_upload(self, req, target_path):
|
||||||
|
"""Install a plugin."""
|
||||||
|
if not req.args.has_key('site_file'):
|
||||||
|
raise TracError('No file uploaded')
|
||||||
|
upload = req.args['site_file']
|
||||||
|
if not upload.filename:
|
||||||
|
raise TracError('No file uploaded')
|
||||||
|
upload_filename = upload.filename.replace('\\', '').replace(':', '').replace('/', '')
|
||||||
|
upload_filename = os.path.basename(upload_filename)
|
||||||
|
if not upload_filename:
|
||||||
|
raise TracError('No file uploaded')
|
||||||
|
a = Attendee.fetch_one(self.env, nick=req.authname, e_id=req.args["event_id"])
|
||||||
|
if not a:
|
||||||
|
req.redirect(req, req.href.booking(req.args["event_id"]))
|
||||||
|
file_path = os.path.join(target_path, upload_filename)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
raise TracError('A file/directory >>%s<< already exists' % upload_filename)
|
||||||
|
self.log.info('Installing plugin %s', upload_filename)
|
||||||
|
flags = os.O_CREAT + os.O_WRONLY + os.O_EXCL
|
||||||
|
try:
|
||||||
|
flags += os.O_BINARY
|
||||||
|
except AttributeError:
|
||||||
|
# OS_BINARY not available on every platform
|
||||||
|
pass
|
||||||
|
target_file = os.fdopen(os.open(file_path, flags), 'w')
|
||||||
|
try:
|
||||||
|
shutil.copyfileobj(upload.file, target_file)
|
||||||
|
self.log.info('File %s uploaded to %s', upload_filename,
|
||||||
|
target_path)
|
||||||
|
finally:
|
||||||
|
target_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
class UploadComponent(Component):
|
||||||
|
implements(IRequestHandler, IPermissionRequestor)
|
||||||
|
|
||||||
|
def match_request(self, req):
|
||||||
|
key = req.path_info
|
||||||
|
if key in ("/upload", "/showupload"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def process_request(self, req):
|
||||||
|
req.perm.require('USER_UPLOAD')
|
||||||
|
target_path = os.path.join(self.env.path, 'htdocs', "data")
|
||||||
|
readonly = False
|
||||||
|
if not os.path.exists(target_path):
|
||||||
|
os.mkdir(target_path)
|
||||||
|
if not (os.path.isdir(target_path) and os.access(target_path, os.F_OK + os.W_OK)):
|
||||||
|
readonly = True
|
||||||
|
if req.method == 'POST':
|
||||||
|
if req.args.has_key('delete'):
|
||||||
|
self._do_delete(req, target_path)
|
||||||
|
elif req.args.has_key('upload'):
|
||||||
|
self._do_upload(req, target_path)
|
||||||
|
else:
|
||||||
|
self.log.warning('Unknown POST request: %s', req.args)
|
||||||
|
req.redirect(self.env.href.upload())
|
||||||
|
|
||||||
|
data = {'readonly' : readonly}
|
||||||
|
self._render_view(req, data, target_path)
|
||||||
|
return 'userupload.html', data, None
|
||||||
|
|
||||||
|
def clean_userdir(self, attendee):
|
||||||
|
if not attendee:
|
||||||
|
return
|
||||||
|
target_path = os.path.join(self.env.path, 'htdocs', 'attendees', attendee.ext_id)
|
||||||
|
shutil.rmtree(target_path, True)
|
||||||
|
|
||||||
|
# IPermissionRequestor
|
||||||
|
def get_permission_actions(self):
|
||||||
|
return ['USER_UPLOAD',]
|
||||||
|
|
||||||
|
def _render_view(self, req, data, target_path):
|
||||||
|
"""Display list of files in trac env htdocs dir"""
|
||||||
|
|
||||||
|
filelist = []
|
||||||
|
if os.path.exists(target_path) and os.path.isdir(target_path):
|
||||||
|
dlist = os.listdir(target_path)
|
||||||
|
for f in dlist:
|
||||||
|
fsize = os.stat(os.path.join(target_path, f))[stat.ST_SIZE]
|
||||||
|
filelist.append({'name' : f,
|
||||||
|
'link' : Markup('<a href="%s">%s</a>') % (self.env.href.showupload(req.authname), f),
|
||||||
|
'size' : pretty_size(fsize)})
|
||||||
|
continue
|
||||||
|
data.update({'files' : filelist})
|
||||||
|
return
|
||||||
|
|
||||||
|
def _do_delete(self, req, target_path):
|
||||||
|
"""Delete a file from htdocs"""
|
||||||
|
err_list = []
|
||||||
|
sel = req.args.get('sel')
|
||||||
|
sel = isinstance(sel, list) and sel or [sel]
|
||||||
|
for key in sel:
|
||||||
|
try:
|
||||||
|
os.unlink(os.path.join(target_path, key))
|
||||||
|
except OSError:
|
||||||
|
err_list.append(key)
|
||||||
|
continue
|
||||||
|
if err_list:
|
||||||
|
errmsg = "Unable to delete the following files:\n"
|
||||||
|
errmsg += '\n'.join(err_list)
|
||||||
|
raise TracError, errmsg
|
||||||
|
|
||||||
|
def _do_upload(self, req, target_path):
|
||||||
|
"""Install a plugin."""
|
||||||
|
if not req.args.has_key('site_file'):
|
||||||
|
raise TracError('No file uploaded')
|
||||||
|
upload = req.args['site_file']
|
||||||
|
if not upload.filename:
|
||||||
|
raise TracError('No file uploaded')
|
||||||
|
upload_filename = upload.filename.replace('\\', '').replace(':', '').replace('/', '')
|
||||||
|
upload_filename = os.path.basename(upload_filename)
|
||||||
|
if not upload_filename:
|
||||||
|
raise TracError('No file uploaded')
|
||||||
|
a = Attendee.fetch_one(self.env, nick=req.authname, e_id=req.args["event_id"])
|
||||||
|
if not a:
|
||||||
|
req.redirect(req, req.href.booking(req.args["event_id"]))
|
||||||
|
file_path = os.path.join(target_path, upload_filename)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
raise TracError('A file/directory >>%s<< already exists' % upload_filename)
|
||||||
|
self.log.info('Installing plugin %s', upload_filename)
|
||||||
|
flags = os.O_CREAT + os.O_WRONLY + os.O_EXCL
|
||||||
|
try:
|
||||||
|
flags += os.O_BINARY
|
||||||
|
except AttributeError:
|
||||||
|
# OS_BINARY not available on every platform
|
||||||
|
pass
|
||||||
|
target_file = os.fdopen(os.open(file_path, flags), 'w')
|
||||||
|
try:
|
||||||
|
shutil.copyfileobj(upload.file, target_file)
|
||||||
|
self.log.info('File %s uploaded to %s', upload_filename,
|
||||||
|
target_path)
|
||||||
|
finally:
|
||||||
|
target_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
#class TracSchedulerTest(Component):
|
||||||
|
#implements( IScheduledTask)
|
||||||
|
#def process_scheduled_task(self, parent):
|
||||||
|
#sqlString = "SELECT edit_deadline FROM booking_event;"
|
||||||
|
#rows = parent.queryDb(sqlString)
|
||||||
|
#n = datetime.now(utc)
|
||||||
|
#for i in rows:
|
||||||
|
#d = datetime.fromtimestamp(i[0], utc)
|
||||||
|
#dt = d - n
|
||||||
|
#if dt < timedelta(0,3600):
|
||||||
|
#parent.queryDb("UPDATE booking_available_option SET active=0 where ao_id in (1,2,3,7);", commit=True)
|
||||||
|
#parent.queryDb("UPDATE booking_available_option SET active=1 where ao_id in (4,5,6,8);", commit=True)
|
Loading…
Reference in New Issue