Project

General

Profile

Download (14.1 KB) Statistics
| Branch: | Tag: | Revision:

root / extra / modules / clicrdv.py @ 3c1248c0

1
import base64
2
import datetime
3
import urllib2
4

    
5
try:
6
    import json
7
except ImportError:
8
    import simplejson as json
9

    
10
import time
11
import vobject
12

    
13
from qommon import get_cfg
14
from qommon.misc import format_time
15
from qommon.form import *
16

    
17
from wcs.data_sources import register_data_source_function
18
from wcs.formdata import Evolution
19
from wcs.forms.common import FormStatusPage
20
from wcs.workflows import Workflow, WorkflowStatusItem, register_item_class
21

    
22
def get_clicrdv_req(url):
23
    misc_cfg = get_cfg('misc', {})
24

    
25
    url = 'https://%s/api/v1/%s' % (
26
                misc_cfg.get('aq-clicrdv-server', 'sandbox.clicrdv.com'), url)
27
    if '?' in url:
28
       url = url + '&apikey=%s&format=json' % misc_cfg.get('aq-clicrdv-api-key')
29
    else:
30
       url = url + '?apikey=%s&format=json' % misc_cfg.get('aq-clicrdv-api-key')
31

    
32
    req = urllib2.Request(url)
33
    username = misc_cfg.get('aq-clicrdv-api-username')
34
    password = misc_cfg.get('aq-clicrdv-api-password')
35
    authheader = 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1]
36
    req.add_header('Authorization', authheader)
37
    return req
38

    
39
def get_json(url):
40
    return json.load(urllib2.urlopen(get_clicrdv_req(url)))
41

    
42
def as_str(s):
43
    if type(s) is unicode:
44
        return s.encode(get_publisher().site_charset)
45
    return s
46

    
47
def get_all_intervention_sets():
48
    interventions_set = []
49
    for interventionset in sorted(get_json('interventionsets').get('records'), 
50
            lambda x,y: cmp(x['sort'],y['sort'])):
51
        interventions = []
52
        for intervention in sorted(get_json('interventionsets/%s/interventions' % interventionset.get('id')).get('records'),
53
                lambda x,y: cmp(x['sort'], y['sort'])):
54
            if intervention.get('deleted') == True:
55
                continue
56
            name = '%s' % as_str(intervention.get('publicname'))
57
            if not name:
58
                name = '%s' % as_str(intervention.get('name'))
59
            interventions.append((intervention.get('id'), as_str(name)))
60
        interventions_set.append({
61
                'id': interventionset.get('id'),
62
                'group_id': interventionset.get('group_id'),
63
                'name': as_str(interventionset.get('name')),
64
                'publicname': as_str(interventionset.get('publicname')) or '',
65
                'description': as_str(interventionset.get('description')) or '',
66
                'interventions': interventions
67
                })
68
    return interventions_set
69

    
70
def get_all_interventions():
71
    interventions = []
72
    for s in get_all_intervention_sets():
73
        for i, publicname in s['interventions']:
74
            intervention_label = '%s - %s' % (s['publicname'], publicname)
75
            interventions.append((i, as_str(intervention_label)))
76
    return interventions
77

    
78
def get_interventions_in_set(interventionset_id):
79
    interventions = []
80
    interventions_json = get_json('interventionsets/%s/interventions' % interventionset_id)
81
    for intervention in interventions_json.get('records'):
82
        if intervention.get('deleted') != True:
83
            name = '%s' % as_str(intervention.get('publicname'))
84
            if not name:
85
                name = '%s' % as_str(intervention.get('name'))
86
            interventions.append((intervention.get('id'), name))
87
    return interventions
88

    
89
def get_available_timeslots(intervention, date_start=None, date_end=None):
90
    timeslots = []
91
    iid = intervention
92
    gid = get_json('interventions/%s' % iid).get('group_id')
93
    request_url = 'availabletimeslots?intervention_ids[]=%s&group_id=%s' % (iid, gid)
94
    if date_start is None:
95
        date_start = datetime.datetime.today().strftime('%Y-%m-%d')
96
    if date_end is None:
97
        date_end = (datetime.datetime.today() + datetime.timedelta(366)).strftime('%Y-%m-%d')
98
    if date_start:
99
        request_url = request_url + '&start=%s' % urllib2.quote(date_start)
100
    if date_end:
101
        request_url = request_url + '&end=%s' % urllib2.quote(date_end)
102
    for timeslot in get_json(request_url).get('availabletimeslots'):
103
        timeslots.append(timeslot.get('start'))
104
    timeslots.sort()
105
    return timeslots
106

    
107
def get_available_dates(intervention):
108
    dates = []
109
    for timeslot in get_available_timeslots(intervention):
110
        parsed = time.strptime(timeslot, '%Y-%m-%d %H:%M:%S')
111
        date_tuple = (time.strftime('%Y-%m-%d', parsed),
112
                      format_time(parsed, '%(weekday_name)s %(day)0.2d/%(month)0.2d/%(year)s'))
