def __call__(self, version=None, include_items=True): if 'allevents' in self.request.form.keys(): results = super(SerializeCollectionToJson, self).__call__( version=version, ) events = expand_events( self.context.results(batch=False), 2 ) # items = [] # i = 0 # for brain in events: # items.append(getMultiAdapter((brain, self.request), # ISerializeToJson)()) # i += 1 # print '{0} / {1}'.format(str(i), len(events)) # results['items'] = items results['items'] = [ getMultiAdapter((brain, self.request), ISerializeToJson)() for brain in events ] results['items_total'] = len(events) return results else: return super(SerializeEventCollectionToJson, self).__call__( version, include_items)
def events(self, ret_mode=RET_MODE_ACCESSORS, expand=True, batch=True): res = [] is_col = self.is_collection is_top = self.is_topic if is_col or is_top: ctx = self.default_context if is_col: res = ctx.results(batch=False, sort_on='start', brains=True) query = queryparser.parseFormquery(ctx, ctx.getRawQuery()) else: res = ctx.queryCatalog(batch=False, full_objects=False) query = ctx.buildQuery() if expand: # get start and end values from the query to ensure limited # listing for occurrences start, end = self._expand_events_start_end(query.get('start'), query.get('end')) res = expand_events(res, ret_mode, sort='start', start=start, end=end) else: res = self._get_events(ret_mode, expand=expand) if batch: b_start = self.b_start b_size = self.b_size res = Batch(res, size=b_size, start=b_start, orphan=self.orphan) return res
def _get_generation_context(self, helper_view, pod_template): result = super(IDocumentGenerationView, self)._get_generation_context( helper_view, pod_template) # if pod_template.portal_type == 'ConfigurablePODTemplate': if IFacetedNavigable.providedBy(self.context): brains = getDashboardQueryResult(self.context) result['brains'] = brains if len(brains) > 0 and brains[0].portal_type == 'Event': expandevents = expand_events(brains, 2) events = [] for event in expandevents: if IOccurrence.providedBy(event): start = event.start end = event.end parent = zope.copy.copy(event.aq_parent.aq_base) parent.start = start parent.end = end req = event.REQUEST parent.REQUEST = req parent.occurrence = True parent.base_event = event.aq_parent events.append(parent) else: events.append(event) result['brains'] = events return result
def _get_generation_context(self, helper_view, pod_template): result = super(IDocumentGenerationView, self)._get_generation_context(helper_view, pod_template) # if pod_template.portal_type == 'ConfigurablePODTemplate': if IFacetedNavigable.providedBy(self.context): brains = getDashboardQueryResult(self.context) result['brains'] = brains if len(brains) > 0 and brains[0].portal_type == 'Event': expandevents = expand_events(brains, 2) events = [] for event in expandevents: if IOccurrence.providedBy(event): start = event.start end = event.end parent = zope.copy.copy(event.aq_parent.aq_base) parent.start = start parent.end = end req = event.REQUEST parent.REQUEST = req parent.occurrence = True parent.base_event = event.aq_parent events.append(parent) else: events.append(event) result['brains'] = events return result
def events(self, ret_mode=RET_MODE_ACCESSORS, expand=True, batch=True): res = [] is_col = self.is_collection is_top = self.is_topic if is_col or is_top: ctx = self.default_context if is_col: res = ctx.results(batch=False, sort_on='start', brains=True) query = queryparser.parseFormquery(ctx, ctx.getRawQuery()) else: res = ctx.queryCatalog(batch=False, full_objects=False) query = ctx.buildQuery() if expand: # get start and end values from the query to ensure limited # listing for occurrences start, end = self._expand_events_start_end( query.get('start'), query.get('end')) res = expand_events(res, ret_mode, sort='start', start=start, end=end) else: res = self._get_events(ret_mode, expand=expand) if batch: b_start = self.b_start b_size = self.b_size res = Batch(res, size=b_size, start=b_start, orphan=self.orphan) return res
def events(self): context = aq_inner(self.context) data = self.data query = {} if data.state: query['review_state'] = data.state events = [] query.update(self.request.get('contentFilter', {})) search_base = self.search_base if ICollection and ICollection.providedBy(search_base): # Whatever sorting is defined, we're overriding it. query = queryparser.parseFormquery( search_base, search_base.query, sort_on='start', sort_order=None ) start = None if 'start' in query: start = query['start'] else: start = localized_now(context) end = None if 'end' in query: end = query['end'] start, end = _prepare_range(search_base, start, end) query.update(start_end_query(start, end)) events = search_base.results( batch=False, brains=True, custom_query=query, limit=data.count ) events = expand_events( events, ret_mode=RET_MODE_ACCESSORS, start=start, end=end, sort='start', sort_reverse=False ) events = events[:data.count] # limit expanded else: search_base_path = self.search_base_path if search_base_path: query['path'] = {'query': search_base_path} events = get_events( context, start=localized_now(context), ret_mode=RET_MODE_ACCESSORS, expand=True, limit=data.count, **query ) eventlist=[] for ev in events: hasimage = bool(getattr(ev.context, 'image', None)) eventlist.append((ev, hasimage)) return eventlist
def is_one_day(self, event): occurences = expand_events([event], RET_MODE_ACCESSORS) if not occurences: start_date = event.start end_date = event.end else: start_date = getattr(occurences[0], "start") end_date = getattr(occurences[-1], "end") return self.toLocalizedTime( start_date, long_format=0) == self.toLocalizedTime(end_date, long_format=0)
def events(self): context = aq_inner(self.context) data = self.data query = {} if data.state: query['review_state'] = data.state events = [] query.update(self.request.get('contentFilter', {})) search_base = self.search_base if ICollection and ICollection.providedBy(search_base): # Whatever sorting is defined, we're overriding it. query = queryparser.parseFormquery(search_base, search_base.query, sort_on='start', sort_order=None) start = None if 'start' in query: start = query['start'] else: start = localized_now(context) end = None if 'end' in query: end = query['end'] start, end = _prepare_range(search_base, start, end) query.update(start_end_query(start, end)) events = search_base.results(batch=False, brains=True, custom_query=query, limit=data.count) events = expand_events(events, ret_mode=RET_MODE_ACCESSORS, start=start, end=end, sort='start', sort_reverse=False) events = events[:data.count] # limit expanded else: search_base_path = self.search_base_path if search_base_path: query['path'] = {'query': search_base_path} events = get_events(context, start=localized_now(context), ret_mode=RET_MODE_ACCESSORS, expand=True, limit=data.count, **query) return events
def events(self, ret_mode=RET_MODE_ACCESSORS, expand=True, batch=True): res = [] if self.is_collection: ctx = self.default_context # Whatever sorting is defined, we're overriding it. sort_on = 'start' sort_order = None if self.mode in ('past', 'all'): sort_order = 'reverse' query = queryparser.parseFormquery( ctx, ctx.query, sort_on=sort_on, sort_order=sort_order) custom_query = self.request.get('contentFilter', {}) if 'start' not in query or 'end' not in query: # ... else don't show the navigation bar start, end = self._start_end start, end = _prepare_range(ctx, start, end) custom_query.update(start_end_query(start, end)) # BAM ... inject our filter viewlet values fc_adapter = ICollectionFilter(ctx) res = fc_adapter.filtered_result(pquery=query, batch=False, custom_query=custom_query) if res is None: # ORIGINAL res = ctx.results(batch=False, brains=True, custom_query=custom_query) if expand: # get start and end values from the query to ensure limited # listing for occurrences _filter_start = self.request.get('_filter_start') if _filter_start: # check for pickadate day filtering fs = DateTime(_filter_start).earliestTime() fe = DateTime(_filter_start).latestTime() start, end = self._expand_events_start_end( dict(query=[fs, fe], range='minmax'), None) else: start, end = self._expand_events_start_end( query.get('start') or custom_query.get('start'), query.get('end') or custom_query.get('end')) res = expand_events( res, ret_mode, start=start, end=end, sort=sort_on, sort_reverse=True if sort_order else False) else: res = self._get_events(ret_mode, expand=expand) if batch: b_start = self.b_start b_size = self.b_size res = Batch(res, size=b_size, start=b_start, orphan=self.orphan) return res
def get_event_dates(self, result): timezone = self.get_portal_timezone() occurences = expand_events([result], RET_MODE_ACCESSORS) start_date = getattr(occurences[0], 'start') end_date = getattr(occurences[-1], 'end') if not start_date: return {'start': '', 'end': ''} if getattr(start_date, 'astimezone', False): start_date = start_date.astimezone(pytz.timezone(timezone)) formated_start = start_date.strftime('%d/%m') if not end_date: return {'start': formated_start, 'end': ''} if getattr(end_date, 'astimezone', False): end_date = end_date.astimezone(pytz.timezone(timezone)) formated_end = end_date.strftime('%d/%m') if formated_start != formated_end: return {'start': formated_start, 'end': formated_end} return {'start': formated_start, 'end': ''}
def get_event_dates(self, result): timezone = self.get_portal_timezone() occurences = expand_events([result], RET_MODE_ACCESSORS) start_date = getattr(occurences[0], "start") end_date = getattr(occurences[-1], "end") if not start_date: return {"start": "", "end": ""} if getattr(start_date, "astimezone", False): start_date = start_date.astimezone(pytz.timezone(timezone)) formated_start = start_date.strftime("%d/%m") if not end_date: return {"start": formated_start, "end": ""} if getattr(end_date, "astimezone", False): end_date = end_date.astimezone(pytz.timezone(timezone)) formated_end = end_date.strftime("%d/%m") if formated_start != formated_end: return {"start": formated_start, "end": formated_end} return {"start": formated_start, "end": ""}
def event_dates(obj): """ Return all days in which the event occurs """ if obj.start is None or obj.end is None: return None event_days = set() occurences = expand_events([obj], RET_MODE_ACCESSORS) for occurence in occurences: start = occurence.start event_days.add(start.date()) end = occurence.end duration = (end.date() - start.date()).days for idx in range(1, duration + 1): day = start + timedelta(days=idx) event_days.add(day.date()) return tuple(event_days)
def events(self, ret_mode=RET_MODE_ACCESSORS, expand=False, batch=True): res = [] if self.is_collection: ctx = self.default_context # Whatever sorting is defined, we're overriding it. sort_on = 'start' sort_order = None if self.mode in ('past', 'all'): sort_order = 'reverse' query = queryparser.parseFormquery(ctx, ctx.query, sort_on=sort_on, sort_order=sort_order) custom_query = self.request.get('contentFilter', {}) if 'start' not in query or 'end' not in query: # ... else don't show the navigation bar start, end = self._start_end start, end = _prepare_range(ctx, start, end) custom_query.update(start_end_query(start, end)) res = ctx.results(batch=False, brains=True, custom_query=custom_query) if expand: # get start and end values from the query to ensure limited # listing for occurrences start, end = self._expand_events_start_end( query.get('start') or custom_query.get('start'), query.get('end') or custom_query.get('end')) # import pdb; pdb.set_trace() res = expand_events(res, ret_mode, start=start, end=end, sort=sort_on, sort_reverse=True if sort_order else False) else: res = self._get_events(ret_mode, expand=expand) if batch: b_start = self.b_start b_size = self.b_size res = Batch(res, size=b_size, start=b_start, orphan=self.orphan) return res
def events(self, ret_mode=RET_MODE_ACCESSORS, expand=False, batch=True): res = [] if self.is_collection: ctx = self.default_context # Whatever sorting is defined, we're overriding it. sort_on = 'start' sort_order = None if self.mode in ('past', 'all'): sort_order = 'reverse' query = queryparser.parseFormquery( ctx, ctx.query, sort_on=sort_on, sort_order=sort_order ) custom_query = self.request.get('contentFilter', {}) if 'start' not in query or 'end' not in query: # ... else don't show the navigation bar start, end = self._start_end start, end = _prepare_range(ctx, start, end) custom_query.update(start_end_query(start, end)) res = ctx.results( batch=False, brains=True, custom_query=custom_query ) if expand: # get start and end values from the query to ensure limited # listing for occurrences start, end = self._expand_events_start_end( query.get('start') or custom_query.get('start'), query.get('end') or custom_query.get('end') ) # import pdb; pdb.set_trace() res = expand_events( res, ret_mode, start=start, end=end, sort=sort_on, sort_reverse=True if sort_order else False ) else: res = self._get_events(ret_mode, expand=expand) if batch: b_start = self.b_start b_size = self.b_size res = Batch(res, size=b_size, start=b_start, orphan=self.orphan) return res
def __call__(self, version=None, include_items=True): if 'allevents' in self.request.form.keys(): results = super(SerializeCollectionToJson, self).__call__(version=version, ) events = expand_events(self.context.results(batch=False), 2) # items = [] # i = 0 # for brain in events: # items.append(getMultiAdapter((brain, self.request), # ISerializeToJson)()) # i += 1 # print '{0} / {1}'.format(str(i), len(events)) # results['items'] = items results['items'] = [ getMultiAdapter((brain, self.request), ISerializeToJson)() for brain in events ] results['items_total'] = len(events) return results else: return super(SerializeEventCollectionToJson, self).__call__(version, include_items)
def cal_data(self): """Calendar iterator over weeks and days of the month to display. """ context = aq_inner(self.context) today = localized_today(context) year, month = self.year_month_display() monthdates = [dat for dat in self.cal.itermonthdates(year, month)] start = monthdates[0] end = monthdates[-1] data = self.data query = {} if data.state: query['review_state'] = data.state events = [] query.update(self.request.get('contentFilter', {})) search_base = self.search_base if ICollection and ICollection.providedBy(search_base): # Whatever sorting is defined, we're overriding it. query = queryparser.parseFormquery( search_base, search_base.query, sort_on='start', sort_order=None ) # restrict start/end with those from query, if given. if 'start' in query and query['start'] > start: start = query['start'] if 'end' in query and query['end'] < end: end = query['end'] start, end = _prepare_range(search_base, start, end) query.update(start_end_query(start, end)) events = search_base.results( batch=False, brains=True, custom_query=query ) events = expand_events( events, ret_mode=RET_MODE_OBJECTS, start=start, end=end, sort='start', sort_reverse=False ) else: search_base_path = self.search_base_path if search_base_path: query['path'] = {'query': search_base_path} events = get_events(context, start=start, end=end, ret_mode=RET_MODE_OBJECTS, expand=True, **query) #today += datetime.timedelta(days=1) cal_dict = construct_calendar(events, start=today, end=end) # [[day1week1, day2week1, ... day7week1], [day1week2, ...]] caldata = [[]] for dat in monthdates: if len(caldata[-1]) == 7: caldata.append([]) date_events = None isodat = dat.isoformat() if isodat in cal_dict: date_events = cal_dict[isodat] events_string_list = [] if date_events: for occ in date_events: accessor = IEventAccessor(occ) location = accessor.location whole_day = accessor.whole_day time = accessor.start.time().strftime('%H:%M') # TODO: make 24/12 hr format configurable events_string_list.append( u'{0}{1}{2}{3}'.format( accessor.title, u' {0}'.format(time) if not whole_day else u'', u', ' if not whole_day and location else u'', u' {0}'.format(location) if location else u'' ) ) caldata[-1].append( {'date': dat, 'day': dat.day, 'prev_month': dat.month < month, 'next_month': dat.month > month, 'today': dat.year == today.year and dat.month == today.month and dat.day == today.day, 'date_string': u"%s-%s-%s" % (dat.year, dat.month, dat.day), 'events_string': u' | '.join(events_string_list), 'events': date_events}) return caldata
def is_one_day(self, event): occurences = expand_events([event], RET_MODE_ACCESSORS) start_date = getattr(occurences[0], 'start') end_date = getattr(occurences[-1], 'end') return self.toLocalizedTime(start_date, long_format=0) == self.toLocalizedTime(end_date, long_format=0) # noqa
def cal_data(self): """Calendar iterator over weeks and days of the month to display. """ context = aq_inner(self.context) today = localized_today(context) year, month = self.year_month_display() monthdates = [dat for dat in self.cal.itermonthdates(year, month)] start = monthdates[0] end = monthdates[-1] data = self.data query = {} if data.state: query['review_state'] = data.state events = [] query.update(self.request.get('contentFilter', {})) search_base = self.search_base if ICollection and ICollection.providedBy(search_base): # Whatever sorting is defined, we're overriding it. query = queryparser.parseFormquery( search_base, search_base.query, sort_on='start', sort_order=None ) # restrict start/end with those from query, if given. if 'start' in query and query['start'] > start: start = query['start'] if 'end' in query and query['end'] < end: end = query['end'] start, end = _prepare_range(search_base, start, end) query.update(start_end_query(start, end)) events = search_base.results( batch=False, brains=True, custom_query=query ) events = expand_events( events, ret_mode=RET_MODE_OBJECTS, start=start, end=end, sort='start', sort_reverse=False ) else: search_base_path = self.search_base_path if search_base_path: query['path'] = {'query': search_base_path} events = get_events(context, start=start, end=end, ret_mode=RET_MODE_OBJECTS, expand=True, **query) cal_dict = construct_calendar(events, start=start, end=end) # [[day1week1, day2week1, ... day7week1], [day1week2, ...]] caldata = [[]] for dat in monthdates: if len(caldata[-1]) == 7: caldata.append([]) date_events = None isodat = dat.isoformat() if isodat in cal_dict: date_events = cal_dict[isodat] events_string = u"" events_title = u"" if date_events: for occ in date_events: accessor = IEventAccessor(occ) location = accessor.location whole_day = accessor.whole_day time = accessor.start.time().strftime('%H:%M') # TODO: make 24/12 hr format configurable base = u'<a href="%s"><span class="title">%s</span>'\ u'%s%s%s</a>' events_title += accessor.title events_string += base % ( accessor.url, accessor.title, u' %s' % time if not whole_day else u'', u', ' if not whole_day and location else u'', u' %s' % location if location else u'') caldata[-1].append( {'date': dat, 'day': dat.day, 'prev_month': dat.month < month, 'next_month': dat.month > month, 'today': dat.year == today.year and dat.month == today.month and dat.day == today.day, 'date_string': u"%s-%s-%s" % (dat.year, dat.month, dat.day), 'events_string': events_string, 'events_title': events_title, 'events': date_events}) return caldata
def cal_data(self): """Calendar iterator over weeks and days of the month to display. """ context = aq_inner(self.context) today = localized_today(context) year, month = self.year_month_display() monthdates = [dat for dat in self.cal.itermonthdates(year, month)] start = monthdates[0] end = monthdates[-1] data = self.data query = {} if data.state: query["review_state"] = data.state events = [] query.update(self.request.get("contentFilter", {})) search_base = self.search_base if ICollection and ICollection.providedBy(search_base): # Whatever sorting is defined, we're overriding it. query = queryparser.parseFormquery(search_base, search_base.query, sort_on="start", sort_order=None) # restrict start/end with those from query, if given. if "start" in query and query["start"] > start: start = query["start"] if "end" in query and query["end"] < end: end = query["end"] start, end = _prepare_range(search_base, start, end) query.update(start_end_query(start, end)) events = search_base.results(batch=False, brains=True, custom_query=query) events = expand_events( events, ret_mode=RET_MODE_OBJECTS, start=start, end=end, sort="start", sort_reverse=False ) else: search_base_path = self.search_base_path if search_base_path: query["path"] = {"query": search_base_path} events = get_events(context, start=start, end=end, ret_mode=RET_MODE_OBJECTS, expand=True, **query) cal_dict = construct_calendar(events, start=start, end=end) # [[day1week1, day2week1, ... day7week1], [day1week2, ...]] caldata = [[]] for dat in monthdates: if len(caldata[-1]) == 7: caldata.append([]) date_events = None isodat = dat.isoformat() if isodat in cal_dict: date_events = cal_dict[isodat] events_string_list = [] if date_events: for occ in date_events: accessor = IEventAccessor(occ) location = accessor.location whole_day = accessor.whole_day time = accessor.start.time().strftime("%H:%M") # TODO: make 24/12 hr format configurable events_string_list.append( u"{0}{1}{2}{3}".format( accessor.title, u" {0}".format(time) if not whole_day else u"", u", " if not whole_day and location else u"", u" {0}".format(location) if location else u"", ) ) caldata[-1].append( { "date": dat, "day": dat.day, "prev_month": dat.month < month, "next_month": dat.month > month, "today": dat.year == today.year and dat.month == today.month and dat.day == today.day, "date_string": u"%s-%s-%s" % (dat.year, dat.month, dat.day), "events_string": u" | ".join(events_string_list), "events": date_events, } ) return caldata
def reply(self): data = json_body(self.request) query = data.get("query", None) # b_start = int(data.get("b_start", 0)) # b_size = int(data.get("b_size", 25)) sort_on = data.get("sort_on", None) sort_order = data.get("sort_order", None) limit = int(data.get("limit", 1000)) # fullobjects = data.get("fullobjects", False) if query is None: raise Exception("No query supplied") if sort_order: sort_order = "descending" if sort_order == "descending" else "ascending" querybuilder = getMultiAdapter((self.context, self.request), name="querybuilderresults") querybuilder_parameters = dict( query=query, brains=True, # b_start=b_start, # b_size=b_size, sort_on=sort_on, sort_order=sort_order, limit=limit, ) # Exclude "self" content item from the results when ZCatalog supports # NOT UUID # queries and it is called on a content object. if not IPloneSiteRoot.providedBy( self.context) and SUPPORT_NOT_UUID_QUERIES: querybuilder_parameters.update( dict(custom_query={"UID": { "not": self.context.UID() }})) # Ottieni tutti i risultati results = querybuilder(**querybuilder_parameters) # preparati per l'expand degli eventi. not_events = [x for x in results if x.portal_type != "Event"] events = [x for x in results if x.portal_type == "Event"] # prende la query e la trasforma in una query per il catalogo # così poi se e quando dobbiamo litigare con delle ricorrenze e date # di start ed end, le abbiamo già calcolate, come plone le proporrebbe # al catalogo query_for_catalog = queryparser.parseFormquery(self.context, query, sort_on=sort_on, sort_order=sort_order) start = None end = None if "start" in query_for_catalog: start = query_for_catalog["start"]["query"] if "end" in query_for_catalog: end = query_for_catalog["end"]["query"] expanded_events = expand_events(events, 3, start, end) all_results = not_events + expanded_events brains_grouped = {} for brain in all_results: if not safe_hasattr(brain, "start") or not brain.start: continue brains_grouped.setdefault(brain.start.strftime("%Y/%m/%d"), []).append(brain) keys = list(brains_grouped.keys()) if sort_order == "descending": keys.sort(reverse=True) else: keys.sort() return {"@id": self.request.get("URL"), "items": keys}
def reply(self): data = json_body(self.request) query = data.get("query", None) sort_on = data.get("sort_on", None) sort_order = data.get("sort_order", None) if query is None: raise Exception("No query supplied") if sort_order: sort_order = "descending" if sort_order else "ascending" # results = querybuilder(**querybuilder_parameters) # Seems that origina querybuilder is not able to handle event search on # a single day... I can handle this calling catalog and going through # DateTime conversion query_for_catalog = queryparser.parseFormquery(self.context, query, sort_on=sort_on, sort_order=sort_order) query_for_catalog["start"]["query"][0] = DateTime( query_for_catalog["start"]["query"][0]) query_for_catalog["start"]["query"][1] = DateTime( query_for_catalog["start"]["query"][1]) results = self.context.portal_catalog(query_for_catalog) # preparati per l'expand degli eventi. not_events = [x for x in results if x.portal_type != "Event"] events = [x for x in results if x.portal_type == "Event"] start = None end = None # qui ce l'abbiamo per forza start if "start" in query_for_catalog: start = query_for_catalog["start"]["query"] if "end" in query_for_catalog: end = query_for_catalog["end"]["query"] expanded_events = expand_events(events, 3, start, end) start_date = start[0].strftime("%Y/%m/%d") correct_events = [] for x in expanded_events: if start_date == x.start.strftime("%Y/%m/%d"): correct_events.append(x) all_results = not_events + correct_events brains_grouped = {} for brain in all_results: if not safe_hasattr(results[0], "start"): continue brains_grouped.setdefault(brain.start.strftime("%Y/%m/%d"), []).append(brain) keys = list(brains_grouped.keys()) keys.sort() results_to_be_returned = {} for key in keys: results_to_be_returned[key] = [] for brain in brains_grouped[key]: if isinstance(brain, (EventAccessor, EventOccurrenceAccessor)): if brain.context.portal_type == "Occurrence": url = brain.url[:-10] else: url = brain.url results_to_be_returned[key].append({ "@id": url, "id": brain.id, "title": brain.title, "text": brain.description, "start": brain.start.strftime("%Y/%m/%d"), "type": self.context.translate("Event"), "category": brain.subjects, }) else: results_to_be_returned[key].append({ "@id": brain.getURL(), "id": brain.getId, "title": brain.Title, "text": brain.Description, "start": brain.start.strftime("%Y/%m/%d"), "type": self.context.translate(brain.portal_type), "category": brain.subject, }) results_to_be_returned[key].sort(key=lambda x: x["title"]) return { "@id": self.request.get("URL"), "items": results_to_be_returned, }