Example #1
0
 def _get_email_subject(self, **mail_params):
     return u'{prefix}[{room}] {subject} {date} {suffix}'.format(
         prefix=to_unicode(mail_params.get('subject_prefix', '')),
         room=self.reservation.room.full_name,
         subject=to_unicode(mail_params.get('subject', '')),
         date=to_unicode(self.start_dt),
         suffix=to_unicode(mail_params.get('subject_suffix', ''))).strip()
Example #2
0
def make_email(to_list=None, cc_list=None, bcc_list=None, from_address=None, reply_address=None, attachments=None,
               subject=None, body=None, template=None, html=False):
    """Creates an email.

    The preferred way to specify the email content is using the
    `template` argument. To do so, use :func:`.get_template_module` on
    a template inheriting from ``emails/base.txt`` for test emails or
    ``emails/base.html`` for HTML emails.

    :param to_list: The recipient email or a collection of emails
    :param cc_list: The CC email or a collection of emails
    :param bcc_list: The BCC email or a collection of emails
    :param from_address: The sender address. Defaults to noreply.
    :param reply_address: The reply-to address or a collection of addresses.
                          Defaults to empty.
    :param attachments: A list of attachments. Each attachment can be
                        a `MIMEBase` subclass, a 3-tuple of the form
                        ``(filename, content, mimetype)``, or a 2-tuple
                        ``(filename, content)`` in which case the mime
                        type will be guessed from the file name.
    :param subject: The subject of the email.
    :param body: The body of the email:
    :param template: A template module containing ``get_subject`` and
                     ``get_body`` macros.
    :param html: ``True`` if the email body is HTML
    """
    if template is not None and (subject is not None or body is not None):
        raise ValueError("Only subject/body or template can be passed")

    if template:
        subject = template.get_subject()
        body = template.get_body()
    if config.DEBUG and '\n' in subject:
        raise ValueError('Email subject contains linebreaks')
    subject = re.sub(r'\s+', ' ', subject)
    if to_list is None:
        to_list = set()
    if cc_list is None:
        cc_list = set()
    if bcc_list is None:
        bcc_list = set()
    to_list = {to_list} if isinstance(to_list, basestring) else to_list
    cc_list = {cc_list} if isinstance(cc_list, basestring) else cc_list
    bcc_list = {bcc_list} if isinstance(bcc_list, basestring) else bcc_list
    reply_address = {reply_address} if isinstance(reply_address, basestring) else (reply_address or set())
    return {
        'to': set(map(to_unicode, to_list)),
        'cc': set(map(to_unicode, cc_list)),
        'bcc': set(map(to_unicode, bcc_list)),
        'from': to_unicode(from_address or config.NO_REPLY_EMAIL),
        'reply_to': set(map(to_unicode, reply_address)),
        'attachments': attachments or [],
        'subject': to_unicode(subject).strip(),
        'body': to_unicode(body).strip(),
        'html': html,
    }
 def render(cls, event):
     start_dt, end_dt = event.start_dt_local, event.end_dt_local
     interval = _("{} to {}").format(
         to_unicode(format_date(start_dt, format='long')),
         to_unicode(format_date(end_dt, format='long')))
     if start_dt.date() == end_dt.date():
         interval = to_unicode(format_datetime(start_dt))
     elif start_dt.date().replace(day=1) == end_dt.date().replace(day=1):
         interval = "{} - {} {}".format(
             start_dt.day, end_dt.day,
             to_unicode(format_date(start_dt, format='MMMM yyyy')))
     return interval
def handle_exception(exc, message=None):
    Logger.get('flask').exception(
        to_unicode(exc.message) or 'Uncaught Exception')
    if not current_app.debug or request.is_xhr or request.is_json:
        sentry_log_exception()
        if message is None:
            message = '{}: {}'.format(
                type(exc).__name__, to_unicode(exc.message))
        return render_error(exc, _('Something went wrong'), message, 500)
    # Let the exception propagate to middleware /the webserver.
    # This triggers the Flask debugger in development and sentry
    # logging (if enabled) (via got_request_exception).
    raise
