passerelle/passerelle/apps/vivaticket/models.py

293 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(perm='can_access', methods=['get'], description=_('Get event categories'))
def events(self, request):
return self.get_list_of_settings('Settings/GetEventCategory')
@endpoint(perm='can_access', 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(perm='can_access', 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', perm='can_access', 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(
perm='can_access',
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()}