508 lines
21 KiB
Python
508 lines
21 KiB
Python
|
# -*- 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)
|