def manage_changeConfig(self, REQUEST=None): """Update my configuration based on form data. Verify it returns nothing. More testing is done in the integration file. >>> from Products.AutoUserMakerPASPlugin.auth import \ ApacheAuthPluginHandler >>> handler = ApacheAuthPluginHandler('someId') >>> handler.manage_changeConfig() """ if not REQUEST: return None reqget = REQUEST.form.get strip = safeToInt(reqget(stripDomainNamesKey, 1), default=1) strip = max(min(strip, 2), 0) # 0 < x < 2 autoupdate = safeToInt(reqget(autoUpdateUserPropertiesKey, 0), default=0) autoupdate_interval = safeToInt(reqget(autoUpdateUserPropertiesIntervalKey, 0), default=0) # If Shib fields change, then update the authz_mappings property. tokens = self.getTokens() formTokens = tuple(reqget(httpAuthzTokensKey, '').splitlines()) if tokens != formTokens: for ii in self.getMappings(): saveVals = {} for jj in formTokens: if ii['values'].has_key(jj): saveVals[jj] = ii['values'][jj] else: saveVals[jj] = '' ii['values'] = saveVals # Save the form values self.manage_changeProperties({stripDomainNamesKey: strip, stripDomainNamesListKey: reqget(stripDomainNamesListKey, ''), httpRemoteUserKey: reqget(httpRemoteUserKey, ''), httpCommonnameKey: reqget(httpCommonnameKey, ''), httpDescriptionKey: reqget(httpDescriptionKey, ''), httpEmailKey: reqget(httpEmailKey, ''), httpLocalityKey: reqget(httpLocalityKey, ''), httpStateKey: reqget(httpStateKey, ''), httpCountryKey: reqget(httpCountryKey, ''), autoUpdateUserPropertiesKey: autoupdate, autoUpdateUserPropertiesIntervalKey: autoupdate_interval, httpAuthzTokensKey: reqget(httpAuthzTokensKey, ''), httpSharingTokensKey: reqget(httpSharingTokensKey, ''), httpSharingLabelsKey: reqget(httpSharingLabelsKey, ''), useCustomRedirectionKey: reqget(useCustomRedirectionKey, False), challengeReplacementKey: reqget(challengeReplacementKey, ''), challengePatternKey: reqget(challengePatternKey, ''), challengeHeaderEnabledKey: reqget(challengeHeaderEnabledKey, False), challengeHeaderNameKey: reqget(challengeHeaderNameKey, ''), defaultRolesKey: reqget(defaultRolesKey, '')}) return REQUEST.RESPONSE.redirect('%s/manage_config' % self.absolute_url())
def query(self, form): """ Get value from form and return a catalog dict query """ query = {} index = self.data.get('index', '') index = index.encode('utf-8', 'replace') if not index: return query value = form.get(self.data.getId(), '') if value: value = uuidToCatalogBrain(value) if value: value = value.getPath() if not value: portal_url = getToolByName(self.context, 'portal_url') root = self.data.get('root', '') if root.startswith('/'): root = root[1:] value = '/'.join([portal_url.getPortalPath(), root]) if not value: return query depth = safeToInt(self.data.get('depth', -1)) query[index] = {"query": value, 'level': depth} return query
def __call__(self): if self.request.method == 'POST': timeout_days = safeToInt(self.request.get('timeout_days'), 7) self.context.setExpirationTimeout(timeout_days) self.context._user_check = bool( self.request.get('user_check', False), ) return self.index()
def query(self, form): """ Get value from form and return a catalog dict query """ query = {} index = self.data.get('index', '') index = index.encode('utf-8', 'replace') if not index: return query if self.hidden: value = self.default else: value = form.get(self.data.getId(), '') value = value.strip().strip('/') if not value: return query url = self.root[:] if not url: return query url.extend(value.split('/')) value = '/'.join(url).rstrip('/') depth = safeToInt(self.data.get('depth', 0)) query[index] = {"query": value, 'level': depth} logger.debug(query) return query
def query(self, batch=True, sort=False, **kwargs): """ Search using given criteria """ if self.request: kwargs.update(self.request.form) kwargs.pop('sort[]', None) kwargs.pop('sort', None) # jQuery >= 1.4 adds type to params keys # $.param({ a: [2,3,4] }) // "a[]=2&a[]=3&a[]=4" # Let's fix this kwargs = dict( (key.replace('[]', ''), val) for key, val in kwargs.items()) #fix for unicode error in indexes for key, val in kwargs.items(): if isinstance(val, str): kwargs[key] = val.decode('utf-8') query = self.criteria(sort=sort, **kwargs) # We don't want to do an unnecessary sort for a counter query counter_query = kwargs.pop('counter_query', False) if counter_query: query.pop('sort_on', None) query.pop('sort_order', None) catalog = getUtility(IFacetedCatalog) num_per_page = 20 criteria = ICriteria(self.context) brains_filters = [] for cid, criterion in criteria.items(): widgetclass = criteria.widget(cid=cid) widget = widgetclass(self.context, self.request, criterion) if widget.widget_type == 'resultsperpage': num_per_page = widget.results_per_page(kwargs) brains_filter = queryAdapter(widget, IWidgetFilterBrains) if brains_filter: brains_filters.append(brains_filter) b_start = safeToInt(kwargs.get('b_start', 0)) orphans = num_per_page * 20 / 100 # orphans = 20% of items per page if batch and not brains_filters: # add b_start and b_size to query to use better sort algorithm query['b_start'] = b_start query['b_size'] = num_per_page + orphans try: brains = catalog(self.context, **query) except Exception, err: logger.exception(err) return Batch([], 20, 0)
def cut_text(self, text='', maxchars=0): """ Cut long text """ maxchars = safeToInt(self.data.get('maxchars', 0)) if not maxchars: return text # Allow 20 % more characters in order # to avoid cutting at the end of the text if len(text) <= (maxchars + round(0.2 * maxchars)): return text return '%s...' % text[0:maxchars]
def vocabulary(self, **kwargs): """ Return data vocabulary """ reverse = safeToInt(self.data.get('sortreversed', 0)) # import ipdb; ipdb.set_trace() self.data.catalog = 'portal_catalog' values = self.catalog_vocabulary() mapping = {} res = [(val, mapping.get(val, val)) for val in values] res.sort(key=operator.itemgetter(1), cmp=intcompare) if reverse: res.reverse() return res
def query(self, batch=True, sort=False, **kwargs): """ Search using given criteria """ if self.request: kwargs.update(self.request.form) kwargs.pop('sort[]', None) kwargs.pop('sort', None) # jQuery >= 1.4 adds type to params keys # $.param({ a: [2,3,4] }) // "a[]=2&a[]=3&a[]=4" # Let's fix this kwargs = dict((key.replace('[]', ''), val) for key, val in kwargs.items()) query = self.criteria(sort=sort, **kwargs) # We don't want to do an unnecessary sort for a counter query counter_query = kwargs.pop('counter_query', False) if counter_query: query.pop('sort_on', None) query.pop('sort_order', None) catalog = getUtility(IFacetedCatalog) num_per_page = 20 criteria = ICriteria(self.context) brains_filters = [] for cid, criterion in criteria.items(): widgetclass = criteria.widget(cid=cid) widget = widgetclass(self.context, self.request, criterion) if widget.widget_type == 'resultsperpage': num_per_page = widget.results_per_page(kwargs) brains_filter = queryAdapter(widget, IWidgetFilterBrains) if brains_filter: brains_filters.append(brains_filter) b_start = safeToInt(kwargs.get('b_start', 0)) orphans = num_per_page * 20 / 100 # orphans = 20% of items per page if batch and not brains_filters: # add b_start and b_size to query to use better sort algorithm query['b_start'] = b_start query['b_size'] = num_per_page + orphans try: brains = catalog(self.context, **query) except Exception, err: logger.exception(err) return Batch([], 20, 0)
def vocabulary(self, **kwargs): """ Return data vocabulary """ reverse = safeToInt(self.data.get('sortreversed', 0)) mapping = self.portal_vocabulary() catalog = self.data.get('catalog', None) if catalog: mapping = dict(mapping) values = self.catalog_vocabulary() res = [(val, mapping.get(val, val)) for val in values] res.sort(key=operator.itemgetter(1), cmp=compare) else: res = mapping if reverse: res.reverse() return res
def process_form(self, instance, field, form, empty_marker=None, emptyReturnsMarker=False, validating=True): """Basic impl for form processing in a widget""" value = {} freq = safeToInt(form.get("%s-freq" % field.getName(), empty_marker)) if freq is -1: freq = None elif freq: value['freq'] = freq until = form.get("%s-until" % field.getName(), empty_marker) if until: # XXX localization of datestring and timezone ??? until = datetime.datetime.strptime(until, '%m/%d/%Y') value['until'] = until.replace(tzinfo=pytz.utc) return [value], {}
def vocabulary(self, **kwargs): """ Return data vocabulary """ reverse = safeToInt(self.data.get('sortreversed', 0)) mapping = self.portal_vocabulary() catalog = self.data.get('catalog', None) if catalog: mapping = dict(mapping) values = [] if HAVE_SOLR: try: # get values from SOLR if collective.solr is present searchutility = queryUtility(ISolrSearch) if searchutility is not None: index = self.data.get('index', None) kw = { 'facet': 'on', 'facet.field': index, # facet on index 'facet.limit': -1, # show unlimited results 'rows': 0 } # no results needed result = searchutility.search('*:*', **kw) try: values = list(result.facet_counts['facet_fields'] [index].keys()) except (AttributeError, KeyError): pass except (SolrConnectionException, SolrInactiveException): # solr is down or disabled pass if not values: values = self.catalog_vocabulary() res = [(val, mapping.get(val, val)) for val in values] res.sort(key=lowercase) else: res = mapping if reverse: res.reverse() return res
def vocabulary(self, **kwargs): """ Return data vocabulary """ reverse = safeToInt(self.data.get('sortreversed', 0)) mapping = self.portal_vocabulary() catalog = self.data.get('catalog', None) if catalog: mapping = dict(mapping) values = [] try: from collective.solr.interfaces import ISearch searchutility = queryUtility(ISearch, None) if searchutility is not None: index = self.data.get('index', None) kw = { 'facet': 'on', 'facet.field': index, # facet on index 'facet.limit': -1, # show unlimited results 'rows': 0 } # no results needed result = searchutility.search('*:*', **kw) try: values = result.facet_counts['facet_fields'][ index].keys() except (AttributeError, KeyError): pass except ImportError: pass if not values: values = self.catalog_vocabulary() res = [(val, mapping.get(val, val)) for val in values] res.sort(key=operator.itemgetter(1), cmp=compare) else: res = mapping if reverse: res.reverse() return res
def vocabulary(self, **kwargs): """ Return data vocabulary """ reverse = safeToInt(self.data.get('sortreversed', 0)) mapping = self.portal_vocabulary() catalog = self.data.get('catalog', None) if catalog: mapping = dict(mapping) values = [] try: from collective.solr.interfaces import ISearch searchutility = queryUtility(ISearch, None) if searchutility is not None: index = self.data.get('index', None) kw = {'facet': 'on', 'facet.field': index, # facet on index 'facet.limit': -1, # show unlimited results 'rows':0} # no results needed result = searchutility.search('*:*', **kw) try: values = result.facet_counts[ 'facet_fields'][index].keys() except (AttributeError, KeyError): pass except ImportError: pass if not values: values = self.catalog_vocabulary() res = [(val, mapping.get(val, val)) for val in values] res.sort(key=operator.itemgetter(1), cmp=compare) else: res = mapping if reverse: res.reverse() return res
def maxitems(self): """ Maximum items """ return safeToInt(self.data.get('maxitems', 0))
def query(self, batch=True, sort=False, **kwargs): """ Search using given criteria """ if self.request: kwargs.update(self.request.form) kwargs.pop('sort[]', None) kwargs.pop('sort', None) # jQuery >= 1.4 adds type to params keys # $.param({ a: [2,3,4] }) // "a[]=2&a[]=3&a[]=4" # Let's fix this kwargs = dict((key.replace('[]', ''), val) for key, val in kwargs.items()) query = self.criteria(sort=sort, **kwargs) # We don't want to do an unnecessary sort for a counter query counter_query = kwargs.pop('counter_query', False) if counter_query: query.pop('sort_on', None) query.pop('sort_order', None) catalog = getUtility(IFacetedCatalog) num_per_page = 20 criteria = ICriteria(self.context) brains_filters = [] for cid, criterion in criteria.items(): widgetclass = criteria.widget(cid=cid) widget = widgetclass(self.context, self.request, criterion) if widget.widget_type == 'resultsperpage': num_per_page = widget.results_per_page(kwargs) brains_filter = queryAdapter(widget, IWidgetFilterBrains) if brains_filter: brains_filters.append(brains_filter) b_start = safeToInt(kwargs.get('b_start', 0)) # make sure orphans is an integer, // is used so in Python3 we have an # integer division as by default, a division result is a float orphans = num_per_page * 20 // 100 # orphans = 20% of items per page if batch and not brains_filters: # add b_start and b_size to query to use better sort algorithm query['b_start'] = b_start query['b_size'] = num_per_page + orphans try: brains = catalog(self.context, **query) except Exception as err: logger.exception(err) return Batch([], 20, 0) if not brains: return Batch([], 20, 0) # Apply after query (filter) on brains start = time.time() for brains_filter in brains_filters: brains = brains_filter(brains, kwargs) if not batch: return brains if isinstance(brains, GeneratorType): brains = [brain for brain in brains] delta = time.time() - start if delta > 30: logger.warn("Very slow IWidgetFilterBrains adapters: %s at %s", brains_filters, self.context.absolute_url()) return Batch(brains, num_per_page, b_start, orphan=orphans)
def hidezerocount(self): """ Hide items that return no result? """ return bool(safeToInt(self.data.get('hidezerocount', 0)))
def sortcountable(self): """ Sort results by countable value? """ return bool(safeToInt(self.data.get('sortcountable', 0)))
def countable(self): """ Count results? """ return bool(safeToInt(self.data.get('count', 0)))
def manage_changeMapping(self, REQUEST=None): """Update mappings based on form data. Verify it returns nothing. More testing is done in the integration file. >>> from Products.AutoUserMakerPASPlugin.auth import \ ApacheAuthPluginHandler >>> handler = ApacheAuthPluginHandler('someId') >>> handler.manage_changeMapping() """ if not REQUEST: return None sources = self.getTokens() roles = self.getRoles() users = self.getUsers() groups = self.getGroups() authz = self.getMappings() # Pull the contents of the form in to a list formatted like authz to # allow us to check that the number of entries in the ZODB is the same # as the amount of input. This sort of handles somebody adding or # deleting a mapping from underneath somebody else. sets = [] for ii in REQUEST.form.iterkeys(): match = self.rKey.match(ii) if not match: continue index = int(match.group(2)) if len(sets) < index + 1: for jj in range(len(sets), index + 1): sets.append(self.getMapping()) # set up all values, from property as check on incoming form for kk in self.getProperty(httpAuthzTokensKey): sets[jj]['values'][kk] = '' # set up all roles, so ones not in REQUEST.form have # an entry for kk in roles: sets[jj]['roles'][kk['id']] = '' if match.group(1) == 'auth': for jj in range(len(REQUEST.form[ii])): sets[index]['values'][sources[jj]] = REQUEST.form[ii][jj] elif match.group(1) == 'userid': ii = REQUEST.form[ii] if ii in users: sets[index]['userid'] = ii elif match.group(1) == 'groupid': sets[index]['groupid'] = [] for ii in REQUEST.form[ii]: if ii in groups: sets[index]['groupid'].append(ii) elif sets[index]['roles'].has_key(match.group(1)): sets[index]['roles'][match.group(1)] = REQUEST.form[ii] if len(sets) != len(authz): return REQUEST.RESPONSE.redirect('%s/manage_authz' % self.absolute_url()) # now process delete checkboxes deleteIds = REQUEST.form.get('delete_ids', []) # make sure deleteIds is a list on integers, in descending order deleteIds = [ safeToInt(did) for did in deleteIds if safeToInt(did, None) != None ] deleteIds.sort(reverse=True) # now shorten without shifting indexes of items still to be removed for ii in deleteIds: try: sets.pop(ii) except IndexError: pass self.putMappings(sets) return REQUEST.RESPONSE.redirect('%s/manage_authz' % self.absolute_url())
def manage_changeConfig(self, REQUEST=None): """Update my configuration based on form data. Verify it returns nothing. More testing is done in the integration file. >>> from Products.AutoUserMakerPASPlugin.auth import \ ApacheAuthPluginHandler >>> handler = ApacheAuthPluginHandler('someId') >>> handler.manage_changeConfig() """ if not REQUEST: return None reqget = REQUEST.form.get strip = safeToInt(reqget(stripDomainNamesKey, 1), default=1) strip = max(min(strip, 2), 0) # 0 < x < 2 autoupdate = safeToInt(reqget(autoUpdateUserPropertiesKey, 0), default=0) autoupdate_interval = safeToInt(reqget( autoUpdateUserPropertiesIntervalKey, 0), default=0) # If Shib fields change, then update the authz_mappings property. tokens = self.getTokens() formTokens = tuple(reqget(httpAuthzTokensKey, '').splitlines()) if tokens != formTokens: for ii in self.getMappings(): saveVals = {} for jj in formTokens: if ii['values'].has_key(jj): saveVals[jj] = ii['values'][jj] else: saveVals[jj] = '' ii['values'] = saveVals # Save the form values self.manage_changeProperties({ stripDomainNamesKey: strip, stripDomainNamesListKey: reqget(stripDomainNamesListKey, ''), httpRemoteUserKey: reqget(httpRemoteUserKey, ''), httpCommonnameKey: reqget(httpCommonnameKey, ''), httpDescriptionKey: reqget(httpDescriptionKey, ''), httpEmailKey: reqget(httpEmailKey, ''), httpLocalityKey: reqget(httpLocalityKey, ''), httpStateKey: reqget(httpStateKey, ''), httpCountryKey: reqget(httpCountryKey, ''), autoUpdateUserPropertiesKey: autoupdate, autoUpdateUserPropertiesIntervalKey: autoupdate_interval, httpAuthzTokensKey: reqget(httpAuthzTokensKey, ''), httpSharingTokensKey: reqget(httpSharingTokensKey, ''), httpSharingLabelsKey: reqget(httpSharingLabelsKey, ''), useCustomRedirectionKey: reqget(useCustomRedirectionKey, False), challengeReplacementKey: reqget(challengeReplacementKey, ''), challengePatternKey: reqget(challengePatternKey, ''), challengeHeaderEnabledKey: reqget(challengeHeaderEnabledKey, False), challengeHeaderNameKey: reqget(challengeHeaderNameKey, ''), defaultRolesKey: reqget(defaultRolesKey, '') }) return REQUEST.RESPONSE.redirect('%s/manage_config' % self.absolute_url())
def intcompare(a, b): return cmp(safeToInt(a), safeToInt(b))
def _getSubfields(self, field, form): sizeFieldName = field.Schema().fields()[0].getName() size = safeToInt(form.get(sizeFieldName, 0)) fields = field.Schema().fields() fields = fields[1:1+size] return fields
def manage_changeMapping(self, REQUEST=None): """Update mappings based on form data. Verify it returns nothing. More testing is done in the integration file. >>> from Products.AutoUserMakerPASPlugin.auth import \ ApacheAuthPluginHandler >>> handler = ApacheAuthPluginHandler('someId') >>> handler.manage_changeMapping() """ if not REQUEST: return None sources = self.getTokens() roles = self.getRoles() users = self.getUsers() groups = self.getGroups() authz = self.getMappings() # Pull the contents of the form in to a list formatted like authz to # allow us to check that the number of entries in the ZODB is the same # as the amount of input. This sort of handles somebody adding or # deleting a mapping from underneath somebody else. sets = [] for ii in REQUEST.form.iterkeys(): match = self.rKey.match(ii) if not match: continue index = int(match.group(2)) if len(sets) < index + 1: for jj in range(len(sets), index + 1): sets.append(self.getMapping()) # set up all values, from property as check on incoming form for kk in self.getProperty(httpAuthzTokensKey): sets[jj]['values'][kk] = '' # set up all roles, so ones not in REQUEST.form have # an entry for kk in roles: sets[jj]['roles'][kk['id']] = '' if match.group(1) == 'auth': for jj in range(len(REQUEST.form[ii])): sets[index]['values'][sources[jj]] = REQUEST.form[ii][jj] elif match.group(1) == 'userid': ii = REQUEST.form[ii] if ii in users: sets[index]['userid'] = ii elif match.group(1) == 'groupid': sets[index]['groupid'] = [] for ii in REQUEST.form[ii]: if ii in groups: sets[index]['groupid'].append(ii) elif sets[index]['roles'].has_key(match.group(1)): sets[index]['roles'][match.group(1)] = REQUEST.form[ii] if len(sets) != len(authz): return REQUEST.RESPONSE.redirect('%s/manage_authz' % self.absolute_url()) # now process delete checkboxes deleteIds = REQUEST.form.get('delete_ids', []) # make sure deleteIds is a list on integers, in descending order deleteIds = [safeToInt(did) for did in deleteIds if safeToInt(did, None) != None] deleteIds.sort(reverse=True) # now shorten without shifting indexes of items still to be removed for ii in deleteIds: try: sets.pop(ii) except IndexError: pass self.putMappings(sets) return REQUEST.RESPONSE.redirect('%s/manage_authz' % self.absolute_url())
class FacetedQueryHandler(object): """ Faceted Query """ def __init__(self, context, request): self.context = context self.request = request if 'HTTP_X_REQUESTED_WITH' in request and request[ 'HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest': registry = getUtility(IRegistry) settings = registry.forInterface(IEEASettings) if settings.disable_diazo_rules_ajax: request.response.setHeader('X-Theme-Disabled', '1') def macros(self, name='content-core'): """ Return macro from default layout """ return IFacetedLayout(self.context).get_macro(macro=name) @property def language(self): """ Get context language """ lang = getattr(self.context, 'getLanguage', None) if lang: return lang() return self.request.get('LANGUAGE', '') @property def default_criteria(self): """ Return default criteria """ query = {} criteria = queryAdapter(self.context, ICriteria) for cid, criterion in criteria.items(): widget = criteria.widget(cid=cid) widget = widget(self.context, self.request, criterion) default = widget.default if not default: continue query[cid.encode('utf-8')] = default return query def get_context(self, content=None): """ Return context """ wrapper = queryAdapter(self.context, IFacetedWrapper) if not wrapper: return self.context return wrapper(content) def criteria(self, sort=True, **kwargs): """ Process catalog query """ if self.request: kwargs.update(self.request.form) # jQuery >= 1.4 adds type to params keys # $.param({ a: [2,3,4] }) // "a[]=2&a[]=3&a[]=4" # Let's fix this kwargs = dict( (key.replace('[]', ''), val) for key, val in kwargs.items()) logger.debug("REQUEST: %r", kwargs) # Generate the catalog query criteria = ICriteria(self.context) query = {} for cid, criterion in criteria.items(): widget = criteria.widget(cid=cid) widget = widget(self.context, self.request, criterion) query.update(widget.query(kwargs)) # Handle language widgets if criterion.get('index', '') == 'Language': language_widget = queryMultiAdapter((widget, self.context), ILanguageWidgetAdapter) if not language_widget: continue query.update(language_widget(kwargs)) # Add default sorting criteria if sort and not query.has_key('sort_on'): query['sort_on'] = 'effective' query['sort_order'] = 'reverse' # Add default language. # Also make sure to return language-independent content. lang = self.language if lang: lang = [lang, ''] query.setdefault('Language', lang) logger.debug('QUERY: %s', query) return query def query(self, batch=True, sort=True, **kwargs): """ Search using given criteria """ if self.request: kwargs.update(self.request.form) kwargs.pop('sort[]', None) kwargs.pop('sort', None) # jQuery >= 1.4 adds type to params keys # $.param({ a: [2,3,4] }) // "a[]=2&a[]=3&a[]=4" # Let's fix this kwargs = dict( (key.replace('[]', ''), val) for key, val in kwargs.items()) query = self.criteria(sort=sort, **kwargs) catalog = getUtility(IFacetedCatalog) try: brains = catalog(self.context, **query) except Exception, err: logger.exception(err) return Batch([], 20, 0) if not brains: return Batch([], 20, 0) # Apply after query (filter) on brains num_per_page = 20 criteria = ICriteria(self.context) for cid, criterion in criteria.items(): widgetclass = criteria.widget(cid=cid) widget = widgetclass(self.context, self.request, criterion) if widget.widget_type == 'resultsperpage': num_per_page = widget.results_per_page(kwargs) brains_filter = queryAdapter(widget, IWidgetFilterBrains) if brains_filter: brains = brains_filter(brains, kwargs) if not batch: return brains b_start = safeToInt(kwargs.get('b_start', 0)) orphans = num_per_page * 20 / 100 # orphans = 20% of items per page if type(brains) == GeneratorType: brains = [brain for brain in brains] return Batch(brains, num_per_page, b_start, orphan=orphans)