import base64 import datetime import urllib2 try: import json except ImportError: import simplejson as json import time import vobject from wcs.qommon import _ from wcs.qommon import get_cfg from wcs.qommon.misc import format_time from wcs.qommon.form import * from wcs.data_sources import register_data_source_function from wcs.formdata import Evolution from wcs.forms.common import FormStatusPage from wcs.workflows import Workflow, WorkflowStatusItem, register_item_class def get_clicrdv_req(url): misc_cfg = get_cfg('misc', {}) url = 'https://%s/api/v1/%s' % ( misc_cfg.get('aq-clicrdv-server', 'sandbox.clicrdv.com'), url) if '?' in url: url = url + '&apikey=%s&format=json' % misc_cfg.get('aq-clicrdv-api-key') else: url = url + '?apikey=%s&format=json' % misc_cfg.get('aq-clicrdv-api-key') req = urllib2.Request(url) username = misc_cfg.get('aq-clicrdv-api-username') password = misc_cfg.get('aq-clicrdv-api-password') authheader = 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1] req.add_header('Authorization', authheader) return req def get_json(url): return json.load(urllib2.urlopen(get_clicrdv_req(url))) def as_str(s): if type(s) is unicode: return s.encode(get_publisher().site_charset) return s def get_all_intervention_sets(): interventions_set = [] for interventionset in sorted(get_json('interventionsets').get('records'), lambda x,y: cmp(x['sort'],y['sort'])): interventions = [] for intervention in sorted(get_json('interventionsets/%s/interventions' % interventionset.get('id')).get('records'), lambda x,y: cmp(x['sort'], y['sort'])): if intervention.get('deleted') == True: continue name = '%s' % as_str(intervention.get('publicname')) if not name: name = '%s' % as_str(intervention.get('name')) interventions.append((intervention.get('id'), as_str(name))) interventions_set.append({ 'id': interventionset.get('id'), 'group_id': interventionset.get('group_id'), 'name': as_str(interventionset.get('name')), 'publicname': as_str(interventionset.get('publicname')) or '', 'description': as_str(interventionset.get('description')) or '', 'interventions': interventions }) return interventions_set def get_all_interventions(): interventions = [] for s in get_all_intervention_sets(): for i, publicname in s['interventions']: intervention_label = '%s - %s' % (s['publicname'], publicname) interventions.append((i, as_str(intervention_label))) return interventions def get_interventions_in_set(interventionset_id): interventions = [] interventions_json = get_json('interventionsets/%s/interventions' % interventionset_id) for intervention in interventions_json.get('records'): if intervention.get('deleted') != True: name = '%s' % as_str(intervention.get('publicname')) if not name: name = '%s' % as_str(intervention.get('name')) interventions.append((intervention.get('id'), name)) return interventions def get_available_timeslots(intervention, date_start=None, date_end=None): timeslots = [] iid = intervention gid = get_json('interventions/%s' % iid).get('group_id') request_url = 'availabletimeslots?intervention_ids[]=%s&group_id=%s' % (iid, gid) if date_start is None: date_start = datetime.datetime.today().strftime('%Y-%m-%d') if date_end is None: date_end = (datetime.datetime.today() + datetime.timedelta(366)).strftime('%Y-%m-%d') if date_start: request_url = request_url + '&start=%s' % urllib2.quote(date_start) if date_end: request_url = request_url + '&end=%s' % urllib2.quote(date_end) for timeslot in get_json(request_url).get('availabletimeslots'): timeslots.append(timeslot.get('start')) timeslots.sort() return timeslots def get_available_dates(intervention): dates = [] for timeslot in get_available_timeslots(intervention): parsed = time.strptime(timeslot, '%Y-%m-%d %H:%M:%S') date_tuple = (time.strftime('%Y-%m-%d', parsed), format_time(parsed, '%(weekday_name)s %(day)0.2d/%(month)0.2d/%(year)s')) if date_tuple in dates: continue dates.append(date_tuple) return dates def get_available_times(intervention, date): times = [] timeslots = get_available_timeslots(intervention, date_start='%s 00:00:00' % date, date_end='%s 23:59:59' % date) for timeslot in timeslots: parsed = time.strptime(timeslot, '%Y-%m-%d %H:%M:%S') time_tuple = (time.strftime('%H:%M:%S', parsed), time.strftime('%Hh%M', parsed)) times.append(time_tuple) times.sort() return times register_data_source_function(get_all_interventions, 'clicrdv_get_all_interventions') register_data_source_function(get_interventions_in_set, 'clicrdv_get_interventions_in_set') register_data_source_function(get_available_dates, 'clicrdv_get_available_dates') register_data_source_function(get_available_times, 'clicrdv_get_available_times') def form_download_event(self): self.check_receiver() found = False for evo in self.filled.evolution: if evo.parts: for p in evo.parts: if not isinstance(p, AppointmentPart): continue cal = vobject.iCalendar() cal.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik' vevent = vobject.newFromBehavior('vevent') vevent.add('uid').value = 'clicrdv-%s' % p.id vevent.add('summary').value = p.json_dict.get('group_name') vevent.add('dtstart').value = datetime.datetime.strptime( p.json_dict.get('start'), '%Y-%m-%d %H:%M:%S') vevent.add('dtend').value = datetime.datetime.strptime( p.json_dict.get('end'), '%Y-%m-%d %H:%M:%S') vevent.add('location').value = p.json_dict.get('location') cal.add(vevent) response = get_response() response.set_content_type('text/calendar') return cal.serialize() raise TraversalError() class AppointmentPart(object): def __init__(self, json_dict): self.id = json_dict.get('id') self.json_dict = json_dict def view(self): return htmltext('

