def reopen_ticket(self, request): user = UserCollection(request.app.session()).by_username( request.identity.userid) try: self.reopen_ticket(user) except InvalidStateChange: request.alert( _("The ticket cannot be re-opened because it's not closed.") ) else: request.success(_("You have reopened ticket ${number}", mapping={ 'number': self.number })) email = self.snapshot.get('email') or self.handler.email send_html_mail( request=request, template='mail_ticket_reopened.pt', subject=_("Your ticket has been reopened"), receivers=(email, ), content={ 'model': self } ) request.app.update_ticket_count() return morepath.redirect(request.link(self))
def view_tickets(self, request): def get_filters(): states = ( ('open', _("Open")), ('pending', _("Pending")), ('closed', _("Closed")) ) for id, text in states: yield Link( text=text, url=request.link(self.for_state(id)), active=self.state == id ) if self.state == 'open': tickets_title = _("Open Tickets") elif self.state == 'pending': tickets_title = _("Pending Tickets") elif self.state == 'closed': tickets_title = _("Closed Tickets") else: raise NotImplementedError return { 'title': _("Tickets"), 'layout': TicketsLayout(self, request), 'tickets': self.batch, 'filters': tuple(get_filters()), 'tickets_title': tickets_title, }
def bottom_links(self): request = self.request if request.current_role == 'editor': return [ Link(_('Logout'), self.logout_url), Link(_('User Profile'), request.link( self.town, 'benutzerprofil' )), Link(_('Files'), request.link(FileCollection(self.app))), Link(_('Images'), request.link(ImageCollection(self.app))), Link('OneGov Cloud', 'http://www.onegovcloud.ch'), Link('Seantis GmbH', 'https://www.seantis.ch') ] elif request.current_role == 'admin': return [ Link(_('Logout'), self.logout_url), Link(_('User Profile'), request.link( self.town, 'benutzerprofil' )), Link(_('Files'), request.link(FileCollection(self.app))), Link(_('Images'), request.link(ImageCollection(self.app))), Link(_('Settings'), request.link(self.town, 'einstellungen')), Link('OneGov Cloud', 'http://www.onegovcloud.ch'), Link('Seantis GmbH', 'https://www.seantis.ch') ] else: return [ Link(_('Login'), self.login_url), Link('OneGov Cloud', 'http://www.onegovcloud.ch'), Link('Seantis GmbH', 'https://www.seantis.ch') ]
def editbar_links(self): if self.request.is_logged_in: # only show the model related links when the ticket is pending if self.model.state == 'pending': links = self.model.handler.get_links(self.request) else: links = [] if self.model.state == 'open': links.append(Link( text=_("Accept ticket"), url=self.request.link(self.model, 'accept'), classes=('ticket-action', 'ticket-accept'), )) elif self.model.state == 'pending': links.append(Link( text=_("Close ticket"), url=self.request.link(self.model, 'close'), classes=('ticket-action', 'ticket-close'), )) elif self.model.state == 'closed': links.append(Link( text=_("Reopen ticket"), url=self.request.link(self.model, 'reopen'), classes=('ticket-action', 'ticket-reopen'), )) return links
def handle_login(self, request, form): """ Handles the login requests. """ if form.submitted(request): response = self.login_to(request=request, **form.login_data) if response: request.success(_("You have been logged in.")) return response request.alert(_("Wrong e-mail address, password or yubikey.")) layout = DefaultLayout(self, request) layout.breadcrumbs = [ Link(_("Homepage"), layout.homepage_url), Link(_("Login"), request.link(self, name='login')) ] return { 'layout': layout, 'password_reset_link': request.link( request.app.town, name='request-password'), 'title': _('Login to ${town}', mapping={ 'town': request.app.town.name }), 'form': form }
def close_ticket(self, request): try: self.close_ticket() except InvalidStateChange: request.alert( _("The ticket cannot be closed because it's not pending") ) else: request.success(_("You have closed ticket ${number}", mapping={ 'number': self.number })) email = self.snapshot.get('email') or self.handler.email send_html_mail( request=request, template='mail_ticket_closed.pt', subject=_("Your ticket has been closed"), receivers=(email, ), content={ 'model': self } ) request.app.update_ticket_count() return morepath.redirect( request.link(TicketCollection(request.app.session())))
def reject_reservation(self, request): collection = ResourceCollection(request.app.libres_context) resource = collection.by_id(self.resource) scheduler = resource.get_scheduler(request.app.libres_context) reservations = scheduler.reservations_by_token(self.token.hex) forms = FormCollection(request.app.session()) submission = forms.submissions.by_id(self.token.hex) send_html_mail( request=request, template='mail_reservation_rejected.pt', subject=_("Your reservation was rejected"), receivers=(self.email, ), content={ 'model': self, 'resource': resource, 'reservations': reservations } ) # create a snapshot of the ticket to keep the useful information tickets = TicketCollection(request.app.session()) ticket = tickets.by_handler_id(self.token.hex) ticket.create_snapshot(request) scheduler.remove_reservation(self.token.hex) if submission: forms.submissions.delete(submission) request.success(_("The reservation was rejected")) # return none on intercooler js requests if not request.headers.get('X-IC-Request'): return morepath.redirect(request.params['return-to'])
def validate(self): """ Make sure a valid RRULE can be generated with the given fields. Might be better to group weekly and end_date in an enclosure, see See http://wtforms.readthedocs.org/en/latest/fields.html #field-enclosures. """ result = super().validate() if self.start_date.data and self.end_date.data: if self.start_date.data > self.end_date.data: message = _("The end date must be later than the start date.") self.end_date.errors.append(message) result = False if self.weekly.data and self.start_date.data: weekday = WEEKDAYS[self.start_date.data.weekday()][0] if weekday not in self.weekly.data: message = _("The weekday of the start date must be selected.") self.weekly.errors.append(message) result = False if self.weekly.data and not self.end_date.data: message = _("Please set and end date if the event is recurring.") self.end_date.errors.append(message) result = False if self.end_date.data and not self.weekly.data: message = _("Please select a weekday if the event is recurring.") self.weekly.errors.append(message) result = False return result
def event_title(self): if self.allocation.partly_available: available = self.translate(_("${percent}% Available", mapping={ 'percent': int(self.availability) })) else: quota = self.allocation.quota quota_left = int(quota * self.availability / 100) if quota == 1: if quota_left: available = self.translate(_("Available")) else: available = self.translate(_("Unavailable")) else: available = self.translate( _("${num}/${max} Available", mapping={ 'num': quota_left, 'max': quota }) ) # add an extra space at the end of the event time, so we can hide # the <br> tag on the output without having the time and the # availability seemingly joined together without space. return '\n'.join((self.event_time + ' ', available))
def confirm_reservation(self, request): # this view is public, but only for a limited time assert_anonymous_access_only_temporary(self, request) collection = ResourceCollection(request.app.libres_context) resource = collection.by_id(self.resource) forms = FormCollection(request.app.session()) submission = forms.submissions.by_id(self.token) if submission: form = request.get_form(submission.form_class, data=submission.data) else: form = None layout = ReservationLayout(resource, request) layout.breadcrumbs.append(Link(_("Confirm"), '#')) return { 'title': _("Confirm your reservation"), 'layout': layout, 'form': form, 'resource': resource, 'allocation': self._target_allocations().first(), 'reservation': self, 'finalize_link': request.link(self, 'abschluss'), 'edit_link': request.link(self, 'bearbeiten') }
def handle_password_reset(self, request, form): request.include("common") request.include("check_password") if form.submitted(request): identity = form.get_identity(request) if identity is not None: response = morepath.redirect(request.link(self)) morepath.remember_identity(response, request, identity) request.success(_("Password changed.")) return response else: request.alert(_("Wrong username or password reset link not valid any more.")) log.info("Failed password reset attempt by {}".format(request.client_addr)) if "token" in request.params: form.token.data = request.params["token"] layout = DefaultLayout(self, request) layout.breadcrumbs = [ Link(_("Homepage"), layout.homepage_url), Link(_("Reset password"), request.link(self, name="request-password")), ] return {"layout": layout, "title": _(u"Reset password"), "form": form, "form_width": "small"}
def handle_password_reset_request(self, request, form): """ Handles the GET and POST password reset requests. """ if form.submitted(request): user, token = form.get_token(request) if user is not None and token is not None: url = "{0}?token={1}".format(request.link(self, name="reset-password"), token) send_html_mail( request=request, template="mail_password_reset.pt", subject=_("Password reset"), receivers=(user.username,), content={"model": None, "url": url}, ) else: log.info("Failed password reset attempt by {}".format(request.client_addr)) response = morepath.redirect(request.link(self)) request.success( _( ( u"A password reset link has been sent to ${email}, provided an " u"account exists for this email address." ), mapping={"email": form.email.data}, ) ) return response layout = DefaultLayout(self, request) layout.breadcrumbs = [ Link(_("Homepage"), layout.homepage_url), Link(_("Reset password"), request.link(self, name="request-password")), ] return {"layout": layout, "title": _(u"Reset password"), "form": form, "form_width": "small"}
def view_occurrences(self, request): """ View all occurrences of all events. """ request.include('common') request.include('events') layout = OccurrencesLayout(self, request) tags = ( Link( text=request.translate(_(tag)), url=request.link(self.for_filter(tag=tag)), active=tag in self.tags and 'active' or '' ) for tag in self.used_tags ) return { 'active_tags': self.tags, 'add_link': request.link(self, name='neu'), 'date_placeholder': date.today().isoformat(), 'end': self.end.isoformat() if self.end else '', 'layout': layout, 'number_of_occurrences': self.subset_count, 'occurrences': self.batch, 'start': self.start.isoformat() if self.start else '', 'tags': tags, 'title': _('Events'), }
def handle_new_definition(self, request, form): if form.submitted(request): model = Bunch( title=None, definition=None, type='custom', meta={}, content={} ) form.update_model(model) # forms added online are always custom forms new_form = self.definitions.add( title=model.title, definition=model.definition, type='custom', meta=model.meta, content=model.content ) request.success(_("Added a new form")) return morepath.redirect(request.link(new_form)) layout = FormEditorLayout(self, request) layout.breadcrumbs = [ Link(_("Homepage"), layout.homepage_url), Link(_("Forms"), request.link(self)), Link(_("New Form"), request.link(self, name='neu')) ] return { 'layout': layout, 'title': _("New Form"), 'form': form, 'form_width': 'large', }
def editbar_links(self): if self.request.is_logged_in: return [ LinkGroup( title=_("Add"), links=[ Link( text=_("Room"), url=self.request.link( self.model, name='neuer-raum' ), classes=('new-room', ) ), Link( text=_("Daypass"), url=self.request.link( self.model, name='neue-tageskarte' ), classes=('new-daypass', ) ) ] ), ]
def search(self, request): layout = DefaultLayout(self, request) layout.breadcrumbs.append(Link(_("Search"), "#")) try: request.app.es_client.ping() except TransportError: log.warn("Elasticsearch cluster is offline") return {"title": _("Search Unavailable"), "layout": layout, "connection": False} if "lucky" in request.GET: url = self.feeling_lucky() if url: return morepath.redirect(url) return { "title": _("Search"), "model": self, "layout": layout, "hide_search_header": True, "searchlabel": _("Search through ${count} indexed documents", mapping={"count": self.available_documents}), "resultslabel": _("${count} Results", mapping={"count": self.subset_count}), "connection": True, }
def handle_edit_definition(self, request, form): if form.submitted(request): self.title = form.title.data if self.type == 'custom': self.definition = form.definition.data form.update_model(self) request.success(_("Your changes were saved")) return morepath.redirect(request.link(self)) else: form.title.data = self.title form.definition.data = self.definition form.apply_model(self) collection = FormCollection(request.app.session()) layout = FormEditorLayout(self, request) layout.breadcrumbs = [ Link(_("Homepage"), layout.homepage_url), Link(_("Forms"), request.link(collection)), Link(self.title, request.link(self)), Link(_("Edit"), request.link(self, name='bearbeiten')) ] return { 'layout': layout, 'title': self.title, 'form': form, 'form_width': 'large', }
def get_edit_links(self, request): """ Yields the edit links shown on the private view of this trait. """ if self.editable: yield Link( _("Edit"), request.link(Editor('edit', self)), classes=('edit-link', ) ) if self.deletable: trait_messages = self.trait_messages[self.trait] if self.children: extra_warning = _( "Please note that this page has subpages " "which will also be deleted!" ) else: extra_warning = "" yield DeleteLink( _("Delete"), request.link(self), confirm=_(trait_messages['delete_question'], mapping={ 'title': self.title }), yes_button_text=trait_messages['delete_button'], extra_information=extra_warning, redirect_after=request.link(self.parent) )
def publish_event(self, request): """ Publish an event. """ self.publish() request.success(_("You have accepted the event ${title}", mapping={ 'title': self.title })) if self.meta.get('submitter_email'): session = request.app.session() ticket = TicketCollection(session).by_handler_id(self.id.hex) send_html_mail( request=request, template='mail_event_accepted.pt', subject=_("Your event was accepted"), receivers=(self.meta.get('submitter_email'), ), content={ 'model': self, 'ticket': ticket } ) if 'return-to' in request.GET: return morepath.redirect(request.GET['return-to']) return morepath.redirect(request.link(self))
def accept_reservation(self, request): if not self.data or not self.data.get('accepted'): collection = ResourceCollection(request.app.libres_context) resource = collection.by_id(self.resource) scheduler = resource.get_scheduler(request.app.libres_context) reservations = scheduler.reservations_by_token(self.token) send_html_mail( request=request, template='mail_reservation_accepted.pt', subject=_("Your reservation was accepted"), receivers=(self.email, ), content={ 'model': self, 'resource': resource, 'reservations': reservations } ) for reservation in reservations: reservation.data = reservation.data or {} reservation.data['accepted'] = True # libres does not automatically detect changes yet flag_modified(reservation, 'data') request.success(_("The reservation was accepted")) else: request.warning(_("The reservation has already been accepted")) return morepath.redirect(request.params['return-to'])
def handle_user_profile(self, request, form): """ Handles the GET and POST login requests. """ layout = DefaultLayout(self, request) collection = UserCollection(request.app.session()) user = collection.by_username(request.identity.userid) if form.submitted(request): form.update_model(user) request.success(_("Your changes were saved")) else: form.apply_model(user) layout.breadcrumbs = [ Link(_("Homepage"), layout.homepage_url), Link(_("User Profile"), request.link(self)) ] return { 'layout': layout, 'title': _("User Profile"), 'form': form, 'username': user.username, 'role': user.role }
def get_site_collection(self, request): """ Returns a list of internal links to be used by the redactor. See `<http://imperavi.com/redactor/plugins/predefined-links/>`_ """ objects = self.get() groups = [ ('topics', request.translate(_("Topics"))), ('news', request.translate(_("Latest news"))), ('forms', request.translate(_("Forms"))), ('resources', request.translate(_("Resources"))), ] links = [] for id, label in groups: for obj in objects[id]: # in addition to the default url/name pairings we use a group # label which will be used as optgroup label links.append({ 'group': label, 'name': obj.title, 'url': request.link(obj) }) return links
def handle_cleanup_allocations(self, request, form): """ Removes all unused allocations between the given dates. """ if form.submitted(request): start, end = form.data['start'], form.data['end'] scheduler = self.get_scheduler(request.app.libres_context) count = scheduler.remove_unused_allocations(start, end) request.success( _("Successfully removed ${count} unused allocations", mapping={ 'count': count }) ) return morepath.redirect(request.link(self)) layout = ResourceLayout(self, request) layout.breadcrumbs.append(Link(_("Clean up"), '#')) layout.editbar_links = None return { 'layout': layout, 'title': _("Clean up"), 'form': form }
def handle_settings(self, request, form): """ Handles the GET and POST login requests. """ layout = DefaultLayout(self, request) layout.include_editor() request.include('check_contrast') if form.submitted(request): with request.app.update_town() as town: town.name = form.name.data town.logo_url = form.logo_url.data town.theme_options = form.theme_options town.meta = { 'contact': form.contact.data, 'contact_html': linkify( form.contact.data).replace('\n', '<br>'), 'contact_url': form.contact_url.data, 'opening_hours': form.opening_hours.data, 'opening_hours_html': linkify( form.opening_hours.data).replace('\n', '<br>'), 'opening_hours_url': form.opening_hours_url.data, 'reply_to': form.reply_to.data, 'facebook_url': form.facebook_url.data, 'twitter_url': form.twitter_url.data, 'analytics_code': form.analytics_code.data, 'online_counter_label': form.online_counter_label.data, 'reservations_label': form.reservations_label.data, 'sbb_daypass_label': form.sbb_daypass_label.data, } request.success(_("Your changes were saved")) else: form.name.data = self.name form.logo_url.data = self.logo_url form.theme_options = self.theme_options form.contact.data = self.meta.get('contact') form.contact_url.data = self.meta.get('contact_url') form.opening_hours.data = self.meta.get('opening_hours') form.opening_hours_url.data = self.meta.get('opening_hours_url') form.reply_to.data = self.meta.get('reply_to') form.facebook_url.data = self.meta.get('facebook_url') form.twitter_url.data = self.meta.get('twitter_url') form.analytics_code.data = self.meta.get('analytics_code') form.online_counter_label.data = self.meta.get('online_counter_label') form.reservations_label.data = self.meta.get('reservations_label') form.sbb_daypass_label.data = self.meta.get('sbb_daypass_label') layout.breadcrumbs = [ Link(_("Homepage"), layout.homepage_url), Link(_("Settings"), request.link(self)) ] return { 'layout': layout, 'title': _('Settings'), 'form': form, 'form_width': 'large' }
def breadcrumbs(self): collection = FormCollection(self.request.app.session()) return [ Link(_("Homepage"), self.homepage_url), Link(_("Forms"), self.request.link(collection)), Link(self.title, '#') ]
def view_get_image_collection(self, request): request.include("dropzone") images = [Img(src=request.link(image.thumbnail), url=request.link(image)) for image in self.files] layout = DefaultLayout(self, request) layout.breadcrumbs = [Link(_("Homepage"), layout.homepage_url), Link(_("Images"), request.link(self))] return {"layout": layout, "title": _(u"Images"), "images": images}
def view_get_file_collection(self, request): request.include("dropzone") files = [Link(text=file_.original_name, url=request.link(file_)) for file_ in self.files] layout = DefaultLayout(self, request) layout.breadcrumbs = [Link(_("Homepage"), layout.homepage_url), Link(_("Files"), request.link(self))] return {"layout": layout, "title": _("Files"), "files": files}
def event_actions(self): if self.availability == 0: yield Link( _("Unavailable"), '#', classes=('new-reservation', 'disabled') ) else: yield Link( _("Reserve"), self.request.link(self.allocation, name='reservieren'), classes=('new-reservation', ) ) if self.request.is_logged_in: yield Link( _("Edit"), self.request.link(self.allocation, name='bearbeiten'), classes=('edit-link', ) ) yield Link( _("Tickets"), self.request.link(TicketCollection( session=self.request.app.session, handler='RSV', state='all', extra_parameters={ 'allocation_id': str(self.allocation.id) } )), classes=('RSV-link', ) ) if self.availability == 100.0: yield DeleteLink( _("Delete"), self.request.link(self.allocation), confirm=_("Do you really want to delete this allocation?"), extra_information=self.event_identification, yes_button_text=_("Delete allocation") ) else: yield DeleteLink( _("Delete"), self.request.link(self.allocation), confirm=_( "This allocation can't be deleted because there are " "existing reservations associated with it." ), extra_information=_( "To delete this allocation, all existing reservations " "need to be cancelled first." ) )
def atoz(self, request): layout = DefaultLayout(self, request) layout.breadcrumbs.append(Link(_("Topics A-Z"), '#')) return { 'title': _("Topics A-Z"), 'model': self, 'layout': layout }
def view_tickets(self, request): def get_filters(): states = ( ('open', _("Open")), ('pending', _("Pending")), ('closed', _("Closed")), ('all', _("All")) ) for id, text in states: yield Link( text=text, url=request.link(self.for_state(id)), active=self.state == id, classes=('ticket-filter-' + id, ) ) def get_handlers(): handlers = ( ('ALL', _("All")), ('EVN', _("Events")), ('FRM', _("Form Submissions")), ('RSV', _("Reservations")), ) for id, text in handlers: yield Link( text=text, url=request.link(self.for_handler(id)), active=self.handler == id, classes=(id + '-link', ) ) if self.state == 'open': tickets_title = _("Open Tickets") elif self.state == 'pending': tickets_title = _("Pending Tickets") elif self.state == 'closed': tickets_title = _("Closed Tickets") elif self.state == 'all': tickets_title = _("All Tickets") else: raise NotImplementedError return { 'title': _("Tickets"), 'layout': TicketsLayout(self, request), 'tickets': self.batch, 'filters': tuple(get_filters()), 'handlers': tuple(get_handlers()), 'tickets_title': tickets_title, 'tickets_state': self.state, 'has_handler_filter': self.handler != 'ALL' }