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()
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
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())))
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)
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()
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))
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)
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
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
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))
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))
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'])))
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))
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))
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
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))
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)
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
def handle_fossir_exception(exc): return render_error(exc, _('Something went wrong'), to_unicode(exc.message), getattr(exc, 'http_status_code', 500))
def handle_baddata(exc): return render_error(exc, _('Invalid or expired token'), to_unicode(exc.message), 400)
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)