Example #5
0
    def get_allowed_sender_emails(self, include_current_user=True, include_creator=True, include_managers=True,
                                  include_contact=True, include_chairs=True, extra=None):
        """
        Return the emails of people who can be used as senders (or
        rather Reply-to contacts) in emails sent from within an event.

        :param include_current_user: Whether to include the email of
                                     the currently logged-in user
        :param include_creator: Whether to include the email of the
                                event creator
        :param include_managers: Whether to include the email of all
                                 event managers
        :param include_contact: Whether to include the "event contact"
                                emails
        :param include_chairs: Whether to include the emails of event
                               chairpersons (or lecture speakers)
        :param extra: An email address that is always included, even
                      if it is not in any of the included lists.
        :return: An OrderedDict mapping emails to pretty names
        """
        emails = {}
        # Contact/Support
        if include_contact:
            for email in self.contact_emails:
                emails[email] = self.contact_title
        # Current user
        if include_current_user and has_request_context() and session.user:
            emails[session.user.email] = session.user.full_name
        # Creator
        if include_creator:
            emails[self.creator.email] = self.creator.full_name
        # Managers
        if include_managers:
            emails.update((p.principal.email, p.principal.full_name)
                          for p in self.acl_entries
                          if p.type == PrincipalType.user and p.full_access)
        # Chairs
        if include_chairs:
            emails.update((pl.email, pl.full_name) for pl in self.person_links if pl.email)
        # Extra email (e.g. the current value in an object from the DB)
        if extra:
            emails.setdefault(extra, extra)
        # Sanitize and format emails
        emails = {to_unicode(email.strip().lower()): '{} <{}>'.format(to_unicode(name), to_unicode(email))
                  for email, name in emails.iteritems()
                  if email and email.strip()}
        own_email = session.user.email if has_request_context() and session.user else None
        return OrderedDict(sorted(emails.items(), key=lambda x: (x[0] != own_email, x[1].lower())))
Example #6
0
 def validate_end_dt(self, field):
     if not self.check_timetable_boundaries:
         return
     if self.update_timetable.data:
         # if we move timetable entries according to the start date
         # change, check that there's enough time at the end.
         start_dt_offset = self.start_dt.data - self.start_dt.object_data
         end_buffer = field.data - max(self.toplevel_timetable_entries,
                                       key=attrgetter('end_dt')).end_dt
         delta = max(timedelta(), start_dt_offset - end_buffer)
         if delta:
             delta_str = format_human_timedelta(delta, 'minutes', True)
             raise ValidationError(
                 _("The event is too short to fit all timetable entries. "
                   "It must be at least {} longer.").format(delta_str))
     else:
         # if we do not update timetable entries, only check that
         # the event does not end before its last timetable entry;
         # a similar check for the start time is done above in that
         # field's validation method.
         max_end_dt = max(self.toplevel_timetable_entries,
                          key=attrgetter('end_dt')).end_dt
         if field.data < max_end_dt:
             raise ValidationError(
                 _("The event cannot end before its last timetable entry, which is at {}."
                   ).format(
                       to_unicode(
                           format_datetime(max_end_dt,
                                           timezone=self.event.tzinfo))))
    def _get_base_path(self, attachment):
        # TODO: adapt to new models (needs extra properties to use event TZ)
        obj = linked_object = attachment.folder.object
        paths = []
        while obj != self.event:
            start_date = _get_start_dt(obj)
            if start_date is not None:
                if isinstance(obj, SubContribution):
                    paths.append(
                        secure_filename(
                            '{}_{}'.format(obj.position, obj.title), ''))
                else:
                    time = format_time(start_date,
                                       format='HHmm',
                                       timezone=self.event.timezone)
                    paths.append(
                        secure_filename(
                            '{}_{}'.format(to_unicode(time), obj.title), ''))
            else:
                if isinstance(obj, SubContribution):
                    paths.append(
                        secure_filename(
                            '{}){}'.format(obj.position, obj.title),
                            unicode(obj.id)))
                else:
                    paths.append(secure_filename(obj.title, unicode(obj.id)))
            obj = _get_obj_parent(obj)

        linked_obj_start_date = _get_start_dt(linked_object)
        if attachment.folder.object != self.event and linked_obj_start_date is not None:
            paths.append(
                secure_filename(linked_obj_start_date.strftime('%Y%m%d_%A'),
                                ''))

        return reversed(paths)