%s

' % ( _('Download Appointment'))) class AppointmentErrorPart(object): def __init__(self, msg): self.msg = msg def view(self): return htmltext('

%s

' % str(self.msg)) class ClicRdvCreateAppointment(WorkflowStatusItem): description = N_('Create a ClicRDV Appointment') key = 'clicrdv-create' category = 'interaction' endpoint = False var_firstname = None var_lastname = None var_email = None var_firstphone = None var_secondphone = None var_datetime = None var_intervention_id = None status_on_success = None status_on_failure = None def init(cls): FormStatusPage._q_extra_exports.append('clicrdvevent') FormStatusPage.clicrdvevent = form_download_event init = classmethod(init) def is_available(self, workflow=None): return get_publisher().has_site_option('clicrdv') is_available = classmethod(is_available) def render_as_line(self): return _('Create an appointment in ClicRDV') def get_parameters(self): return ('var_firstname', 'var_lastname', 'var_email', 'var_firstphone', 'var_secondphone', 'var_datetime', 'var_intervention_id', 'status_on_success', 'status_on_failure') def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): parameter_labels = { 'var_firstname': N_('First Name'), 'var_lastname': N_('Last Name'), 'var_email': N_('Email'), 'var_firstphone': N_('Phone (1st)'), 'var_secondphone': N_('Phone (2nd)'), 'var_datetime': N_('Date/time'), 'var_intervention_id': N_('Intervention Id'), } for parameter in self.get_parameters(): if not parameter in parameter_labels: continue if parameter in parameters: form.add(StringWidget, '%s%s' % (prefix, parameter), title=_(parameter_labels.get(parameter)), value=getattr(self, parameter), required=False) if 'status_on_success' in parameters: form.add(SingleSelectWidget, '%sstatus_on_success' % prefix, title=_('Status On Success'), value=self.status_on_success, options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status]) if 'status_on_failure' in parameters: form.add(SingleSelectWidget, '%sstatus_on_failure' % prefix, title=_('Status On Failure'), value=self.status_on_failure, options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status]) def perform(self, formdata): args = {} for parameter in self.get_parameters(): args[parameter] = self.compute(getattr(self, parameter)) if not args.get(parameter): del args[parameter] message = {'appointment': {'fiche': {'firstname': args.get('var_firstname', '-'), 'lastname': args.get('var_lastname', '-'), 'email': args.get('var_email'), 'firstphone': args.get('var_firstphone'), 'secondphone': args.get('var_secondphone'), }, 'date': args.get('var_datetime'), 'intervention_ids': [int(args.get('var_intervention_id'))], # 'comments': '-', 'websource': 'Publik'} } req = get_clicrdv_req('appointments') req.add_data(json.dumps(message)) req.add_header('Content-Type', 'application/json') try: fd = urllib2.urlopen(req) except urllib2.HTTPError, e: success = False try: msg = json.load(e.fp)[0].get('error') except: msg = _('unknown error') if formdata.evolution: evo = formdata.evolution[-1] else: formdata.evolution = [] evo = Evolution() evo.time = time.localtime() evo.status = formdata.status formdata.evolution.append(evo) evo.add_part(AppointmentErrorPart(msg)) else: success = True response = json.load(fd) appointment_id = response.get('records')[0].get('id') # add a message in formdata.evolution if formdata.evolution: evo = formdata.evolution[-1] else: formdata.evolution = [] evo = Evolution() evo.time = time.localtime() evo.status = formdata.status formdata.evolution.append(evo) evo.add_part(AppointmentPart(response.get('records')[0])) formdata.store() if (success and self.status_on_success) or (success is False and self.status_on_failure): if success: formdata.status = 'wf-%s' % self.status_on_success else: formdata.status = 'wf-%s' % self.status_on_failure register_item_class(ClicRdvCreateAppointment) class ClicRdvCancelAppointment(WorkflowStatusItem): description = N_('Cancel a ClicRDV Appointment') key = 'clicrdv-cancel' category = 'interaction' endpoint = False status_on_success = None status_on_failure = None def get_parameters(self): return ('status_on_success', 'status_on_failure') def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): if 'status_on_success' in parameters: form.add(SingleSelectWidget, '%sstatus_on_success' % prefix, title=_('Status On Success'), value=self.status_on_success, options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status]) if 'status_on_failure' in parameters: form.add(SingleSelectWidget, '%sstatus_on_failure' % prefix, title=_('Status On Failure'), value=self.status_on_failure, options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status]) def is_available(self, workflow=None): return get_publisher().has_site_option('clicrdv') is_available = classmethod(is_available) def render_as_line(self): return _('Cancel an appointment in ClicRDV') def perform(self, formdata): success = True for evo in [evo for evo in formdata.evolution if evo.parts]: for part in [part for part in evo.parts if isinstance(part, AppointmentPart)]: appointment_id = part.id try: req = get_clicrdv_req('appointments/%s' % appointment_id) req.get_method = (lambda: 'DELETE') fd = urllib2.urlopen(req) none = fd.read() except urllib2.URLError: # clicrdv will return a "Bad Request" (HTTP 400) response # when it's not possible to remove an appointment # (for example because it's too late) success = False if (success and self.status_on_success) or (success is False and self.status_on_failure): if success: formdata.status = 'wf-%s' % self.status_on_success else: formdata.status = 'wf-%s' % self.status_on_failure register_item_class(ClicRdvCancelAppointment)