# -*- 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('%s') % (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('%s') % (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)