Example #8
0
def verbose_iterator(iterable, total, get_id, get_title, print_every=10):
    """Iterates large iterables verbosely

    :param iterable: An iterable
    :param total: The number of items in `iterable`
    :param get_id: callable to retrieve the ID of an item
    :param get_title: callable to retrieve the title of an item
    """
    term_width = terminal_size()[0]
    start_time = time.time()
    fmt = cformat(
        '[%{cyan!}{:6}%{reset}/%{cyan}{}%{reset}  %{yellow!}{:.3f}%{reset}%  %{green!}{}%{reset}]  {:>8}  %{grey!}{}'
    )

    for i, item in enumerate(iterable, 1):
        if i % print_every == 0 or i == total:
            remaining_seconds = int((time.time() - start_time) / i * (total - i))
            remaining = '{:02}:{:02}'.format(remaining_seconds // 60, remaining_seconds % 60)
            id_ = get_id(item)
            title = to_unicode(get_title(item).replace('\n', ' '))
            text = fmt.format(i, total, (i / total * 100.0), remaining, id_, title)
            print('\r', ' ' * term_width, end='', sep='')
            # terminal width + ansi control code length - trailing reset code (4)
            print('\r', text[:term_width + len(text.value_colors) - len(text.value_no_colors) - 4], cformat('%{reset}'),
                  end='', sep='')
            sys.stdout.flush()

        yield item

    print()
Example #9
0
 def format_event_date(self, event):
     day_month = 'dd MMM'
     tzinfo = self.category.display_tzinfo
     start_dt = event.start_dt.astimezone(tzinfo)
     end_dt = event.end_dt.astimezone(tzinfo)
     if start_dt.year != end_dt.year:
         return '{} - {}'.format(
             to_unicode(format_date(start_dt, timezone=tzinfo)),
             to_unicode(format_date(end_dt, timezone=tzinfo)))
     elif (start_dt.month != end_dt.month) or (start_dt.day != end_dt.day):
         return '{} - {}'.format(
             to_unicode(format_date(start_dt, day_month, timezone=tzinfo)),
             to_unicode(format_date(end_dt, day_month, timezone=tzinfo)))
     else:
         return to_unicode(format_date(start_dt, day_month,
                                       timezone=tzinfo))
Example #10
0
def get_time_changes_notifications(changes, tzinfo, entry=None):
    notifications = []
    for obj, change in changes.iteritems():
        if entry:
            if entry.object == obj:
                continue
            if not isinstance(obj, Event) and obj.timetable_entry in entry.children:
                continue
        msg = None
        if isinstance(obj, Event):
            if 'start_dt' in change:
                new_time = change['start_dt'][1]
                msg = _("Event start time changed to {}")
            elif 'end_dt' in change:
                new_time = change['end_dt'][1]
                msg = _("Event end time changed to {}")
            else:
                raise ValueError("Invalid change in event.")
        elif isinstance(obj, SessionBlock):
            if 'start_dt' in change:
                new_time = change['start_dt'][1]
                msg = _("Session block start time changed to {}")
            elif 'end_dt' in change:
                new_time = change['end_dt'][1]
                msg = _("Session block end time changed to {}")
            else:
                raise ValueError("Invalid change in session block.")
        if msg:
            notifications.append(msg.format(to_unicode(format_time(new_time, timezone=tzinfo))))
    return notifications
    def _process(self):
        reminder = self.reminder
        form = ReminderForm(obj=self._get_defaults(), event=self.event)
        if form.validate_on_submit():
            if reminder.is_sent:
                flash(
                    _("This reminder has already been sent and cannot be modified anymore."
                      ), 'error')
                return redirect(url_for('.edit', reminder))
            form.populate_obj(reminder, existing_only=True)
            if form.schedule_type.data == 'now':
                _send_reminder(reminder)
            else:
                logger.info('Reminder modified by %s: %s', session.user,
                            reminder)
                flash(
                    _("The reminder at {} has been modified.").format(
                        to_unicode(format_datetime(reminder.scheduled_dt))),
                    'success')
            return jsonify_data(flash=False)

        return jsonify_template('events/reminders/edit_reminder.html',
                                event=self.event,
                                reminder=reminder,
                                form=form)
Example #12
0
        def _iterate_objs(query_string):
            query = (Event.query.filter(
                Event.title_matches(to_unicode(query_string)),
                ~Event.is_deleted).options(
                    undefer('effective_protection_mode')))
            sort_dir = db.desc if self._descending else db.asc
            if self._orderBy == 'start':
                query = query.order_by(sort_dir(Event.start_dt))
            elif self._orderBy == 'end':
                query = query.order_by(sort_dir(Event.end_dt))
            elif self._orderBy == 'id':
                query = query.order_by(sort_dir(Event.id))
            elif self._orderBy == 'title':
                query = query.order_by(sort_dir(db.func.lower(Event.title)))

            counter = 0
            # Query the DB in chunks of 1000 records per query until the limit is satisfied
            for event in query.yield_per(1000):
                if event.can_access(self._user):
                    counter += 1
                    # Start yielding only when the counter reaches the given offset
                    if (self._offset is None) or (counter > self._offset):
                        yield event
                        # Stop querying the DB when the limit is satisfied
                        if (self._limit is not None) and (
                                counter == self._offset + self._limit):
                            break
Example #13
0
def restrictedHTML(txt, sanitizationLevel):
    try:
        parser = RestrictedHTMLParser(sanitizationLevel)
        parser.feed(to_unicode(txt) + u'>')
        parser.close()
    except (HarmfulHTMLException, HTMLParseError) as exc:
        return exc.msg
    return None
Example #14
0
    def _execute(self, fossils):
        results = fossils['results']
        if type(results) != list:
            results = [results]

        feed = AtomFeed(
            title='fossir Feed',
            feed_url=fossils['url']
        )

        for fossil in results:
            feed.add(
                title=to_unicode(fossil['title']) or None,
                summary=to_unicode(fossil['description']) or None,
                url=fossil['url'],
                updated=_deserialize_date(fossil['startDate'])  # ugh, but that's better than creationDate
            )
        return feed.to_string()
def unaccent_match(column, value, exact):
    from fossir.core.db import db
    value = to_unicode(value).replace('%', r'\%').replace('_', r'\_').lower()
    if not exact:
        value = '%{}%'.format(value)
    # we always use LIKE, even for an exact match. when using the pg_trgm indexes this is
    # actually faster than `=`
    return db.func.fossir.fossir_unaccent(db.func.lower(column)).ilike(
        db.func.fossir.fossir_unaccent(value))
Example #16
0
def create(grant_admin):
    """Creates a new user"""
    user_type = 'user' if not grant_admin else 'admin'
    while True:
        email = prompt_email()
        if email is None:
            return
        email = email.lower()
        if not User.find(User.all_emails.contains(email), ~User.is_deleted,
                         ~User.is_pending).count():
            break
        print(cformat('%{red}Email already exists'))
    first_name = click.prompt("First name").strip()
    last_name = click.prompt("Last name").strip()
    affiliation = click.prompt("Affiliation", '').strip()
    print()
    while True:
        username = click.prompt("Enter username").lower().strip()
        if not Identity.find(provider='fossir', identifier=username).count():
            break
        print(cformat('%{red}Username already exists'))
    password = prompt_pass()
    if password is None:
        return

    identity = Identity(provider='fossir',
                        identifier=username,
                        password=password)
    user = create_user(
        email, {
            'first_name': to_unicode(first_name),
            'last_name': to_unicode(last_name),
            'affiliation': to_unicode(affiliation)
        }, identity)
    user.is_admin = grant_admin
    _print_user_info(user)

    if click.confirm(cformat("%{yellow}Create the new {}?").format(user_type),
                     default=True):
        db.session.add(user)
        db.session.commit()
        print(
            cformat("%{green}New {} created successfully with ID: %{green!}{}"
                    ).format(user_type, user.id))
Example #17
0
 def _getAnswer(self):
     event_persons = []
     criteria = {
         'surName': self._surName,
         'name': self._name,
         'organisation': self._organisation,
         'email': self._email
     }
     users = search_avatars(criteria, self._exactMatch, self._searchExt)
     if self._event:
         fields = {EventPerson.first_name: self._name,
                   EventPerson.last_name: self._surName,
                   EventPerson.email: self._email,
                   EventPerson.affiliation: self._organisation}
         criteria = [unaccent_match(col, val, exact=self._exactMatch) for col, val in fields.iteritems()]
         event_persons = self._event.persons.filter(*criteria).all()
     fossilized_users = fossilize(sorted(users, key=lambda av: (av.getStraightFullName(), av.getEmail())))
     fossilized_event_persons = map(serialize_event_person, event_persons)
     unique_users = {to_unicode(user['email']): user for user in chain(fossilized_users, fossilized_event_persons)}
     return sorted(unique_users.values(), key=lambda x: (to_unicode(x['name']).lower(), to_unicode(x['email'])))
Example #18
0
def redirect_to_login(next_url=None, reason=None):
    """Redirects to the login page.

    :param next_url: URL to be redirected upon successful login. If not
                     specified, it will be set to ``request.relative_url``.
    :param reason: Why the user is redirected to a login page.
    """
    if not next_url:
        next_url = request.relative_url
    if reason:
        session['login_reason'] = to_unicode(reason)
    return redirect(url_for_login(next_url))
Example #19
0
def get_error_description(exception):
    """Gets a user-friendy description for an exception

    This overrides some HTTPException messages to be more suitable
    for end-users.
    """
    try:
        description = exception.description
    except AttributeError:
        return to_unicode(exception.message)
    if isinstance(exception,
                  Forbidden) and description == Forbidden.description:
        return _(u"You are not allowed to access this page.")
    elif isinstance(exception,
                    NotFound) and description == NotFound.description:
        return _(u"The page you are looking for doesn't exist.")
    elif isinstance(exception,
                    BadRequest) and description == BadRequest.description:
        return _(u"The request was invalid or contained invalid arguments.")
    else:
        return to_unicode(description)
 def _process(self):
     if self.reminder.is_sent:
         flash(_('Sent reminders cannot be deleted.'), 'error')
     else:
         db.session.delete(self.reminder)
         logger.info('Reminder deleted by %s: %s', session.user,
                     self.reminder)
         flash(
             _("The reminder at {} has been deleted.").format(
                 to_unicode(format_datetime(self.reminder.scheduled_dt))),
             'success')
     return redirect(url_for('.list', self.event))
Example #21
0
def secure_filename(filename, fallback):
    """Returns a secure version of a filename.

    This removes possibly dangerous characters and also converts the
    filename to plain ASCII for maximum compatibility.

    :param filename: A filename
    :param fallback: The filename to use if there were no safe chars
                     in the original filename.
    """
    if not filename:
        return fallback
    return _secure_filename(unicode_to_ascii(to_unicode(filename))) or fallback
Example #22
0
 def _process(self):
     f = request.files['css_file']
     self.event.stylesheet = to_unicode(f.read()).strip()
     self.event.stylesheet_metadata = {
         'hash': crc32(self.event.stylesheet),
         'size': len(self.event.stylesheet),
         'filename': secure_filename(f.filename, 'stylesheet.css')
     }
     db.session.flush()
     flash(_('New CSS file saved. Do not forget to enable it ("Use custom CSS") after verifying that it is correct '
             'using the preview.'), 'success')
     logger.info('CSS file for %s uploaded by %s', self.event, session.user)
     return jsonify_data(content=get_css_file_data(self.event))
Example #23
0
def _log_email(email, event, module, user):
    from fossir.modules.events.logs import EventLogKind, EventLogRealm
    if not event:
        return None
    log_data = {
        'content_type': 'text/html' if email['html'] else 'text/plain',
        'from': email['from'],
        'to': sorted(email['to']),
        'cc': sorted(email['cc']),
        'bcc': sorted(email['bcc']),
        'subject': email['subject'],
        'body': email['body'].strip(),
        'state': 'pending',
        'sent_dt': None,
    }
    return event.log(EventLogRealm.emails, EventLogKind.other, to_unicode(module or 'Unknown'), log_data['subject'],
                     user, type_='email', data=log_data)
    def _process(self):
        form = ReminderForm(event=self.event, schedule_type='relative')
        if form.validate_on_submit():
            reminder = EventReminder(creator=session.user, event=self.event)
            form.populate_obj(reminder, existing_only=True)
            db.session.add(reminder)
            db.session.flush()
            if form.schedule_type.data == 'now':
                _send_reminder(reminder)
            else:
                logger.info('Reminder created by %s: %s', session.user,
                            reminder)
                flash(
                    _("A reminder at {} has been created.").format(
                        to_unicode(format_datetime(reminder.scheduled_dt))),
                    'success')
            return jsonify_data(flash=False)

        return jsonify_template('events/reminders/edit_reminder.html',
                                event=self.event,
                                reminder=None,
                                form=form,
                                widget_attrs=form.default_widget_attrs)
Example #25
0
 def _process(self):
     form = AddAttachmentFilesForm(linked_object=self.object)
     if form.validate_on_submit():
         files = form.files.data
         folder = form.folder.data or AttachmentFolder.get_or_create_default(linked_object=self.object)
         for f in files:
             filename = secure_filename(f.filename, 'attachment')
             attachment = Attachment(folder=folder, user=session.user, title=to_unicode(f.filename),
                                     type=AttachmentType.file, protection_mode=form.protection_mode.data)
             if attachment.is_self_protected:
                 attachment.acl = form.acl.data
             content_type = mimetypes.guess_type(f.filename)[0] or f.mimetype or 'application/octet-stream'
             attachment.file = AttachmentFile(user=session.user, filename=filename, content_type=content_type)
             attachment.file.save(f.stream)
             db.session.add(attachment)
             db.session.flush()
             logger.info('Attachment %s uploaded by %s', attachment, session.user)
             signals.attachments.attachment_created.send(attachment, user=session.user)
         flash(ngettext("The attachment has been uploaded", "{count} attachments have been uploaded", len(files))
               .format(count=len(files)), 'success')
         return jsonify_data(attachment_list=_render_attachment_list(self.object))
     return jsonify_template('attachments/upload.html', form=form, action=url_for('.upload', self.object),
                             protection_message=_render_protection_message(self.object),
                             folders_protection_info=_get_folders_protection_info(self.object))
def force_text(val):
    if is_lazy_string(val):
        val = val.value
    return to_unicode(val)
    def modify(self, data, user):
        """Modifies an existing reservation.

        :param data: A dict containing the booking data, usually from a :class:`ModifyBookingForm` instance
        :param user: The :class:`.User` who modifies the booking.
        """

        populate_fields = ('start_dt', 'end_dt', 'repeat_frequency', 'repeat_interval', 'booked_for_user',
                           'contact_email', 'contact_phone', 'booking_reason', 'used_equipment',
                           'needs_assistance', 'uses_vc', 'needs_vc_assistance')
        # fields affecting occurrences
        occurrence_fields = {'start_dt', 'end_dt', 'repeat_frequency', 'repeat_interval'}
        # fields where date and time are compared separately
        date_time_fields = {'start_dt', 'end_dt'}
        # fields for the repetition
        repetition_fields = {'repeat_frequency', 'repeat_interval'}
        # pretty names for logging
        field_names = {
            'start_dt/date': u"start date",
            'end_dt/date': u"end date",
            'start_dt/time': u"start time",
            'end_dt/time': u"end time",
            'repetition': u"booking type",
            'booked_for_user': u"'Booked for' user",
            'contact_email': u"contact email",
            'contact_phone': u"contact phone number",
            'booking_reason': u"booking reason",
            'used_equipment': u"list of equipment",
            'needs_assistance': u"option 'General Assistance'",
            'uses_vc': u"option 'Uses Videoconference'",
            'needs_vc_assistance': u"option 'Videoconference Setup Assistance'"
        }

        self.room.check_advance_days(data['end_dt'].date(), user)
        self.room.check_bookable_hours(data['start_dt'].time(), data['end_dt'].time(), user)
        if data['room_usage'] == 'current_user':
            data['booked_for_user'] = session.user

        changes = {}
        update_occurrences = False
        old_repetition = self.repetition

        for field in populate_fields:
            if field not in data:
                continue
            old = getattr(self, field)
            new = data[field]
            converter = unicode
            if field == 'used_equipment':
                # Dynamic relationship
                old = sorted(old.all())
                converter = lambda x: u', '.join(x.name for x in x)
            if old != new:
                # Booked for user updates the (redundant) name
                if field == 'booked_for_user':
                    old = self.booked_for_name
                    new = self.booked_for_name = data[field].full_name
                # Apply the change
                setattr(self, field, data[field])
                # If any occurrence-related field changed we need to recreate the occurrences
                if field in occurrence_fields:
                    update_occurrences = True
                # Record change for history entry
                if field in date_time_fields:
                    # The date/time fields create separate entries for the date and time parts
                    if old.date() != new.date():
                        changes[field + '/date'] = {'old': old.date(), 'new': new.date(), 'converter': format_date}
                    if old.time() != new.time():
                        changes[field + '/time'] = {'old': old.time(), 'new': new.time(), 'converter': format_time}
                elif field in repetition_fields:
                    # Repetition needs special handling since it consists of two fields but they are tied together
                    # We simply update it whenever we encounter such a change; after the last change we end up with
                    # the correct change data
                    changes['repetition'] = {'old': old_repetition,
                                             'new': self.repetition,
                                             'converter': lambda x: RepeatMapping.get_message(*x)}
                else:
                    changes[field] = {'old': old, 'new': new, 'converter': converter}

        if not changes:
            return False

        # Create a verbose log entry for the modification
        log = [u'Booking modified']
        for field, change in changes.iteritems():
            field_title = field_names.get(field, field)
            converter = change['converter']
            old = to_unicode(converter(change['old']))
            new = to_unicode(converter(change['new']))
            if not old:
                log.append(u"The {} was set to '{}'".format(field_title, new))
            elif not new:
                log.append(u"The {} was cleared".format(field_title))
            else:
                log.append(u"The {} was changed from '{}' to '{}'".format(field_title, old, new))

        self.edit_logs.append(ReservationEditLog(user_name=user.full_name, info=log))

        # Recreate all occurrences if necessary
        if update_occurrences:
            cols = [col.name for col in ReservationOccurrence.__table__.columns
                    if not col.primary_key and col.name not in {'start_dt', 'end_dt'}]

            old_occurrences = {occ.date: occ for occ in self.occurrences}
            self.occurrences.delete(synchronize_session='fetch')
            self.create_occurrences(True, user)
            db.session.flush()
            # Restore rejection data etc. for recreated occurrences
            for occurrence in self.occurrences:
                old_occurrence = old_occurrences.get(occurrence.date)
                # Copy data from old occurrence UNLESS the new one is invalid (e.g. because of collisions)
                # Otherwise we'd end up with valid occurrences ignoring collisions!
                if old_occurrence and occurrence.is_valid:
                    for col in cols:
                        setattr(occurrence, col, getattr(old_occurrence, col))
            # Don't cause new notifications for the entire booking in case of daily repetition
            if self.repeat_frequency == RepeatFrequency.DAY and all(occ.notification_sent
                                                                    for occ in old_occurrences.itervalues()):
                for occurrence in self.occurrences:
                    occurrence.notification_sent = True

        # Sanity check so we don't end up with an "empty" booking
        if not any(occ.is_valid for occ in self.occurrences):
            raise NoReportError(_(u'Reservation has no valid occurrences'))

        notify_modification(self, changes)
        return True
Example #28
0
def handle_fossir_exception(exc):
    return render_error(exc, _('Something went wrong'),
                        to_unicode(exc.message),
                        getattr(exc, 'http_status_code', 500))
Example #29
0
def handle_baddata(exc):
    return render_error(exc, _('Invalid or expired token'),
                        to_unicode(exc.message), 400)
Example #30
0
def handle_badrequestkeyerror(exc):
    if current_app.debug:
        raise
    msg = _('Required argument missing: {}').format(to_unicode(exc.message))
    return render_error(exc, exc.name, msg, exc.code)