Пример #1
0
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)
        }))
Пример #2
0
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
    }
Пример #3
0
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,
    }
Пример #4
0
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,
    }
Пример #5
0
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))
Пример #6
0
 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"), '#'))
Пример #7
0
 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, '#'))
Пример #8
0
    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
        )
Пример #9
0
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
    }
Пример #10
0
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()
Пример #11
0
    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})
Пример #12
0
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")
    }
Пример #13
0
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"))
Пример #14
0
    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
Пример #15
0
    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
Пример #16
0
    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
Пример #17
0
 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"))
Пример #18
0
    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)
Пример #19
0
    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']
Пример #20
0
    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)
Пример #21
0
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})
Пример #22
0
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
Пример #23
0
    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)
Пример #24
0
    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)))
Пример #25
0
    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
Пример #26
0
    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)
Пример #27
0
    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
Пример #28
0
    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
Пример #29
0
 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
Пример #30
0
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