def flush_rclone(self): if not self.rclone_remote or not self.rclone_queue: return click.echo() for name, data in self.buckets.viewitems(): if not data['exists']: self.create_bucket(name) data['exists'] = True for bucket, data in self.rclone_queue.viewitems(): click.echo( cformat( 'Copying %{cyan}{}%{reset} files (%{cyan}{}%{reset}) to %{cyan}{}%{reset} via rclone' ).format(data['files'], do_filesizeformat(data['bytes']), bucket)) start = datetime.now() try: subprocess.check_call([ 'rclone', 'copy', '--copy-links', data['path'], '{}:{}'.format(self.rclone_remote, bucket) ]) except subprocess.CalledProcessError: click.secho('\nError while running rclone', fg='red') raise duration = (datetime.now() - start) click.echo('...finished after {}'.format( format_human_timedelta(duration, 'minutes', narrow=True))) rmlinktree(data['path']) self.rclone_queue.clear()
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 validate_duration(self, field): super(BaseEntryForm, self).validate_duration(field) if self.entry.type == TimetableEntryType.SESSION_BLOCK: needed_duration = max(x.end_dt for x in self.entry.children) - min(x.start_dt for x in self.entry.children) if field.data < needed_duration: raise ValidationError(_("The duration must be at least {duration} to fit the entries within.") .format(duration=format_human_timedelta(needed_duration, 'minutes')))
def run(self): """Perform the rescheduling.""" if self.fit_blocks: self._fit_blocks() if self.mode == RescheduleMode.time: self._reschedule_time() elif self.mode == RescheduleMode.duration: self._reschedule_duration() db.session.flush() self.event.log( EventLogRealm.management, EventLogKind.change, 'Timetable', "Entries rescheduled", session.user, data={ 'Mode': self.mode.title, 'Day': format_date(self.day, locale='en_GB'), 'Fit Blocks': self.fit_blocks, 'Gap': format_human_timedelta(self.gap) if self.gap else None, 'Session': self.session.title if self.session else None, 'Session block': self.session_block.full_title if self.session_block else None })
def generate_spreadsheet_from_contributions(contributions): """Return a tuple consisting of spreadsheet columns and respective contribution values""" headers = ['Id', 'Title', 'Description', 'Date', 'Duration', 'Type', 'Session', 'Track', 'Presenters', 'Materials'] rows = [] for c in sorted(contributions, key=attrgetter('friendly_id')): contrib_data = {'Id': c.friendly_id, 'Title': c.title, 'Description': c.description, 'Duration': format_human_timedelta(c.duration), 'Date': format_datetime(c.timetable_entry.start_dt) if c.timetable_entry else None, 'Type': c.type.name if c.type else None, 'Session': c.session.title if c.session else None, 'Track': c.track.title if c.track else None, 'Materials': None, 'Presenters': ', '.join(speaker.person.full_name for speaker in c.speakers)} attachments = [] attached_items = get_attached_items(c) for attachment in attached_items.get('files', []): attachments.append(attachment.absolute_download_url) for folder in attached_items.get('folders', []): for attachment in folder.attachments: attachments.append(attachment.absolute_download_url) if attachments: contrib_data['Materials'] = ', '.join(attachments) rows.append(contrib_data) return headers, rows
def _render_template(self, template_name, kwargs): template_dir = os.path.join(get_root_path('indico'), 'legacy/pdfinterface/latex_templates') env = Environment(loader=FileSystemLoader(template_dir), autoescape=False, trim_blocks=True, keep_trailing_newline=True, auto_reload=config.DEBUG, extensions=[LatexEscapeExtension], undefined=StrictUndefined, block_start_string=r'\JINJA{', block_end_string='}', variable_start_string=r'\VAR{', variable_end_string='}', comment_start_string=r'\#{', comment_end_string='}') env.filters['format_date'] = EnsureUnicodeExtension.wrap_func( format_date) env.filters['format_time'] = EnsureUnicodeExtension.wrap_func( format_time) env.filters['format_duration'] = lambda delta: format_human_timedelta( delta, 'minutes') env.filters['latex'] = _latex_escape env.filters['rawlatex'] = RawLatex env.filters['markdown'] = kwargs.pop('markdown') env.globals['_'] = _ env.globals['ngettext'] = ngettext env.globals['session'] = session template = env.get_or_select_template(template_name) distribution = pkg_resources.get_distribution('indico-fonts') font_dir = os.path.join(distribution.location, 'indico_fonts', '') # XXX: trailing slash required return template.render(font_dir=font_dir, **kwargs)
def generate_spreadsheet_from_contributions(contributions): """Return a tuple consisting of spreadsheet columns and respective contribution values""" headers = ['Id', 'Title', 'Description', 'Date', 'Duration', 'Type', 'Session', 'Track', 'Presenters', 'Materials'] rows = [] for c in sorted(contributions, key=attrgetter('friendly_id')): contrib_data = {'Id': c.friendly_id, 'Title': c.title, 'Description': c.description, 'Duration': format_human_timedelta(c.duration), 'Date': c.timetable_entry.start_dt if c.timetable_entry else None, 'Type': c.type.name if c.type else None, 'Session': c.session.title if c.session else None, 'Track': c.track.title if c.track else None, 'Materials': None, 'Presenters': ', '.join(speaker.person.full_name for speaker in c.speakers)} attachments = [] attached_items = get_attached_items(c) for attachment in attached_items.get('files', []): attachments.append(attachment.absolute_download_url) for folder in attached_items.get('folders', []): for attachment in folder.attachments: attachments.append(attachment.absolute_download_url) if attachments: contrib_data['Materials'] = ', '.join(attachments) rows.append(contrib_data) return headers, rows
def _render_template(self, template_name, kwargs): template_dir = os.path.join(get_root_path('indico'), 'legacy/pdfinterface/latex_templates') env = Environment(loader=FileSystemLoader(template_dir), autoescape=False, trim_blocks=True, keep_trailing_newline=True, auto_reload=config.DEBUG, extensions=[LatexEscapeExtension], undefined=StrictUndefined, block_start_string=r'\JINJA{', block_end_string='}', variable_start_string=r'\VAR{', variable_end_string='}', comment_start_string=r'\#{', comment_end_string='}') env.filters['format_date'] = EnsureUnicodeExtension.wrap_func(format_date) env.filters['format_time'] = EnsureUnicodeExtension.wrap_func(format_time) env.filters['format_duration'] = lambda delta: format_human_timedelta(delta, 'minutes') env.filters['latex'] = _latex_escape env.filters['rawlatex'] = RawLatex env.filters['markdown'] = kwargs.pop('markdown') env.globals['_'] = _ env.globals['ngettext'] = ngettext env.globals['session'] = session template = env.get_or_select_template(template_name) distribution = pkg_resources.get_distribution('indico-fonts') font_dir = os.path.join(distribution.location, 'indico_fonts', '') # XXX: trailing slash required return template.render(font_dir=font_dir, **kwargs)
def _process(self): form = ContributionDurationForm(obj=FormDefaults(self.contrib), contrib=self.contrib) if form.validate_on_submit(): with track_time_changes(): update_contribution(self.contrib, {'duration': form.duration.data}) return jsonify_data(new_value=format_human_timedelta(self.contrib.duration)) return jsonify_form(form, back_button=False, disabled_until_change=True)
def verbose_iterator(iterable, total, get_id, get_title=None, print_every=10, print_total_time=False): """Iterate 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 :param print_every: after which number of items to update the progress :param print_total_time: whether to print the total time spent at the end """ term_width = terminal_size()[0] start_time = time.time() fmt = cformat( '[%{cyan!}{:6}%{reset}/%{cyan}{}%{reset} %{yellow!}{:.3f}%{reset}% %{green!}{}%{reset}] {:>8} %{grey!}{}' ) end_fmt = cformat( '[%{cyan!}{:6}%{reset}/%{cyan}{}%{reset} %{yellow!}{:.3f}%{reset}% %{green!}{}%{reset}] ' 'Total duration: %{green}{}') def _print_text(text): 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() 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)) minutes, seconds = divmod(remaining_seconds, 60) remaining = f'{minutes:02}:{seconds:02}' id_ = get_id(item) title = get_title(item).replace('\n', ' ') if get_title else '' text = fmt.format(i, total, (i / total * 100.0), remaining, id_, title) _print_text(text) yield item if print_total_time: total_duration = timedelta(seconds=(time.time() - start_time)) _print_text( end_fmt.format(total, total, 100, '00:00', format_human_timedelta(total_duration))) print()
def validate_duration(self, field): super(BaseEntryForm, self).validate_duration(field) if self.entry.type == TimetableEntryType.SESSION_BLOCK: needed_duration = max(x.end_dt for x in self.entry.children) - min( x.start_dt for x in self.entry.children) if field.data < needed_duration: raise ValidationError( _("The duration must be at least {duration} to fit the entries within." ).format(duration=format_human_timedelta( needed_duration, 'minutes')))
def _convert_data(event, value): if isinstance(value, timedelta): value = format_human_timedelta(value) elif isinstance(value, datetime): value = format_datetime(value, locale="en_GB", timezone=event.timezone) elif value.__class__.__name__ == "ContributionType": value = value._name elif value.__class__.__name__ == "AbstractFieldContent": value = '{}: "{}"'.format(convert_to_unicode(value.field._caption), convert_to_unicode(value.value)) return convert_to_unicode(value).strip()
def generate_spreadsheet_from_contributions(contributions): """ Return a tuple consisting of spreadsheet columns and respective contribution values. """ has_board_number = any(c.board_number for c in contributions) has_authors = any(pl.author_type != AuthorType.none for c in contributions for pl in c.person_links) headers = [ 'Id', 'Title', 'Description', 'Date', 'Duration', 'Type', 'Session', 'Track', 'Presenters', 'Materials' ] if has_authors: headers += ['Authors', 'Co-Authors'] if has_board_number: headers.append('Board number') rows = [] for c in sort_contribs(contributions, sort_by='friendly_id'): contrib_data = { 'Id': c.friendly_id, 'Title': c.title, 'Description': c.description, 'Duration': format_human_timedelta(c.duration), 'Date': c.timetable_entry.start_dt if c.timetable_entry else None, 'Type': c.type.name if c.type else None, 'Session': c.session.title if c.session else None, 'Track': c.track.title if c.track else None, 'Materials': None, 'Presenters': ', '.join(speaker.full_name for speaker in c.speakers) } if has_authors: contrib_data.update({ 'Authors': ', '.join(author.full_name for author in c.primary_authors), 'Co-Authors': ', '.join(author.full_name for author in c.secondary_authors) }) if has_board_number: contrib_data['Board number'] = c.board_number attachments = [] attached_items = get_attached_items(c) for attachment in attached_items.get('files', []): attachments.append(attachment.absolute_download_url) for folder in attached_items.get('folders', []): for attachment in folder.attachments: attachments.append(attachment.absolute_download_url) if attachments: contrib_data['Materials'] = ', '.join(attachments) rows.append(contrib_data) return headers, rows
def _convert_data(event, value): if isinstance(value, timedelta): value = format_human_timedelta(value) elif isinstance(value, datetime): value = format_datetime(value, locale='en_GB', timezone=event.timezone) elif value.__class__.__name__ == 'ContributionType': value = value._name elif value.__class__.__name__ == 'AbstractFieldContent': value = '{}: "{}"'.format(convert_to_unicode(value.field._caption), convert_to_unicode(value.value)) return convert_to_unicode(value).strip()
def validate_end_dt(self, field): if not self.check_timetable_boundaries: return 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))
def run(self): """Perform the rescheduling""" if self.fit_blocks: self._fit_blocks() if self.mode == RescheduleMode.time: self._reschedule_time() elif self.mode == RescheduleMode.duration: self._reschedule_duration() db.session.flush() self.event.log(EventLogRealm.management, EventLogKind.change, 'Timetable', "Entries rescheduled", session.user, data={'Mode': self.mode.title, 'Day': format_date(self.day, locale='en_GB'), 'Fit Blocks': self.fit_blocks, 'Gap': format_human_timedelta(self.gap) if self.gap else None, 'Session': self.session.title if self.session else None, 'Session block': self.session_block.full_title if self.session_block else None})
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 run(self): """Perform the rescheduling""" if self.fit_blocks: self._fit_blocks() if self.mode == RescheduleMode.time: self._reschedule_time() elif self.mode == RescheduleMode.duration: self._reschedule_duration() db.session.flush() self.event.log( EventLogRealm.management, EventLogKind.change, "Timetable", "Entries rescheduled", session.user, data={ "Mode": self.mode.title, "Day": format_date(self.day, locale="en_GB"), "Fit Blocks": self.fit_blocks, "Gap": format_human_timedelta(self.gap) if self.gap else None, "Session": self.session.title if self.session else None, "Session block": self.session_block.full_title if self.session_block else None, }, )
def flush_rclone(self): if not self.rclone_remote or not self.rclone_queue: return click.echo() for name, data in self.buckets.viewitems(): if not data['exists']: self.create_bucket(name) data['exists'] = True for bucket, data in self.rclone_queue.viewitems(): click.echo(cformat('Copying %{cyan}{}%{reset} files (%{cyan}{}%{reset}) to %{cyan}{}%{reset} via rclone') .format(data['files'], do_filesizeformat(data['bytes']), bucket)) start = datetime.now() try: subprocess.check_call([ 'rclone', 'copy', '--copy-links', data['path'], '{}:{}'.format(self.rclone_remote, bucket) ]) except subprocess.CalledProcessError: click.secho('\nError while running rclone', fg='red') raise duration = (datetime.now() - start) click.echo('...finished after {}'.format(format_human_timedelta(duration, 'minutes', narrow=True))) rmlinktree(data['path']) self.rclone_queue.clear()
def test_format_human_timedelta(delta, granularity, expected): assert format_human_timedelta(delta, granularity) == expected
def __call__(self, form, field): if field.data is not None and field.data > self.max_duration: raise ValidationError( _('Duration cannot exceed {}').format( format_human_timedelta(self.max_duration)))
def __call__(self, form, field): if field.data is not None and field.data > self.max_duration: raise ValidationError(_('Duration cannot exceed {}').format(format_human_timedelta(self.max_duration)))