passerelle/passerelle/apps/vivaticket/models.py

292 lines
10 KiB
Python

# Copyright (C) 2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import hashlib
from urllib import parse as urlparse
from django.core.cache import cache
from django.db import models
from django.utils.translation import gettext_lazy as _
from passerelle.base.models import BaseResource
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
EVENTBOOK_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Vivaticket',
'description': '',
'type': 'object',
'required': [
'id',
'email',
'start_datetime',
'end_datetime',
'event',
'theme',
'room',
'quantity',
'form_url',
],
'properties': {
'id': {
'description': 'formdata id',
'type': 'string',
},
'title': {
'description': 'user title',
'type': 'string',
},
'last_name': {
'description': 'user last name',
'type': 'string',
},
'first_name': {
'description': 'user first name',
'type': 'string',
},
'social_reason': {
'description': 'user social reason',
'type': 'string',
},
'address': {
'description': 'user address',
'type': 'string',
},
'zipcode': {
'description': 'user zipcode',
'type': 'string',
},
'city': {
'description': 'user city',
'type': 'string',
},
'country': {
'description': 'user country',
'type': 'string',
},
'phone': {
'description': 'user phone',
'type': 'string',
},
'mobile': {
'description': 'user mobile',
'type': 'string',
},
'email': {
'description': 'user email',
'type': 'string',
},
'start_datetime': {
'description': 'event start datetime',
'type': 'string',
'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}$',
},
'end_datetime': {
'description': 'event end datetime',
'type': 'string',
'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}$',
},
'event': {
'description': 'event id',
'type': 'string',
},
'theme': {
'description': 'theme id',
'type': 'string',
},
'room': {
'description': 'room id',
'type': 'string',
},
'quantity': {'description': 'quantity', 'type': 'string', 'pattern': '^[0-9]+$'},
'booking_comment': {
'description': 'booking comment',
'type': 'string',
},
'room_comment': {
'description': 'room comment',
'type': 'string',
},
'form_url': {
'description': 'form url',
'type': 'string',
},
'school_level': {
'description': 'School Level code',
'type': 'string',
},
},
}
class VivaTicket(BaseResource):
url = models.URLField(_('API URL'))
login = models.CharField(_('API Login'), max_length=256)
password = models.CharField(_('API Password'), max_length=256)
category = _('Business Process Connectors')
log_requests_errors = False
class Meta:
verbose_name = 'VivaTicket'
@classmethod
def get_verbose_name(cls):
return cls._meta.verbose_name
def check_status(self):
response = self.requests.get(urlparse.urljoin(self.url, 'Settings/GetVersion'))
response.raise_for_status()
def get_apikey(self, renew=False):
cache_key_name = 'vivaticket-%s-key' % self.id
if not renew and cache.get(cache_key_name):
return cache.get(cache_key_name)
url = urlparse.urljoin(self.url, 'Connect/PostConnect')
payload = {'Login': self.login, 'Password': self.password}
response = self.requests.post(url, json=payload)
if not response.ok:
raise APIError(response.content)
api_key = response.json()['Key']
# api key is available for 30 minutes
cache.set(cache_key_name, api_key, 60 * 30)
return api_key
def get(self, endpoint, **kwargs):
url = urlparse.urljoin(self.url, endpoint)
params = {'key': self.get_apikey()}
params.update(kwargs)
response = self.requests.get(url, params=params)
# api key is expired
if response.status_code == 401:
params['key'] = self.get_apikey(True)
else:
return response
return self.requests.get(url, params=params)
def post(self, endpoint, payload, headers=None):
url = urlparse.urljoin(self.url, endpoint)
payload.update({'Key': self.get_apikey()})
response = self.requests.post(url, json=payload, headers=headers)
# api key is expired
if response.status_code == 401:
payload['key'] = self.get_apikey(True)
return self.requests.post(url, json=payload, headers=headers)
return response
def get_list_of_settings(self, endpoint, **kwargs):
response = self.get(endpoint, **kwargs)
json = response.json()
data = []
for setting in json.get('ListOfSettings', []):
data.append({'id': setting['Code'], 'text': setting['Label']})
return {'data': data}
@endpoint(methods=['get'], description=_('Get event categories'))
def events(self, request):
return self.get_list_of_settings('Settings/GetEventCategory')
@endpoint(methods=['get'], description=_('Get rooms'))
def rooms(self, request, event=None):
query = {}
if event is not None:
query['eventCategory'] = event
return self.get_list_of_settings('Settings/GetRooms', **query)
@endpoint(methods=['get'], description=_('Get themes'))
def themes(self, request, room=None):
query = {}
if room is not None:
query['room'] = room
return self.get_list_of_settings('Settings/GetThemes', **query)
@endpoint(name='school-levels', methods=['get'], description=_('Get school levels'))
def school_levels(self, request):
return self.get_list_of_settings('Settings/GetSchoolLevel')
def get_or_create_contact(self, data, name_id=None):
contact_payload = {
'Civility': data.get('title', ''),
'LastName': data.get('last_name', ''),
'FirstName': data.get('first_name', ''),
'SocialReason': data.get('social_reason', ''),
'Address1': data.get('address', ''),
'ZipCode': data.get('zipcode', ''),
'City': data.get('city', ''),
'Country': data.get('country', ''),
'Email': data['email'],
'Phone': data.get('phone', ''),
'Mobile': data.get('mobile', ''),
}
if name_id is not None:
unhashed_external_code = name_id
else:
unhashed_external_code = data['email']
external_code = hashlib.md5(unhashed_external_code.encode('utf-8')).hexdigest()[:20]
contact_payload['ExternalCode'] = external_code
response = self.get('Contact/Get', externalCode=external_code, email=data['email'])
self.logger.debug('Got contact response: %r', response.text)
if not response.ok:
response = self.post('Contact/Post', {'Contact': contact_payload})
self.logger.debug('Contact creation response: %r', response.text)
response.raise_for_status()
internal_code = response.json()['InternalCode']
else:
internal_code = response.json()['InternalCode']
# update contact data
url = urlparse.urljoin(self.url, 'Contact/Put')
response = self.requests.put(
url,
params={'id': response.json()['InternalCode']},
json={'Key': self.get_apikey(), 'Contact': contact_payload},
)
return {'InternalCode': internal_code}
@endpoint(
description=_('Book an event'),
post={
'description': _('Creates a booking for an event'),
'request_body': {'schema': {'application/json': EVENTBOOK_SCHEMA}},
},
)
def book(self, request, post_data, nameid=None):
booking = {
'externalCode': post_data['id'],
'startDateTime': post_data['start_datetime'],
'endDateTime': post_data['end_datetime'],
'comment': post_data.get('booking_comment', ''),
'contact': self.get_or_create_contact(post_data, nameid),
'roomList': [
{
'eventCategoryCode': post_data['event'],
'roomCode': post_data['room'],
'themeCode': post_data['theme'],
'quantity': int(post_data['quantity']),
'startDateTime': post_data['start_datetime'],
'endDateTime': post_data['end_datetime'],
'comment': post_data.get('room_comment', ''),
'schoolLevelCode': post_data.get('school_level', ''),
}
],
}
headers = {'X-Vivaticket-Form-URL': post_data['form_url']}
r = self.post('Booking/Post', {'Booking': booking}, headers=headers)
self.logger.debug('Book response: %r' % r.text)
if not r.ok:
raise APIError(r.text)
return {'data': r.json()}