passerelle/passerelle/contrib/agoraplus/views.py

667 lines
25 KiB
Python

# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2015 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 unicodedata
import logging
from django.core.exceptions import ObjectDoesNotExist
from django.views.generic import DetailView as GenericDetailView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse, HttpResponseBadRequest, Http404, HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _
from django.utils.http import urlencode
from passerelle.compat import json_loads
import passerelle.utils as utils
from .models import AgoraPlus, AgoraPlusLink, AgoraAPIError
from .wcs import Formdata
from .provisioning import provision_attributes, provision_roles
logger = logging.getLogger('passerelle.contrib.agoraplus')
def normalize_lower(s):
return unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').lower()
class AgoraPlusDetailView(GenericDetailView):
model = AgoraPlus
template_name = 'passerelle/contrib/agoraplus/detail.html'
class RedirectSSOView(GenericDetailView):
model = AgoraPlus
def get(self, request, *args, **kwargs):
resource = self.get_object()
if not resource.frontoffice_url:
raise Http404('no frontoffice defined')
url = resource.frontoffice_url + '/!portail_external.go_to_agora?'
if (request.user.is_authenticated() and hasattr(request.user, 'saml_identifiers')
and request.user.saml_identifiers.exists()):
name_id = request.user.saml_identifiers.first().name_id
link = AgoraPlusLink.objects.filter(resource=resource, name_id=name_id).first()
if link:
try:
login = resource.auth(link.login, link.password)
if login:
token = resource.get_token_raw(login)
url += urlencode((('p_login', login), ('oauth_token', token)))
except AgoraAPIError as e:
pass
return HttpResponseRedirect(url)
class DetailView(GenericDetailView):
model = AgoraPlus
name_id = None
login = None
family = None
def set_user_from_request(self, request):
'''
Get Agora+ user from name_id, if exists, and check it.
'''
self.name_id = None
self.login = None
if 'NameID' in request.GET:
self.name_id = request.GET['NameID']
links = AgoraPlusLink.objects.filter(resource=self.object, name_id=self.name_id)
if len(links) > 1:
raise AgoraAPIError('multiple links')
if links:
self.login = self.object.auth(links[0].login, links[0].password)
if not self.login:
raise AgoraAPIError('unable to find an Agora+ user for NameID %r' %
self.name_id)
elif 'login' in request.GET: # fallback ?login= (only for tests)
user = self.object.get_user(request.GET['login'])
if not user:
raise ObjectDoesNotExist('unknown login')
self.login = user['login']
def get_family(self):
if not self.family:
self.family = self.object.get_family(name_id=self.name_id, login=self.login)
return self.family
def get_child(self, child_id):
family = self.get_family()
if not family:
raise AgoraAPIError('empty family')
childrens = self.get_family().get('children', [])
for child in childrens:
if child['id'] == child_id:
return child
raise AgoraAPIError('unknown child')
def get_data(self, request, *args, **kwargs):
raise NotImplementedError
@utils.to_json()
@utils.protected_api('can_access')
def get(self, request, *args, **kwargs):
self.object = self.get_object()
self.set_user_from_request(request)
self.family = None
data = self.get_data(request, *args, **kwargs)
if isinstance(data, HttpResponse):
return data
return {'data': data}
class PingView(DetailView):
def get_data(self, request, *args, **kwargs):
# simple check Agora+ WS availability
token = self.object.get_token()
response = {'ping': 'pong'}
if 'debug' in request.GET:
response['token'] = token
return response
class ReferenceView(DetailView):
def get_data(self, request, name, reference_id=None, *args, **kwargs):
results = self.object.get_reference(name, reference_id)
if not reference_id and 'q' in request.GET:
q = normalize_lower(request.GET['q'])
results = [result for result in results if q in normalize_lower(result['text'])]
return results
class SchoolView(DetailView):
def get_data(self, request, school_id, *args, **kwargs):
if school_id:
return self.object.get_school(school_id=school_id) # a specific school
if 'birthdate' not in request.GET and 'child_id' not in request.GET:
return self.object.get_school() # all schools
birthdate = request.GET.get('birthdate') or self.get_child(request.GET['child_id'])['birthdate']
year_id = request.GET.get('year_id') or None
return self.object.get_school(family=self.get_family(), birthdate=birthdate, year_id=year_id)
class CommuneView(DetailView):
def get_data(self, request, commune_id=None, *args, **kwargs):
results = self.object.get_commune(commune_id)
if not commune_id and 'q' in request.GET:
q = normalize_lower(request.GET['q'])
results = [result for result in results if q in normalize_lower(result['text'])]
return results
class TypeOfStreetView(DetailView):
def get_data(self, request, type_of_street_id=None, *args, **kwargs):
return self.object.get_type_of_street(type_of_street_id)
class StreetView(DetailView):
def get_data(self, request, commune_id=None, type_of_street_id=None, street_id=None, *args, **kwargs):
results = self.object.get_street(commune_id, type_of_street_id, street_id)
if not street_id and 'q' in request.GET:
q = normalize_lower(request.GET['q'])
results = [result for result in results if q in normalize_lower(result['text'])]
return results
class EducationalStageView(DetailView):
def get_data(self, request, edustage_id=None, *args, **kwargs):
if edustage_id: # a specific stage
return self.object.get_educational_stage(edustage_id=edustage_id)
if 'birthdate' not in request.GET and 'child_id' not in request.GET:
return self.object.get_educational_stage() # all stages
if 'birthdate' in request.GET:
birthdate = request.GET['birthdate']
else:
birthdate = self.get_child(request.GET['child_id'])['birthdate']
return self.object.get_educational_stage(birthdate=birthdate,
year_id=request.GET.get('year_id'))
class UserView(DetailView):
def get_data(self, request, *args, **kwargs):
if self.login:
return self.object.get_user(self.login)
else:
return self.object.get_user() # list all users
class AuthView(DetailView):
def get_data(self, request, *args, **kwargs):
if 'login' not in request.GET:
raise AgoraAPIError('missing login parameter', http_error=400)
if 'password' not in request.GET:
raise AgoraAPIError('missing password parameter', http_error=400)
login = self.object.auth(request.GET['login'], request.GET['password'])
return bool(login)
class LinkView(GenericDetailView):
model = AgoraPlus
@utils.to_json()
@utils.protected_api('can_access')
def get(self, request, *args, **kwargs):
agoraplus = self.get_object()
if 'NameID' not in request.GET:
raise AgoraAPIError('missing NameID parameter')
if 'login' not in request.GET:
raise AgoraAPIError('missing login parameter')
if 'password' not in request.GET:
raise AgoraAPIError('missing password parameter')
login = request.GET['login']
password = request.GET['password']
login = agoraplus.auth(login, password)
if not login:
raise AgoraAPIError('authentication of login %r failed' % login)
family = agoraplus.get_family(login=login)
if not family:
raise AgoraAPIError('no family for login %r' % login)
name_id = request.GET['NameID']
provision_roles(name_id)
provision_attributes(family, login, name_id)
AgoraPlusLink.objects.update_or_create(resource=agoraplus,
name_id=name_id,
defaults={'login': login, 'password': password})
logger.info('link created: NameID:%r with Agora login:%r', name_id, login)
return {'data': True}
class UnlinkView(GenericDetailView):
model = AgoraPlus
@utils.to_json()
@utils.protected_api('can_access')
def get(self, request, *args, **kwargs):
agoraplus = self.get_object()
if 'NameID' not in request.GET:
raise AgoraAPIError('missing NameID parameter')
name_id = request.GET['NameID']
provision_roles(name_id, 'DELETE')
AgoraPlusLink.objects.filter(resource=agoraplus, name_id=name_id).delete()
return {'data': True}
class FamilyView(DetailView):
def get_data(self, request, *args, **kwargs):
family = self.get_family()
if family and family.get('children'):
self.object.add_children_plannings(family)
return family
class FamilyItemView(DetailView):
def get_data(self, request, key, item_id=None, *args, **kwargs):
family = self.get_family()
if not family:
raise AgoraAPIError('empty family')
items = family.get(key, [])
if not item_id:
return items
if item_id:
for item in items:
if item['id'] == item_id:
return item
class ExistEmailView(DetailView):
def get_data(self, request, *args, **kwargs):
email = request.GET.get('email')
if email:
return self.object.exist_email(email)
return False
class ExistChildView(DetailView):
def get_data(self, request, *args, **kwargs):
if 'first_name' not in request.GET:
raise AgoraAPIError('missing first_name parameter')
if 'last_name' not in request.GET:
raise AgoraAPIError('missing last_name parameter')
if 'birthdate' not in request.GET:
raise AgoraAPIError('missing birthdate parameter')
first_name = request.GET.get('first_name')
last_name = request.GET.get('last_name')
birthdate = request.GET.get('birthdate')
return self.object.exist_child(first_name, last_name, birthdate)
class ExistRepresentantView(DetailView):
def get_data(self, request, *args, **kwargs):
if 'first_name' not in request.GET:
raise AgoraAPIError('missing first_name parameter')
if 'last_name' not in request.GET:
raise AgoraAPIError('missing last_name parameter')
first_name = request.GET.get('first_name')
last_name = request.GET.get('last_name')
birthdate = request.GET.get('birthdate')
return self.object.exist_representant(first_name, last_name, birthdate)
class ExistListView(DetailView):
'''
Returns all persons with same first_name, last_name and birthdate (opt.)
'''
def get_data(self, request, *args, **kwargs):
if 'first_name' not in request.GET:
raise AgoraAPIError('missing first_name parameter')
if 'last_name' not in request.GET:
raise AgoraAPIError('missing last_name parameter')
first_name = request.GET.get('first_name')
last_name = request.GET.get('last_name')
birthdate = request.GET.get('birthdate')
first_names = [first_name]
if ' ' in first_name:
first_names.extend(first_name.split(' '))
last_names = [last_name]
if ' ' in last_name:
last_names.extend(last_name.split(' '))
duplicates = []
for first_name in first_names:
for last_name in last_names:
if self.object.exist_person(first_name, last_name, birthdate):
if birthdate:
duplicates.append('%s %s - %s' % (first_name, last_name, birthdate))
else:
duplicates.append('%s %s' % (first_name, last_name))
return duplicates
class SasView(DetailView):
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(SasView, self).dispatch(*args, **kwargs)
def post_data(self, name_id, formdata):
raise NotImplementedError
@utils.to_json()
@utils.protected_api('can_access_sas')
def post(self, request, *args, **kwargs):
self.object = self.get_object()
self.set_user_from_request(request)
data = json_loads(request.body)
formdata = Formdata(data)
name_id = formdata.get('NameID')
if not name_id:
raise AgoraAPIError('missing NameID parameter')
return {'data': self.post_data(name_id, formdata)}
class SasFamilyView(SasView):
def post_data(self, name_id, formdata):
provision_roles(name_id)
family, adult1, adult2 = formdata.get_family()
sas_family = self.object.sas_upsert(name_id, 'FAMILY', family)
sas_adult1 = self.object.sas_upsert(name_id, 'ADULT', adult1)
if adult2.keys() != ['sas_id']: # not empty adult
sas_adult2 = self.object.sas_upsert(name_id, 'ADULT', adult2)
else:
self.object.sas_delete(name_id, 'ADULT', adult2)
sas_adult2 = None
return {
'family': sas_family,
'adult1': sas_adult1,
'adult2': sas_adult2,
}
class SasFamilyPushView(DetailView):
def get_data(self, request, *args, **kwargs):
if not self.name_id:
raise AgoraAPIError('missing NameID parameter')
if self.login:
raise AgoraAPIError('already linked account, cannot push')
return self.object.push_family(self.name_id, validation='validation' in request.GET)
class SasChildView(SasView):
def post_data(self, name_id, formdata):
child = formdata.get_child()
return {
'child': self.object.sas_upsert(name_id, 'CHILD', child),
}
class SasChildPushView(DetailView):
def get_data(self, request, sas_child_id, *args, **kwargs):
if not self.name_id:
raise AgoraAPIError('missing NameID parameter')
if not self.login:
raise AgoraAPIError('no family linked, cannot push child')
return self.object.push_child(self.name_id, self.login, sas_child_id)
class SasChildDeleteView(DetailView):
def get_data(self, request, sas_child_id, *args, **kwargs):
if not self.name_id:
raise AgoraAPIError('missing NameID parameter')
fake_sas_child = {'sas_id': 'sas_%s' % sas_child_id}
return self.object.sas_delete(self.name_id, 'CHILD', fake_sas_child)
class SasContactView(SasView):
def post_data(self, name_id, formdata):
data = formdata.get_contact()
return {
'contact': self.object.sas_upsert(name_id, 'CONTACT', data),
}
class SasContactPushView(DetailView):
def get_data(self, request, sas_contact_id, *args, **kwargs):
if not self.name_id:
raise AgoraAPIError('missing NameID parameter')
if not self.login:
raise AgoraAPIError('no family linked, cannot push contact')
return self.object.push_contact(self.name_id, self.login, sas_contact_id)
class SasContactDeleteView(DetailView):
def get_data(self, request, sas_contact_id, *args, **kwargs):
if not self.name_id:
raise AgoraAPIError('missing NameID parameter')
fake_sas_contact = {'sas_id': 'sas_%s' % sas_contact_id}
return self.object.sas_delete(self.name_id, 'CONTACT', fake_sas_contact)
class SasCheckDuplicatesView(DetailView):
def get_data(self, request, *args, **kwargs):
if not self.name_id:
raise AgoraAPIError('missing NameID parameter')
return self.object.sas_check_duplicates(self.name_id)
class SasPurgeView(DetailView):
def get_data(self, request, *args, **kwargs):
if not self.name_id:
raise AgoraAPIError('missing NameID parameter')
provision_roles(self.name_id, method='DELETE')
return self.object.sas_purge(self.name_id)
class PostFormdataView(DetailView):
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(PostFormdataView, self).dispatch(*args, **kwargs)
def post_data(self, formdata):
raise NotImplementedError
@utils.to_json()
@utils.protected_api('can_access_sas')
def post(self, request, *args, **kwargs):
self.object = self.get_object()
self.set_user_from_request(request)
data = json_loads(request.body)
formdata = Formdata(data)
return {'data': self.post_data(formdata)}
class IncomesDeclarationView(PostFormdataView):
def post_data(self, formdata):
if not self.name_id:
raise AgoraAPIError('missing NameID parameter')
if not self.login:
raise AgoraAPIError('no family linked, cannot add incomes declaration')
data = formdata.get_incomes_declaration()
return self.object.incomes_declaration(self.login, data)
class SchoolEnrollmentView(PostFormdataView):
def post_data(self, formdata):
data = formdata.get_school_enrollment()
return self.object.school_enrollment(data)
class SchoolEnrollmentsView(DetailView):
def get_data(self, request, *args, **kwargs):
family = self.object.get_agoraplus_family(login=self.login, raise_error=True)
enrollments = []
for child in family.get('children') or []:
enrollment = self.object.get_school_enrollment(child)
enrollments.extend(enrollment)
return enrollments
class NurseryEnrollmentView(PostFormdataView):
def post_data(self, formdata):
data = formdata.get_nursery_enrollment()
return self.object.nursery_enrollment(data)
class NurseryEnrollmentResultView(DetailView):
def get_data(self, request, enroll_id, *args, **kwargs):
return self.object.get_nursery_enrollment_result(enroll_id)
class PeriscolChildEnrollmentsView(DetailView):
def get_data(self, request, child_id, *args, **kwargs):
return self.object.get_periscol_enrollments(child_id, request.GET.get('service_id'),
request.GET.get('NameID'), request.GET.get('start_days', 35))
class PeriscolEnrollmentPlanningView(DetailView):
def get_data(self, request, enrollment_id, *args, **kwargs):
return self.object.get_periscol_enrollment_planning(enrollment_id)
class PeriscolChildEnrollmentPlanningView(DetailView):
def get_data(self, request, child_id, enrollment_id, *args, **kwargs):
return self.object.get_periscol_child_enrollment_planning(child_id, enrollment_id,
request.GET.get('reserved_day'), request.GET.get('NameID'),
request.GET.get('start_days', 2), request.GET.get('end_days', 365))
class PeriscolAddReservationView(DetailView):
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(PeriscolAddReservationView, self).dispatch(*args, **kwargs)
@utils.to_json()
@utils.protected_api('can_access')
def post(self, request, *args, **kwargs):
self.object = self.get_object()
try:
data = json_loads(request.body)
enrollment_id = int(data['enrollment_id'])
dates = data['dates']
tarif_id = int(data['tarif_id'])
majored = data['majored']
except (ValueError, TypeError, KeyError) as e:
return HttpResponseBadRequest()
return self.object.create_periscol_reservation(enrollment_id, dates, tarif_id, majored)
class PeriscolDeleteReservationsView(DetailView):
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(PeriscolDeleteReservationsView, self).dispatch(*args, **kwargs)
@utils.to_json()
@utils.protected_api('can_access')
def post(self, request, *args, **kwargs):
self.object = self.get_object()
data = json_loads(request.body)
return self.object.delete_periscol_enrollment_reservations(data['activity_id'], data['dates'])
class InvoicesView(DetailView):
def get_data(self, request, **kwargs):
if not self.login: # unlinked account: no invoice
return []
return self.object.get_payable_invoices(self.login)
class HistoryInvoicesView(DetailView):
def get_data(self, request, **kwargs):
if not self.login: # unlinked account: no invoice
return []
return self.object.get_historical_invoices(self.login)
class InvoicePayView(DetailView):
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(InvoicePayView, self).dispatch(*args, **kwargs)
@utils.to_json()
@utils.protected_api('can_access')
def post(self, request, *args, **kwargs):
self.object = self.get_object()
data = json_loads(request.body)
return {'data': self.object.pay_invoice(kwargs['invoice_id'], data['transaction_id'],
data['transaction_date'])}
class InvoiceView(DetailView):
def get_data(self, request, invoice_id, **kwargs):
if not self.login: # unlinked account: no invoice
return None
return self.object.get_invoice(self.login, invoice_id)
class InvoicePDFView(DetailView):
def get_data(self, request, invoice_id, **kwargs):
pdf = self.object.get_invoice_pdf(self.login, invoice_id)
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % invoice_id
response.write(pdf)
return response
class DocumentView(DetailView):
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(DocumentView, self).dispatch(*args, **kwargs)
@utils.protected_api('can_access')
def post(self, request, *args, **kwargs):
self.object = self.get_object()
try:
data = json_loads(request.body)
uri = data['uri']
content_type = data['content_type']
except (ValueError, TypeError, KeyError) as e:
return HttpResponseBadRequest()
document = self.object.get_document(uri, content_type)
response = HttpResponse(content_type=content_type)
if document is not None:
response.write(document)
else:
response.status_code = 404
return response
class AddressUpdateView(PostFormdataView):
def post_data(self, formdata):
new_address = formdata.get_address()
leaving_date = formdata.get('leaving_date')
family = self.object.get_agoraplus_family(login=self.login)
if not family:
raise Http404(_('no family in Agora+'))
return self.object.update_address(family, new_address, leaving_date)
class PhoneUpdateView(PostFormdataView):
def post_data(self, formdata):
adult_id = formdata.get('adult').get('id')
new_phone_number = formdata.get('phone')
new_cellphone_number = formdata.get('cellphone')
try:
return self.object.update_phone_numbers(self.login, self.name_id, adult_id,
new_phone_number, new_cellphone_number)
except ObjectDoesNotExist as e:
raise Http404(str(e))
class ProfessionUpdateView(PostFormdataView):
def post_data(self, formdata):
adult_id = formdata.get('adult').get('id')
new_profession = formdata.get('profession')
new_pcs = formdata.get('pcs')
new_employer_name = formdata.get('employer')
new_employer_city = formdata.get('employer_city')
new_employer_phone = formdata.get('employer_phone')
try:
return self.object.update_profession(self.login, self.name_id, adult_id,
new_profession, new_pcs, new_employer_name,
new_employer_city, new_employer_phone)
except ObjectDoesNotExist as e:
raise Http404(str(e))