113
        if date_tuple in dates:
114
            continue
115
        dates.append(date_tuple)
116
    return dates
117

    
118
def get_available_times(intervention, date):
119
    times = []
120
    timeslots = get_available_timeslots(intervention,
121
                    date_start='%s 00:00:00' % date,
122
                    date_end='%s 23:59:59' % date)
123
    for timeslot in timeslots:
124
        parsed = time.strptime(timeslot, '%Y-%m-%d %H:%M:%S')
125
        time_tuple = (time.strftime('%H:%M:%S', parsed),
126
                      time.strftime('%Hh%M', parsed))
127
        times.append(time_tuple)
128
    times.sort()
129
    return times
130

    
131
register_data_source_function(get_all_interventions, 'clicrdv_get_all_interventions')
132
register_data_source_function(get_interventions_in_set, 'clicrdv_get_interventions_in_set')
133
register_data_source_function(get_available_dates, 'clicrdv_get_available_dates')
134
register_data_source_function(get_available_times, 'clicrdv_get_available_times')
135

    
136
def form_download_event(self):
137
    self.check_receiver()
138

    
139
    found = False
140
    for evo in self.filled.evolution:
141
        if evo.parts:
142
            for p in evo.parts:
143
                if not isinstance(p, AppointmentPart):
144
                    continue
145
                cal = vobject.iCalendar()
146
                cal.add('prodid').value = '-//Entr\'ouvert//NON SGML Au Quotidien'
147
                vevent = vobject.newFromBehavior('vevent')
148
                vevent.add('uid').value = 'clicrdv-%s' % p.id
149
                vevent.add('summary').value = p.json_dict.get('group_name')
150
                vevent.add('dtstart').value = datetime.datetime.strptime(
151
                                p.json_dict.get('start'), '%Y-%m-%d %H:%M:%S')
152
                vevent.add('dtend').value = datetime.datetime.strptime(
153
                                p.json_dict.get('end'), '%Y-%m-%d %H:%M:%S')
154
                vevent.add('location').value = p.json_dict.get('location')
155
                cal.add(vevent)
156

    
157
                response = get_response()
158
                response.set_content_type('text/calendar')
159
                return cal.serialize()
160

    
161
    raise TraversalError()
162

    
163

    
164
class AppointmentPart(object):
165
    def __init__(self, json_dict):
166
        self.id = json_dict.get('id')
167
        self.json_dict = json_dict
168

    
169
    def view(self):
170
        return htmltext('<p class="appointment"><a href="clicrdvevent">%s</a></p>' % (
171
                                _('Download Appointment')))
172

    
173

    
174
class AppointmentErrorPart(object):
175
    def __init__(self, msg):
176
        self.msg = msg
177

    
178
    def view(self):
179
        return htmltext('<p class="appointment-error">%s</p>' % str(self.msg))
180

    
181

    
182
class ClicRdvCreateAppointment(WorkflowStatusItem):
183
    description = N_('Create a ClicRDV Appointment')
184
    key = 'clicrdv-create'
185
    endpoint = False
186

    
187
    var_firstname = None
188
    var_lastname = None
189
    var_email = None
190
    var_firstphone = None
191
    var_secondphone = None
192
    var_datetime = None
193
    var_intervention_id = None
194
    status_on_success = None
195
    status_on_failure = None
196

    
197
    def init(cls):
198
        FormStatusPage._q_extra_exports.append('clicrdvevent')
199
        FormStatusPage.clicrdvevent = form_download_event
200
    init = classmethod(init)
201

    
202
    def is_available(self):
203
        return get_publisher().has_site_option('clicrdv')
204
    is_available = classmethod(is_available)
205

    
206
    def render_as_line(self):
207
        return _('Create an appointment in ClicRDV')
208

    
209
    def get_parameters(self):
210
        return ('var_firstname', 'var_lastname', 'var_email', 'var_firstphone',
211
                'var_secondphone', 'var_datetime', 'var_intervention_id',
212
                'status_on_success', 'status_on_failure')
213

    
214
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
215
        parameter_labels = {
216
            'var_firstname': N_('First Name'),
217
            'var_lastname': N_('Last Name'),
218
            'var_email': N_('Email'),
219
            'var_firstphone': N_('Phone (1st)'),
220
            'var_secondphone':  N_('Phone (2nd)'),
221
            'var_datetime': N_('Date/time'),
222
            'var_intervention_id': N_('Intervention Id'),
223
        }
224
        for parameter in self.get_parameters():
225
            if not parameter in parameter_labels:
226
                continue
227
            if parameter in parameters:
228
                form.add(StringWidget, '%s%s' % (prefix, parameter),
229
                         title=_(parameter_labels.get(parameter)),
230
                         value=getattr(self, parameter),
231
                         required=False)
232
        if 'status_on_success' in parameters:
