def cancel_booking(self, request): request.assert_valid_csrf_token() if not self.period.wishlist_phase: if not request.is_admin: if self.occasion.is_past_cancellation(date.today()): request.alert(_( "Only admins may cancel bookings at this point" )) return BookingCollection(request.session).cancel_booking( booking=self, score_function=self.period.scoring, cascade=False) request.success(_("The booking was cancelled successfully")) @request.after def update_matching(response): response.headers.add('X-IC-Trigger', 'reload-from') response.headers.add('X-IC-Trigger-Data', json.dumps({ 'selector': '#{}'.format(self.occasion.id) }))
def edit_occasion(self, request, form): if self.period.confirmed: warning = _( "The period of this occasion has already been confirmed. " "It is not recommended to change the period associated with " "this occasion. " ) else: warning = None if form.submitted(request): form.populate_obj(self) request.success(_("Your changes were saved")) return request.redirect(request.link(self.activity)) elif not request.POST: form.process(obj=self) return { 'layout': OccasionFormLayout( self.activity, request, _("Edit Occasion")), 'title': _("Edit Occasion"), 'form': form, 'callout': warning }
def edit_attendee_limit(self, request, form): assert request.is_admin or self.username == request.current_username bookings = BookingCollection(request.session) bookings = bookings.for_username(self.username) if form.submitted(request): form.populate_obj(self) request.success(_("Your changes were saved")) return request.redirect(request.link(bookings)) elif not request.POST: form.process(obj=self) title = _("Booking Limit of ${name}", mapping={'name': self.name}) layout = BookingCollectionLayout(bookings, request, self.user) layout.breadcrumbs.append(Link(title, request.link(self))) return { 'form': form, 'layout': layout, 'title': title, }
def edit_attendee(self, request, form): # note: attendees are added in the views/occasion.py file assert request.is_admin or self.username == request.current_username bookings = BookingCollection(request.session) bookings = bookings.for_username(self.username) if form.submitted(request): form.populate_obj(self) request.success(_("Your changes were saved")) return request.redirect(request.link(bookings)) elif not request.POST: form.process(obj=self) title = _("Edit Attendee") layout = BookingCollectionLayout(bookings, request, self.user) layout.breadcrumbs.append(Link(title, request.link(self))) return { 'form': form, 'layout': layout, 'title': title, }
def handle_payment(self, request): provider = request.app.default_payment_provider token = request.params.get('payment_token') period = request.params.get('period') invoice = self.for_period_id(period_id=period).query()\ .outerjoin(InvoiceItem)\ .one() price = request.app.default_payment_provider.adjust_price(invoice.price) payment = process_payment('cc', price, provider, token) if not payment: request.alert(_("Your payment could not be processed")) else: for item in invoice.items: if item.paid: continue item.payments.append(payment) item.source = provider.type item.paid = True request.success(_("Your payment has been received. Thank you!")) return request.redirect(request.link(self))
def breadcrumbs(self): return (Link(_("Homepage"), self.homepage_url), Link(_("Activities"), self.request.class_link(VacationActivityCollection)), Link(self.model.activity.title, self.request.link(self.model.activity)), Link(_("Attendees"), '#'))
def breadcrumbs(self): return (Link(_("Homepage"), self.homepage_url), Link(_("Activities"), self.request.class_link(VacationActivityCollection)), Link(_("Manage Periods"), self.request.class_link(PeriodCollection)), Link(self.title, '#'))
def group_action(booking, action): assert action in ('join', 'leave') if attendees_count == 1 and action == 'leave': traits = ( Intercooler( request_method='POST', redirect_after=request.class_link(BookingCollection) ), ) else: traits = ( Intercooler( request_method='POST', redirect_after=request.link(self) ), ) url = URL(request.link(self, action))\ .query_param('booking_id', booking.id)\ .as_string() return Link( text=(action == 'join' and _("Join Group") or _("Leave Group")), url=layout.csrf_protected_url(url), traits=traits )
def clone_occasion(self, request, form): if form.submitted(request): occasions = OccasionCollection(request.session) periods = PeriodCollection(request.session) form.populate_obj(occasions.add( activity=self.activity, start=form.parsed_dates[0].start, end=form.parsed_dates[0].end, timezone=form.timezone, period=periods.by_id(form.period_id.data) )) request.success(_("Your changes were saved")) return request.redirect(request.link(self.activity)) elif not request.POST: form.process(obj=self) return { 'layout': OccasionFormLayout( self.activity, request, _("Clone Occasion")), 'title': _("Clone Occasion"), 'form': form }
class VacationActivityForm(Form): title = TextField(label=_("Title"), description=_("The title of the activity"), validators=[InputRequired()]) lead = TextAreaField( label=_("Lead"), description=_("Describes briefly what this activity is about"), validators=[InputRequired()], render_kw={'rows': 4}) text = HtmlField(label=_("Text")) tags = OrderedMultiCheckboxField(label=_("Tags"), choices=TAGS) username = SelectField(label=_("Organiser"), validators=[InputRequired()], fieldset=_("Administration"), default='0xdeadbeef') location = TextAreaField(label=_("Location"), fieldset=_("Map"), render_kw={'rows': 4}) @property def username_choices(self): assert self.request.is_admin # safety net users = UserCollection(self.request.session) users = users.by_roles('admin', 'editor') users = users.with_entities(User.username, User.title) def choice(row): return row[0], row[1] def by_title(choice): return normalize_for_url(choice[1]) return sorted([choice(r) for r in users.all()], key=by_title) def set_username_default(self, value): # we can't set self.username.default here, as that has already been # done by wtforms - but we can see if the default has been used if self.username.data == '0xdeadbeef': self.username.data = value def for_admins(self): self.set_username_default(self.request.current_username) self.username.choices = self.username_choices def for_non_admins(self): self.delete_field('username') def on_request(self): if self.request.is_admin: self.for_admins() else: self.for_non_admins()
def title(self): wishlist_phase = self.app.active_period \ and self.app.active_period.wishlist_phase if self.user.username == self.request.current_username: return wishlist_phase and _("Wishlist") or _("Bookings") elif wishlist_phase: return _("Wishlist of ${user}", mapping={'user': self.user.title}) else: return _("Bookings of ${user}", mapping={'user': self.user.title})
def handle_send_notification(self, request, form): period = PeriodCollection(request.session).active() variables = TemplateVariables(request, period) layout = NotificationTemplateLayout(self, request) if form.submitted(request): recipients = form.recipients if not recipients: request.alert(_("There are no recipients matching the selection")) else: current = request.current_username if current not in recipients: recipients.add(current) subject = variables.render(self.subject) content = render_template('mail_notification.pt', request, { 'layout': DefaultMailLayout(self, request), 'title': subject, 'notification': variables.render(self.text) }) plaintext = html_to_text(content) for recipient in recipients: request.app.send_marketing_email( receivers=(recipient, ), subject=subject, content=content, plaintext=plaintext, ) self.last_sent = utcnow() request.success(_( "Successfully sent the e-mail to ${count} recipients", mapping={ 'count': len(recipients) } )) return request.redirect( request.class_link(NotificationTemplateCollection)) return { 'title': _("Mailing"), 'layout': layout, 'form': form, 'preview_subject': variables.render(self.subject), 'preview_body': variables.render(self.text), 'edit_link': request.return_here(request.link(self, 'edit')), 'button_text': _("Send E-Mail Now") }
def handle_delete_donation(self, request): assert self.user_id and self.period_id request.assert_valid_csrf_token() period = request.app.periods_by_id[self.period_id.hex] bills = BillingCollection(request, period) if bills.exclude_donation(self.user_id): request.success(_("Your donation was removed")) else: request.alert(_("This donation has already been paid"))
def ensure_valid_donation(self): # let's prevent shenanigans, like negative donations try: amount = float(self.amount.data) except ValueError: self.amount.errors = [_("Invalid amount")] return False if not (0 < amount < float('inf')): self.amount.errors = [_("Invalid amount")] return False
def ensure_complete_userprofile(self): if not self.is_complete_userprofile: if self.user.username == self.request.current_username: self.attendee.errors.append( _("Your userprofile is not complete. It needs to be " "complete before signing up any attendees.")) else: self.attendee.errors.append( _("The user's userprofile is not complete. It needs to be " "complete before signing up any attendees.")) return False
def breadcrumbs(self): links = [ Link(_("Homepage"), self.homepage_url), Link(_("Activities"), self.request.class_link(VacationActivityCollection)), Link(_("Notification Templates"), self.request.class_link(NotificationTemplateCollection)) ] if self.subtitle: links.append(Link(self.subtitle, '#')) return links
def invoice_actions(details): if details.paid: yield InvoiceAction(session=session, id=details.first.id, action='mark-unpaid', extend_to='invoice', text=_("Mark whole bill as unpaid")) else: yield InvoiceAction(session=session, id=details.first.id, action='mark-paid', extend_to='invoice', text=_("Mark whole bill as paid"))
def family_removal_links(self): attrs = {'class': ('remove-manual', 'extend-to-family')} for record in self.families: text = _('Delete "${text}"', mapping={ 'text': record.text, }) url = self.csrf_protected_url( self.request.class_link( InvoiceAction, { 'id': record.item, 'action': 'remove-manual', 'extend_to': 'family' })) if record.has_online_payments: traits = (Block( _("This booking cannot be removed, at least one " "booking has been paid online."), _("You may remove the bookings manually one by one."), _("Cancel")), ) else: traits = (Confirm( _('Do you really want to remove "${text}"?', mapping={'text': record.text}), _("${count} bookings will be removed", mapping={'count': record.count}), _("Remove ${count} bookings", mapping={'count': record.count}), _("Cancel")), Intercooler(request_method='POST')) yield Link(text=text, url=url, attrs=attrs, traits=traits)
def on_request(self): self.target.choices = [('all', _("All")), ('for-user', _("For a specific user"))] self.load_usernames() self.load_user_tags() if self.tags.choices: self.target.choices.append( ('for-users-with-tags', _("For users with tags"))) if self.request.params.get('for-user'): self.target.data = 'for-user' self.username.data = self.request.params['for-user']
def ordered_tags(self, request): tags = [request.translate(_(tag)) for tag in self.tags] durations = sum( o.duration for o in (request.session.query(Occasion).with_entities( Occasion.duration).distinct().filter_by(activity_id=self.id))) if DAYS.has(durations, DAYS.half): tags.append(request.translate(_("Half day"))) if DAYS.has(durations, DAYS.full): tags.append(request.translate(_("Full day"))) if DAYS.has(durations, DAYS.many): tags.append(request.translate(_("Multiple days"))) return sorted(tags)
def toggle_star(self, request): if self.period.wishlist_phase: if not self.starred: if not self.star(max_stars=3): show_error_on_attendee(request, self.attendee, _( "Cannot select more than three favorites per child")) else: self.unstar() else: show_error_on_attendee(request, self.attendee, _( "The period is not in the wishlist-phase")) layout = BookingCollectionLayout(self, request) return render_macro(layout.macros['star'], request, {'booking': self})
class BillingForm(Form): confirm = RadioField(label=_("Confirm billing:"), default='no', choices=[('no', _("No, preview only")), ('yes', _("Yes, confirm billing"))]) sure = BooleanField( label=_("I know that this stops all changes including cancellations."), default=False, depends_on=('confirm', 'yes')) @property def finalize_period(self): return self.confirm.data == 'yes' and self.sure.data is True
def invoice_item_fields(self, item): yield _("Invoice Item Group"), item.group yield _("Invoice Item Text"), item.text yield _("Invoice Item Paid"), item.paid yield _("Invoice Item Transaction ID"), item.tid or '' yield _("Invoice Item Source"), item.source or '' yield _("Invoice Item Unit"), item.unit yield _("Invoice Item Quantity"), item.quantity yield _("Invoice Item Amount"), item.amount yield _("Invoice Item References"), '\n'.join( r.readable for r in item.invoice.references)
def payment_actions(payment): if payment.state == 'paid': amount = '{:02f} {}'.format(payment.amount, payment.currency) yield Link(text=_("Refund Payment"), url=layout.csrf_protected_url( request.link(payment, 'refund')), attrs={'class': 'payment-refund'}, traits=(Confirm( _("Do you really want to refund ${amount}?", mapping={'amount': amount}), _("This cannot be undone."), _("Refund ${amount}", mapping={'amount': amount}), _("Cancel")), Intercooler(request_method='POST', redirect_after=request.url)))
def ensure_valid_range(self): lower = self.min_number.data or 0 upper = self.max_number.data or 0 if lower > upper: self.min_number.errors.append(_("Minimum is larger than maximum")) return False
def manual_booking_link(username): url = request.link(self, name='booking') url = f'{url}&for-user={quote_plus(username)}' return Link(_("Add manual booking"), attrs={'class': 'new-booking'}, url=url)
def ensure_before_deadline(self): if self.request.is_admin: return True if self.model.is_past_deadline(date.today()): self.attendee.errors.append(_("The deadline has passed")) return False
def ensure_not_over_limit(self): if self.is_new_attendee: return True if self.model.period.confirmed: if self.model.exempt_from_booking_limit: limit = None elif self.model.period.booking_limit: limit = self.model.period.booking_limit else: limit = self.request.session.query(Attendee.limit)\ .filter(Attendee.id == self.attendee.data)\ .one()\ .limit else: limit = None if limit: bookings = self.booking_collection query = bookings.query().with_entities(Booking.id).join(Occasion) query = query.filter(Booking.attendee_id == self.attendee.data) query = query.filter(Booking.state == 'accepted') query = query.filter(Occasion.exempt_from_booking_limit == False) count = query.count() if count >= limit: self.attendee.errors.append( _(("The attendee already has already reached the maximum " "number of ${count} bookings"), mapping={'count': limit})) return False
def ensure_max_age_after_min_age(self): if self.min_age.data and self.max_age.data: if self.min_age.data > self.max_age.data: self.min_age.errors.append(_( "The minium age cannot be higher than the maximum age" )) return False
class PeriodSelectForm(Form): period = SelectField(label=_("Period"), validators=[InputRequired()], default='0xdeadbeef') @property def period_choices(self): q = PeriodCollection(self.request.session).query() q = q.with_entities(Period.id, Period.title) q = q.order_by(Period.execution_start) return [(row.id.hex, row.title) for row in q] @property def selected_period(self): return PeriodCollection(self.request.session).by_id(self.period.data) @property def active_period(self): return self.request.app.active_period def on_request(self): self.period.choices = self.period_choices if self.period.data == '0xdeadbeef' and self.active_period: self.period.data = self.active_period.id.hex