text: # This file is part of Indico.
  # Copyright (C) 2002 - 2021 CERN
  #
  # Indico is free software; you can redistribute it and/or
  # modify it under the terms of the MIT License; see the
  # LICENSE file for more details.
  
  from flask import flash, redirect, request, session
  from werkzeug.exceptions import BadRequest, Forbidden, NotFound
  
  from indico.core.db import db
  from indico.modules.events.management.controllers import RHManageEventBase
  from indico.modules.events.requests import get_request_definitions
  from indico.modules.events.requests.exceptions import RequestModuleError
  from indico.modules.events.requests.models.requests import Request, RequestState
  from indico.modules.events.requests.util import is_request_manager
  from indico.modules.events.requests.views import WPRequestsEventManagement
  from indico.util.i18n import _
  from indico.web.flask.util import url_for
  
  
  class EventOrRequestManagerMixin:
      def _check_access(self):
          self.protection_overridden = False
          if hasattr(self, 'definition'):
              # check if user can manage *that* request
              can_manage_request = session.user and self.definition and self.definition.can_be_managed(session.user)
          else:
              # check if user can manage any request
              can_manage_request = session.user and is_request_manager(session.user)
          can_manage_event = self.event.can_manage(session.user)
          self.protection_overridden = can_manage_request and not can_manage_event
          if not can_manage_request and not can_manage_event:
              RHManageEventBase._check_access(self)
  
  
  class RHRequestsEventRequests(EventOrRequestManagerMixin, RHManageEventBase):
      """Overview of existing requests (event)."""
  
      def _process(self):
          definitions = get_request_definitions()
          if not definitions:
              raise NotFound
          requests = Request.find_latest_for_event(self.event)
          if self.protection_overridden:
              definitions = {name: def_ for name, def_ in definitions.items() if def_.can_be_managed(session.user)}
              requests = {name: req for name, req in requests.items()
                          if req.definition and req.definition.can_be_managed(session.user)}
          return WPRequestsEventManagement.render_template('events/requests/event_requests.html', self.event,
                                                           definitions=definitions, requests=requests)
  
  
  class RHRequestsEventRequestBase(RHManageEventBase):
      """Base class for pages handling a specific request type."""
  
      #: if a request must be present in the database
      _require_request = True
  
      def _process_args(self):
          RHManageEventBase._process_args(self)
          try:
              self.definition = get_request_definitions()[request.view_args['type']]
          except KeyError:
              raise NotFound
          self.request = Request.find_latest_for_event(self.event, self.definition.name)
          if self._require_request and not self.request:
              raise NotFound
  
  
  class RHRequestsEventRequestDetailsBase(EventOrRequestManagerMixin, RHRequestsEventRequestBase):
      """Base class for the details/edit/manage views of a specific request."""
  
      def _process(self):
          self.is_manager = self.definition.can_be_managed(session.user)
          self.form = self.definition.create_form(self.event, self.request)
          self.manager_form = None
          if self.request and self.is_manager:
              self.manager_form = self.definition.create_manager_form(self.request)
              if self.request.state not in {RequestState.accepted, RequestState.rejected}:
                  del self.manager_form.action_save
              if self.request.state == RequestState.accepted:
                  del self.manager_form.action_accept
              if self.request.state == RequestState.rejected:
                  del self.manager_form.action_reject
  
          rv = self.process_form()
          if rv:
              return rv
  
          return self.definition.render_form(event=self.event, definition=self.definition, req=self.request,
                                             form=self.form, manager_form=self.manager_form,
                                             is_manager=self.is_manager,
                                             protection_overridden=self.protection_overridden)
  
      def process_form(self):
          raise NotImplementedError
  
  
  class RHRequestsEventRequestDetails(RHRequestsEventRequestDetailsBase):
      """Details/form for a specific request."""
  
      _require_request = False
  
      def process_form(self):
          form = self.form
          if not form.validate_on_submit():
              return
          if not self.request or not self.request.can_be_modified:
              req = Request(event=self.event, definition=self.definition, created_by_user=session.user)
              new = True
          else:
              req = self.request
              new = False
  
          try:
              with db.session.no_autoflush:
                  self.definition.send(req, form.data)
          except RequestModuleError:
              self.commit = False
              flash_msg = _('There was a problem sending your request ({0}). Please contact support.')
              flash(flash_msg.format(self.definition.title), 'error')
          else:
              if new:
                  flash_msg = (_('Your request ({0}) has been accepted.') if req.state == RequestState.accepted
                               else _('Your request ({0}) has been sent.'))
              else:
                  flash_msg = _('Your request ({0}) has been modified.')
              flash(flash_msg.format(self.definition.title), 'success')
          if self.is_manager:
              return redirect(url_for('.event_requests_details', self.event, type=self.definition.name))
          else:
              return redirect(url_for('.event_requests', self.event))
  
  
  class RHRequestsEventRequestProcess(RHRequestsEventRequestDetailsBase):
      """Accept/Reject a request."""
  
      def _check_access(self):
          self._require_user()
          if not self.definition.can_be_managed(session.user):
              raise Forbidden
  
      def process_form(self):
          form = self.manager_form
          if self.request.state == RequestState.withdrawn:
              return
          elif not form.validate_on_submit():
              # very unlikely, unless the definition uses a custom form
              return
          if 'action_accept' in form and form.action_accept.data:
              action = 'accept'
          elif 'action_reject' in form and form.action_reject.data:
              action = 'reject'
          elif 'action_save' in form and form.action_save.data:
              action = 'save'
          else:
              # This happens when the request was already processed, e.g. in another
              # tab or if you simply click the submit button twice.  No nee to fail;
              # just don't do anything.
              return redirect(url_for('.event_requests_details', self.request))
          if action == 'accept':
              self.definition.accept(self.request, self.manager_form.data, session.user)
              flash(_('You have accepted this request.'), 'info')
          elif action == 'reject':
              self.definition.reject(self.request, self.manager_form.data, session.user)
              flash(_('You have rejected this request.'), 'info')
          elif action == 'save':
              self.definition.manager_save(self.request, self.manager_form.data)
              flash(_('You have updated the request (only management-specific data).'), 'info')
          return redirect(url_for('.event_requests_details', self.request))
  
  
  class RHRequestsEventRequestWithdraw(EventOrRequestManagerMixin, RHRequestsEventRequestBase):
      """Withdraw a request."""
  
      def _check_access(self):
          EventOrRequestManagerMixin._check_access(self)
          if self.protection_overridden:
              raise Forbidden
  
      def _process(self):
          if self.request.state not in {RequestState.pending, RequestState.accepted}:
              raise BadRequest
          try:
              with db.session.no_autoflush:
                  self.definition.withdraw(self.request)
          except RequestModuleError:
              self.commit = False
              flash_msg = _('There was a problem withdrawing your request ({0}). Please contact support.')
              flash(flash_msg.format(self.definition.title), 'error')
          else:
              flash(_('You have withdrawn your request ({0})').format(self.definition.title))
          return redirect(url_for('.event_requests', self.event))
  