ctdo-trac/TracBooking/tracbooking/web_ui.py

510 lines
22 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, utc.localize(datetime.utcnow()), 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" : utc.localize(datetime.utcnow())}
#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, utc.localize(datetime.utcnow()), 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()
session_tzname, selected_tz = get_tz(req.session.get("tz", self.env.config.get("trac", "default_timezone") or None))
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,
"selected_tz" : selected_tz,
"now" : utc.localize(datetime.utcnow()),
"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 = utc.localize(datetime.utcnow())
#for i in rows:
#d = datetime.utcfromtimestamp(i[0])
#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)