def view_notices_statistics_xlsx(self, request): """ View the statistics as XLSX. """ output = BytesIO() workbook = Workbook(output) for title, row, content in ( (_("Organizations"), _("Organization"), self.count_by_organization), (_("Categories"), _("Category"), self.count_by_category), (_("Groups"), _("Group"), self.count_by_group), (_("Rejected"), _("Name"), self.count_rejected), ): worksheet = workbook.add_worksheet() worksheet.name = request.translate(title) worksheet.write_row(0, 0, ( request.translate(row), request.translate(_("Count")) )) for index, row in enumerate(content()): worksheet.write_row(index + 1, 0, row) workbook.close() output.seek(0) response = Response() response.content_type = ( 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response.content_disposition = 'inline; filename={}-{}-{}.xlsx'.format( request.translate(_("Statistics")).lower(), normalize_for_url(request.translate(TRANSLATIONS.get(self.state, ''))), datetime.utcnow().strftime('%Y%m%d%H%M') ) response.body = output.read() return response
def id(self): """ An ID based on the title. """ id = normalize_for_url(self.title.data or 'page') query = self.request.session.query(TranslatablePage) while query.filter_by(id=id).first(): id = increment_name(id) return id
def attendees_by_username(request, username): """ Loads the given attendees linked to the given username, sorted by their name. """ a = AttendeeCollection(request.session).by_username(username).all() a.sort(key=lambda a: normalize_for_url(a.name)) return a
def get_unique_notice_name(name, session, model_class): """ Create a unique, URL-friendly name. """ # it's possible for `normalize_for_url` to return an empty string... name = normalize_for_url(name) or "notice" while session.query(model_class.name).\ filter(model_class.name == name).first(): name = increment_name(name) return name
def id_from_title(self, session): """ Returns a unique, user friendly id derived from the title. """ title = self.get_title(self.session_manager.default_locale) id = normalize_for_url(title or self.__class__.__name__) while True: query = session.query(self.polymorphic_base).filter_by(id=id) items = [item for item in query if item != self] if not items: return id id = increment_name(id)
def svg_name(self): """ Returns a nice to read SVG filename. """ return '{}.svg'.format( normalize_for_url( '{}-{}'.format( self.model.id, self.request.translate(self.title() or '') ) ) )
def add(self, title, html, lead=None, meta=None, content=None): name = normalize_for_url(title) if self.by_name(name): raise AlreadyExistsError(name) newsletter = Newsletter( name=normalize_for_url(title), title=title, html=html, lead=lead, meta=meta or {}, content=content or {} ) self.session.add(newsletter) self.session.flush() return newsletter
def _get_unique_name(self, name): """ Create a unique, URL-friendly name. """ # it's possible for `normalize_for_url` to return an empty string... name = normalize_for_url(name) or "event" session = self.session while session.query(Event.name).filter(Event.name == name).first(): name = increment_name(name) return name
def parse_definition(definition): for service in yaml.safe_load(definition): service_id = normalize_for_url(service['titel']) days = (d.strip() for d in service['tage'].split(',')) yield service_id, Bunch( id=service_id, title=service['titel'], percentage=Decimal(service['prozent']), days=OrderedSet(SERVICE_DAYS[d.lower()[:2]] for d in days), )
def preview_notice_pdf(self, request): """ Preview the notice as PDF. """ pdf = NoticesPdf.from_notice(self, request) filename = normalize_for_url('{}-{}-{}'.format( request.translate(_("Gazette")), request.app.principal.name, self.title)) return Response(pdf.read(), content_type='application/pdf', content_disposition=f'inline; filename={filename}.pdf')
def view_election_compound_data_as_csv(self, request): """ View the raw data as CSV. """ @request.after def add_last_modified(response): add_last_modified_header(response, self.last_modified) return Response( convert_list_of_dicts_to_csv(self.export()), content_type='text/csv', content_disposition='inline; filename={}.csv'.format( normalize_for_url(self.title) ) )
def view_election_compound_data_as_json(self, request): """ View the raw data as JSON. """ @request.after def add_last_modified(response): add_last_modified_header(response, self.last_modified) return Response( json.dumps(self.export(), sort_keys=True, indent=2).encode('utf-8'), content_type='application/json', content_disposition='inline; filename={}.json'.format( normalize_for_url(self.title) ) )
def pdf_file(self, value): """ Sets the PDF content for the agency (and all its suborganizations). Automatically sets a nice filename. Replaces only the reference, if possible. """ filename = '{}.pdf'.format(normalize_for_url(self.title)) if self.pdf: self.pdf.reference = as_fileintent(value, filename) self.pdf.name = filename else: pdf = AgencyPdf(id=random_token()) pdf.reference = as_fileintent(value, filename) pdf.name = filename self.pdf = pdf
def migrate_file_metadata_to_jsonb(context): context.session.execute(""" ALTER TABLE files ALTER COLUMN reference TYPE JSONB USING reference::jsonb """) context.operations.drop_index('files_by_type_and_name') context.add_column_with_defaults( table='files', column=Column('order', Text, nullable=False), default=lambda r: normalize_for_url(r.name)) context.operations.create_index( 'files_by_type_and_order', 'files', ['type', 'order'])
def set_filename(response): bfs_number = self.linked_swissvotes[0].bfs_number name = self.name.split('-')[0] extension = {'results_by_domain': 'xlsx'}.get(name, 'pdf') title = { 'voting_text': _("Voting text"), 'brief_description': _("Brief description Swissvotes"), 'realization': _("Realization"), 'federal_council_message': _("Federal council message"), 'parliamentary_debate': _("Parliamentary debate"), 'voting_booklet': _("Voting booklet"), 'ad_analysis': _("Analysis of the advertising campaign"), 'resolution': _("Resolution"), 'results_by_domain': _("Result by canton, district and municipality") }.get(name, '') title = normalize_for_url(request.translate(title)) response.headers['Content-Disposition'] = ( f'inline; filename={bfs_number}-{title}.{extension}')
def view_election_parties_data_as_csv(self, request): """ View the raw parties data as CSV. """ @request.after def add_last_modified(response): add_last_modified_header(response, self.last_modified) return Response( convert_list_of_dicts_to_csv(self.export_parties()), content_type='text/csv', content_disposition='inline; filename={}.csv'.format( normalize_for_url( '{}-{}'.format( self.title, request.translate(_("Parties")).lower() ) ) ) )
def view_election_compound_pdf(self, request): """ View the generated PDF. """ layout = ElectionCompoundLayout(self, request) if not layout.pdf_path: return Response(status='503 Service Unavailable') content = None with request.app.filestorage.open(layout.pdf_path, 'rb') as f: content = f.read() return Response( content, content_type='application/pdf', content_disposition='inline; filename={}.pdf'.format( normalize_for_url(self.title) ) )
def add(self, title, timezone, type=None, name=None, meta={}, content={}, definition=None, group=None): # look up the right class depending on the type (we need to do # this a bit akwardly here, because Resource does not use the # ModelBase as declarative base) resource = Resource.get_polymorphic_class(type, Resource)() resource.id == uuid4() resource.name = name or normalize_for_url(title) resource.title = title resource.timezone = timezone resource.meta = meta resource.content = content resource.definition = definition resource.group = group self.session.add(resource) self.session.flush() return self.bind(resource)
def view_notices_index(self, request): """ Export the index to the notices as PDF. This view is only visible by a publisher. """ pdf = IndexPdf.from_notices(self, request) filename = normalize_for_url( '{}-{}'.format( request.translate(_("Gazette")), request.app.principal.name ) ) return Response( pdf.read(), content_type='application/pdf', content_disposition=f'inline; filename={filename}.pdf' )
def load_homepage_pages(self): pages = PageCollection(self.session()).query() pages = pages.filter(Topic.type == 'topic') # XXX use JSON/JSONB for this (the attribute is not there if it's # false, so this is not too bad speed-wise but it's still awful) pages = pages.filter(Topic.meta.contains( 'is_visible_on_homepage' )) result = defaultdict(list) for page in pages.all(): if page.is_visible_on_homepage: result[page.root.id].append(page) for key in result: result[key] = list(sorted( result[key], key=lambda p: utils.normalize_for_url(p.title) )) return result
def get_root_pdf(self, request): if not request.app.root_pdf_exists: return Response(status='503 Service Unavailable') @request.after def cache_headers(response): last_modified = request.app.root_pdf_modified if last_modified: max_age = 1 * 24 * 60 * 60 expires = datetime.now() + timedelta(seconds=max_age) fmt = '%a, %d %b %Y %H:%M:%S GMT' response.headers.add('Cache-Control', f'max-age={max_age}, public') response.headers.add('ETag', last_modified.isoformat()) response.headers.add('Expires', expires.strftime(fmt)) response.headers.add('Last-Modified', last_modified.strftime(fmt)) return Response(request.app.root_pdf, content_type='application/pdf', content_disposition='inline; filename={}.pdf'.format( normalize_for_url(request.app.org.name)))
def handle_new_definition(self, request, form): if form.submitted(request): model = Bunch( title=None, definition=None, type='custom', meta={}, content={} ) form.update_model(model) if self.definitions.by_name(normalize_for_url(model.title)): request.alert(_("A form with this name already exists")) else: # forms added online are always custom forms new_form = self.definitions.add( title=model.title, definition=model.definition, type='custom', meta=model.meta, content=model.content ) request.success(_("Added a new form")) return morepath.redirect(request.link(new_form)) layout = FormEditorLayout(self, request) layout.breadcrumbs = [ Link(_("Homepage"), layout.homepage_url), Link(_("Forms"), request.link(self)), Link(_("New Form"), request.link(self, name='neu')) ] return { 'layout': layout, 'title': _("New Form"), 'form': form, 'form_width': 'large', }
def view_rdf(self, request): """ Returns an XML / RDF / DCAT-AP for Switzerland format for opendata.swiss. See http://handbook.opendata.swiss/en/library/ch-dcat-ap for more information. """ principal_name = request.app.principal.name publisher_id = self.open_data.get('id') publisher_name = self.open_data.get('name') publisher_mail = self.open_data.get('mail') if not publisher_id or not publisher_name or not publisher_mail: raise HTTPNotImplemented() @request.after def set_headers(response): response.headers['Content-Type'] = 'application/rdf+xml; charset=UTF-8' layout = DefaultLayout(self, request) domains = dict(self.domains_election) domains.update(self.domains_vote) locales = {k: k[:2] for k in request.app.locales} default_locale = request.default_locale rdf = Element('rdf:RDF', attrib={ 'xmlns:dct': 'http://purl.org/dc/terms/', 'xmlns:dc': 'http://purl.org/dc/elements/1.1/', 'xmlns:dcat': 'http://www.w3.org/ns/dcat#', 'xmlns:foaf': 'http://xmlns.com/foaf/0.1/', 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema#', 'xmlns:rdfs': 'http://www.w3.org/2000/01/rdf-schema#', 'xmlns:rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'xmlns:vcard': 'http://www.w3.org/2006/vcard/ns#', 'xmlns:odrs': 'http://schema.theodi.org/odrs#', 'xmlns:schema': 'http://schema.org/', }) catalog = sub(rdf, 'dcat:Catalog') session = request.session items = session.query(Election).all() items.extend(session.query(ElectionCompound).all()) items.extend(session.query(Vote).all()) translations = request.app.translations def translate(text, locale): translator = translations.get(locale) if translator: return text.interpolate(translator.gettext(text)) return text.interpolate(text) for item in sorted(items, key=lambda i: i.date, reverse=True): is_vote = isinstance(item, Vote) # IDs item_id = '{}-{}'.format('vote' if is_vote else 'election', item.id) ds = sub(catalog, 'dcat:dataset') ds = sub(ds, 'dcat:Dataset', { 'rdf:about': 'http://{}/{}'.format(publisher_id, item_id)} ) sub(ds, 'dct:identifier', {}, '{}@{}'.format(item_id, publisher_id)) # Dates sub( ds, 'dct:issued', {'rdf:datatype': 'http://www.w3.org/2001/XMLSchema#dateTime'}, datetime( item.date.year, item.date.month, item.date.day ).isoformat() ) sub( ds, 'dct:modified', {'rdf:datatype': 'http://www.w3.org/2001/XMLSchema#dateTime'}, item.last_modified.replace(microsecond=0).isoformat() ) sub( ds, 'dct:accrualPeriodicity', {'rdf:resource': 'http://purl.org/cld/freq/completelyIrregular'} ) # Theme sub( ds, 'dcat:theme', {'rdf:resource': 'http://opendata.swiss/themes/politics'} ) # Landing page sub(ds, 'dcat:landingPage', {}, request.link(item, 'data')) # Keywords for keyword in ( _("Vote") if is_vote else _("Election"), domains[item.domain] ): for locale, lang in locales.items(): value = translate(keyword, locale).lower() sub(ds, 'dcat:keyword', {'xml:lang': lang}, value) # Title for locale, lang in locales.items(): title = item.get_title(locale, default_locale) or '' sub(ds, 'dct:title', {'xml:lang': lang}, title) # Description for locale, lang in locales.items(): locale = locale if is_vote: if item.domain == 'canton': des = _( "Final results of the cantonal vote \"${title}\", " "${date}, ${principal}, " "broken down by municipalities.", mapping={ 'title': ( item.get_title(locale, default_locale) or '' ), 'date': format_date( item.date, format='long', locale=locale ), 'principal': principal_name } ) else: des = _( "Final results of the federal vote \"${title}\", " "${date}, ${principal}, " "broken down by municipalities.", mapping={ 'title': ( item.get_title(locale, default_locale) or '' ), 'date': format_date( item.date, format='long', locale=locale ), 'principal': principal_name } ) else: if item.domain == 'canton': des = _( "Final results of the cantonal election \"${title}\", " "${date}, ${principal}, " "broken down by candidates and municipalities.", mapping={ 'title': ( item.get_title(locale, default_locale) or '' ), 'date': format_date( item.date, format='long', locale=locale ), 'principal': principal_name } ) elif item.domain == 'region': des = _( "Final results of the regional election \"${title}\", " "${date}, ${principal}, " "broken down by candidates and municipalities.", mapping={ 'title': ( item.get_title(locale, default_locale) or '' ), 'date': format_date( item.date, format='long', locale=locale ), 'principal': principal_name } ) else: des = _( "Final results of the federal election \"${title}\", " "${date}, ${principal}, " "broken down by candidates and municipalities.", mapping={ 'title': ( item.get_title(locale, default_locale) or '' ), 'date': format_date( item.date, format='long', locale=locale ), 'principal': principal_name } ) des = translate(des, locale) sub(ds, 'dct:description', {'xml:lang': lang}, des) # Format description for locale, lang in locales.items(): label = translate(_("Format Description"), locale) url = layout.get_opendata_link(lang) fmt_des = sub(ds, 'dct:relation') fmt_des = sub(fmt_des, 'rdf:Description', {'rdf:about': url}) sub(fmt_des, 'rdfs:label', {}, label) # Publisher pub = sub(ds, 'dct:publisher') pub = sub(pub, 'rdf:Description') sub(pub, 'rdfs:label', {}, publisher_name) mail = sub(ds, 'dcat:contactPoint') mail = sub(mail, 'vcard:Organization') sub(mail, 'vcard:fn', {}, publisher_name) sub(mail, 'vcard:hasEmail', { 'rdf:resource': 'mailto:{}'.format(publisher_mail) }) # Date date = sub(ds, 'dct:temporal') date = sub(date, 'dct:PeriodOfTime') sub( date, 'schema:startDate', {'rdf:datatype': 'http://www.w3.org/2001/XMLSchema#date'}, item.date.isoformat() ) sub( date, 'schema:endDate', {'rdf:datatype': 'http://www.w3.org/2001/XMLSchema#date'}, item.date.isoformat() ) # Distributions for fmt, media_type in ( ('csv', 'text/csv'), ('json', 'application/json'), ): url = request.link(item, 'data-{}'.format(fmt)) # IDs dist = sub(ds, 'dcat:distribution') dist = sub(dist, 'dcat:Distribution', { 'rdf:about': 'http://{}/{}/{}'.format( publisher_id, item_id, fmt ) }) sub(dist, 'dct:identifier', {}, fmt) # Title for locale, lang in locales.items(): title = item.get_title(locale, default_locale) or item.id title = '{}.{}'.format(normalize_for_url(title), fmt) sub(dist, 'dct:title', {'xml:lang': lang}, title) # Dates sub( dist, 'dct:issued', {'rdf:datatype': 'http://www.w3.org/2001/XMLSchema#dateTime'}, item.created.replace(microsecond=0).isoformat() ) sub( dist, 'dct:modified', {'rdf:datatype': 'http://www.w3.org/2001/XMLSchema#dateTime'}, item.last_modified.replace(microsecond=0).isoformat() ) # URLs sub( dist, 'dcat:accessURL', {'rdf:datatype': 'http://www.w3.org/2001/XMLSchema#anyURI'}, url ) sub( dist, 'dcat:downloadURL', {'rdf:datatype': 'http://www.w3.org/2001/XMLSchema#anyURI'}, url ) # Legal sub( dist, 'dct:rights', {}, 'NonCommercialAllowed-CommercialAllowed-ReferenceRequired' ) # Media Type sub(dist, 'dcat:mediaType', {}, media_type) out = BytesIO() ElementTree(rdf).write(out, encoding='utf-8', xml_declaration=True) out.seek(0) return out.read()
def name_observer(self, name): self.order = normalize_for_url(name)
def validate_name(self, key, name): assert normalize_for_url(name) == name, ( "The given name was not normalized" ) return name
def get_subdomain(self, name): return utils.normalize_for_url(name)
def view_occasion_bookings_table(self, request): layout = DefaultLayout(self, request) wishlist_phase = self.period.wishlist_phase booking_phase = self.period.booking_phase phase_title = wishlist_phase and _("Wishlist") or _("Bookings") users = UserCollection(request.session).query() users = users.with_entities(User.username, User.id) users = {u.username: u.id.hex for u in users} def occasion_links(oid): if self.period.finalized: yield Link( text=_("Signup Attendee"), url='#', traits=( Block(_( "The period has already been finalized. No new " "attendees may be added." ), no=_("Cancel")), ) ) else: yield Link( text=_("Signup Attendee"), url=request.return_to( request.class_link(Occasion, {'id': oid}, 'book'), request.class_link(MatchCollection) ) ) @lru_cache(maxsize=10) def bookings_link(username): return request.class_link( BookingCollection, { 'period_id': self.period.id, 'username': username } ) @lru_cache(maxsize=10) def user_link(username): return request.return_here( request.class_link( User, {'id': users[username]} ) ) @lru_cache(maxsize=10) def attendee_link(attendee_id): return request.return_here( request.class_link( Attendee, {'id': attendee_id} ) ) @lru_cache(maxsize=10) def group_link(group_code): return request.class_link( GroupInvite, { 'group_code': group_code } ) def booking_links(booking): yield Link(_("User"), user_link(booking.attendee.username)) yield Link(_("Attendee"), attendee_link(booking.attendee_id)) yield Link(phase_title, bookings_link(booking.attendee.username)) if booking.group_code: yield Link(_("Group"), group_link(booking.group_code)) if wishlist_phase: yield Link( text=_("Remove Wish"), url=layout.csrf_protected_url( request.class_link(Booking, {'id': booking.id}) ), traits=( Confirm(_( "Do you really want to remove ${attendee}'s wish?", mapping={ 'attendee': booking.attendee.name } ), yes=_("Remove Wish"), no=_("Cancel")), Intercooler( request_method='DELETE', target='#{}'.format(booking.id) ) ) ) elif booking_phase and booking.state != 'accepted': yield Link( text=_("Remove Booking"), url=layout.csrf_protected_url( request.class_link(Booking, {'id': booking.id}) ), traits=( Confirm(_( "Do you really want to delete ${attendee}'s booking?", mapping={ 'attendee': booking.attendee.name } ), yes=_("Remove Booking"), no=_("Cancel")), Intercooler( request_method='DELETE', target='#{}'.format(booking.id) ) ) ) elif booking_phase and booking.state == 'accepted': yield Link( text=_("Cancel Booking"), url=layout.csrf_protected_url( request.class_link( Booking, {'id': booking.id}, 'cancel' ) ), traits=( Confirm( _( "Do you really want to cancel ${attendee}'s " "booking?", mapping={ 'attendee': booking.attendee.name } ), _("This cannot be undone."), _("Cancel Booking"), _("Cancel") ), Intercooler( request_method='POST', ) ) ) bookings = {'accepted': [], 'other': []} q = request.session.query(Booking).filter_by(occasion_id=self.id) q = q.options(joinedload(Booking.attendee)) for booking in q: state = booking.state == 'accepted' and 'accepted' or 'other' bookings[state].append(booking) bookings['accepted'].sort(key=lambda b: normalize_for_url(b.attendee.name)) bookings['other'].sort(key=lambda b: normalize_for_url(b.attendee.name)) return { 'layout': layout, 'bookings': bookings, 'oid': self.id, 'occasion_links': occasion_links, 'booking_links': booking_links, 'period': self.period }
def by_title(choice): return normalize_for_url(choice[1])
def view_activities(self, request): active_period = request.app.active_period show_activities = bool(active_period or request.is_organiser) layout = VacationActivityCollectionLayout(self, request) filters = {} def link(text, active, url, rounded=False): return Link(text=text, active=active, url=url, rounded=rounded, attrs={ 'ic-get-from': url }) if show_activities: filters['timelines'] = [ link( text=request.translate(_("Elapsed")), active='past' in self.filter.timelines, url=request.link(self.for_filter(timeline='past')) ), link( text=request.translate(_("Now")), active='now' in self.filter.timelines, url=request.link(self.for_filter(timeline='now')) ), link( text=request.translate(_("Scheduled")), active='future' in self.filter.timelines, url=request.link(self.for_filter(timeline='future')) ), ] if request.is_organiser: filters['timelines'].insert(0, link( text=request.translate(_("Without")), active='undated' in self.filter.timelines, url=request.link(self.for_filter(timeline='undated')) )) filters['tags'] = [ link( text=request.translate(_(tag)), active=tag in self.filter.tags, url=request.link(self.for_filter(tag=tag)), ) for tag in self.used_tags ] filters['tags'].sort(key=lambda l: l.text) filters['durations'] = tuple( link( text=request.translate(text), active=duration in self.filter.durations, url=request.link(self.for_filter(duration=duration)) ) for text, duration in ( (_("Half day"), DAYS.half), (_("Full day"), DAYS.full), (_("Multiple days"), DAYS.many), ) ) ages = self.available_ages() if ages: def age_filters(): for age in range(*ages): if age < 16: yield str(age), (age, age) else: yield "16+", (16, 99) break filters['ages'] = tuple( link( text=request.translate(text), active=self.filter.contains_age_range(age_range), url=request.link(self.for_filter(age_range=age_range)) ) for text, age_range in age_filters() ) filters['price_range'] = tuple( link( text=request.translate(text), active=self.filter.contains_price_range(price_range), url=request.link(self.for_filter(price_range=price_range)) ) for text, price_range in ( (_("Free of Charge"), (0, 0)), (_("Up to 25 CHF"), (1, 25)), (_("Up to 50 CHF"), (26, 50)), (_("Up to 100 CHF"), (51, 100)), (_("More than 100 CHF"), (101, 100000)), ) ) if active_period: filters['weeks'] = tuple( link( text='{} - {}'.format( layout.format_date(daterange[0], 'date'), layout.format_date(daterange[1], 'date') ), active=daterange in self.filter.dateranges, url=request.link(self.for_filter(daterange=daterange)) ) for nth, daterange in enumerate( self.available_weeks(active_period), start=1 ) ) filters['weekdays'] = tuple( link( text=WEEKDAYS[weekday], active=weekday in self.filter.weekdays, url=request.link(self.for_filter(weekday=weekday)) ) for weekday in range(0, 7) ) filters['available'] = tuple( link( text=request.translate(text), active=available in self.filter.available, url=request.link(self.for_filter(available=available)) ) for text, available in ( (_("None"), 'none'), (_("Few"), 'few'), (_("Many"), 'many'), ) ) filters['municipalities'] = [ link( text=municipality, active=municipality in self.filter.municipalities, url=request.link(self.for_filter(municipality=municipality)) ) for municipality in self.used_municipalities ] filters['municipalities'].sort(key=lambda l: normalize_for_url(l.text)) if request.is_organiser: if request.app.periods: filters['periods'] = [ link( text=period.title, active=period.id in self.filter.period_ids, url=request.link(self.for_filter(period_id=period.id)) ) for period in request.app.periods if period ] filters['periods'].sort(key=lambda l: l.text) filters['own'] = ( link( text=request.translate(_("Own")), active=request.current_username in self.filter.owners, url=request.link( self.for_filter(owner=request.current_username) ) ), ) filters['states'] = tuple( link( text=ACTIVITY_STATE_TRANSLATIONS[state], active=state in self.filter.states, url=request.link(self.for_filter(state=state)) ) for state in ACTIVITY_STATES ) def get_period_bound_occasions(activity): if not active_period: return [] return [ o for o in activity.occasions if o.period_id == active_period.id ] def get_ages(a): return tuple(o.age for o in get_period_bound_occasions(a)) def get_available_spots(a): if not active_period: return 0 if not active_period.confirmed: return sum( o.max_spots for o in get_period_bound_occasions(a) ) else: return sum( o.available_spots for o in get_period_bound_occasions(a) ) def get_min_cost(a): occasions = get_period_bound_occasions(a) if not occasions: return None if active_period.all_inclusive: extra = 0 else: extra = active_period.booking_cost or 0 return min((o.cost or 0) + extra for o in occasions) filtered = next( ( True for links in filters.values() for link in links if link.active ), False ) return { 'activities': self.batch if show_activities else None, 'layout': layout, 'title': _("Activities"), 'filters': filters, 'filtered': filtered, 'period': active_period, 'get_ages': get_ages, 'get_min_cost': get_min_cost, 'get_available_spots': get_available_spots, }