233
            form.add(SingleSelectWidget, '%sstatus_on_success' % prefix,
234
                    title=_('Status On Success'), value=self.status_on_success,
235
                    options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
236
        if 'status_on_failure' in parameters:
237
            form.add(SingleSelectWidget, '%sstatus_on_failure' % prefix,
238
                    title=_('Status On Failure'), value=self.status_on_failure,
239
                    options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
240

    
241
    def perform(self, formdata):
242
        args = {}
243
        for parameter in self.get_parameters():
244
            args[parameter] = self.compute(getattr(self, parameter))
245
            if not args.get(parameter):
246
                del args[parameter]
247
        message = {'appointment':
248
                      {'fiche': {'firstname': args.get('var_firstname', '-'),
249
                                 'lastname': args.get('var_lastname', '-'),
250
                                 'email': args.get('var_email'),
251
                                 'firstphone': args.get('var_firstphone'),
252
                                 'secondphone': args.get('var_secondphone'),
253
                                },
254
                       'date': args.get('var_datetime'),
255
                       'intervention_ids': [int(args.get('var_intervention_id'))],
256
                       # 'comments': '-',
257
                       'websource': 'Au Quotidien'}
258
                  }
259

    
260
        req = get_clicrdv_req('appointments')
261
        req.add_data(json.dumps(message))
262
        req.add_header('Content-Type', 'application/json')
263

    
264
        try:
265
            fd = urllib2.urlopen(req)
266
        except urllib2.HTTPError, e:
267
            success = False
268
            try:
269
                msg = json.load(e.fp)[0].get('error')
270
            except:
271
                msg = _('unknown error')
272

    
273
            if formdata.evolution:
274
                evo = formdata.evolution[-1]
275
            else:
276
                formdata.evolution = []
277
                evo = Evolution()
278
                evo.time = time.localtime()
279
                evo.status = formdata.status
280
                formdata.evolution.append(evo)
281
            evo.add_part(AppointmentErrorPart(msg))
282
        else:
283
            success = True
284
            response = json.load(fd)
285
            appointment_id = response.get('records')[0].get('id')
286

    
287
            # add a message in formdata.evolution
288
            if formdata.evolution:
289
                evo = formdata.evolution[-1]
290
            else:
291
                formdata.evolution = []
292
                evo = Evolution()
293
                evo.time = time.localtime()
294
                evo.status = formdata.status
295
                formdata.evolution.append(evo)
296
            evo.add_part(AppointmentPart(response.get('records')[0]))
297

    
298
        formdata.store()
299

    
300
        if (success and self.status_on_success) or (success is False and self.status_on_failure):
301
            if success:
302
                formdata.status = 'wf-%s' % self.status_on_success
303
            else:
304
                formdata.status = 'wf-%s' % self.status_on_failure
305

    
306
register_item_class(ClicRdvCreateAppointment)
307

    
308

    
309
class ClicRdvCancelAppointment(WorkflowStatusItem):
310
    description = N_('Cancel a ClicRDV Appointment')
311
    key = 'clicrdv-cancel'
312
    endpoint = False
313

    
314
    status_on_success = None
315
    status_on_failure = None
316

    
317
    def get_parameters(self):
318
        return ('status_on_success', 'status_on_failure')
319

    
320
    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
321
        if 'status_on_success' in parameters:
322
            form.add(SingleSelectWidget, '%sstatus_on_success' % prefix,
323
                    title=_('Status On Success'), value=self.status_on_success,
324
                    options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
325
        if 'status_on_failure' in parameters:
326
            form.add(SingleSelectWidget, '%sstatus_on_failure' % prefix,
327
                    title=_('Status On Failure'), value=self.status_on_failure,
328
                    options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
329

    
330
    def is_available(self):
331
        return get_publisher().has_site_option('clicrdv')
332
    is_available = classmethod(is_available)
333

    
334
    def render_as_line(self):
335
        return _('Cancel an appointment in ClicRDV')
336

    
337
    def perform(self, formdata):
338
        success = True
339

    
340
        for evo in [evo for evo in formdata.evolution if evo.parts]:
341
            for part in [part for part in evo.parts if isinstance(part, AppointmentPart)]:
342
                appointment_id = part.id
343
                try:
344
                    req = get_clicrdv_req('appointments/%s' % appointment_id)
345
                    req.get_method = (lambda: 'DELETE')
346
                    fd = urllib2.urlopen(req)
347
                    none = fd.read()
348
                except urllib2.URLError:
349
                    # clicrdv will return a "Bad Request" (HTTP 400) response
350
                    # when it's not possible to remove an appointment
351
                    # (for example because it's too late)
352
                    success = False
353

    
354
        if (success and self.status_on_success) or (success is False and self.status_on_failure):
355
            if success:
356
                formdata.status = 'wf-%s' % self.status_on_success
357
            else:
358
                formdata.status = 'wf-%s' % self.status_on_failure
359

    
360
register_item_class(ClicRdvCancelAppointment)
(13-13/32)