def test_get_locator(): class _Prop(object): @property def locator(self): return {'foo': 'bar'} class _Locator(object): @locator_property def locator(self): return {'foo': 'bar'} assert get_locator(_Prop()) == {'foo': 'bar'} assert get_locator(_Locator()) == {'foo': 'bar'} assert get_locator({'foo': 'bar'}) == {'foo': 'bar'}
def test_locator_property(): class _Test(object): @locator_property def locator(self): return {'foo': 'bar'} @locator.fancy def locator(self): return dict(self.locator, awesome='magic') assert isinstance(_Test.locator, locator_property) t = _Test() assert get_locator(t) == {'foo': 'bar'} assert get_locator(t.locator) == {'foo': 'bar'} assert get_locator(t.locator.fancy) == {'foo': 'bar', 'awesome': 'magic'} with pytest.raises(AttributeError): t.locator.invalid
def url_for(endpoint, *targets, **values): """Wrapper for Flask's url_for() function. Instead of an endpoint you can also pass an URLHandler - in this case **only** its _endpoint will be used. However, there is usually no need to do so. This is just so you can use it in places where sometimes a UH might be passed instead. The `target` argument allows you to pass some object having a `locator` property or `getLocator` method returning a dict. This should be used e.g. when generating an URL for an event since ``getLocator()`` provides the ``{'confId': 123}`` dict instead of you having to pass ``confId=event.getId()`` as a kwarg. For details on Flask's url_for, please see its documentation. Anyway, the important arguments you can put in `values` besides actual arguments are: _external: if set to `True`, an absolute URL is generated _secure: if True/False, set _scheme to https/http if possible (only with _external) _scheme: a string specifying the desired URL scheme (only with _external) - use _secure if possible! _anchor: if provided this is added as #anchor to the URL. """ if hasattr(endpoint, '_endpoint'): endpoint = endpoint._endpoint secure = values.pop('_secure', None) if secure is not None: from indico.core.config import Config if secure and Config.getInstance().getBaseSecureURL(): values['_scheme'] = 'https' elif not secure: values['_scheme'] = 'http' if targets: locator = {} for target in targets: if target: # don't fail on None or mako's Undefined locator.update(get_locator(target)) intersection = set(locator.iterkeys()) & set(values.iterkeys()) if intersection: raise ValueError('url_for kwargs collide with locator: %s' % ', '.join(intersection)) values.update(locator) static_site_mode = bool(ContextManager.get('offlineMode')) values.setdefault('_external', static_site_mode) for key, value in values.iteritems(): # Avoid =True and =False in the URL if isinstance(value, bool): values[key] = int(value) url = _url_for(endpoint, **values) if static_site_mode and not values['_external']: # for static sites we assume all relative urls need to be # mangled to a filename # we should really fine a better way to handle anything # related to offline site urls... from indico.modules.events.static.util import url_to_static_filename url = url_to_static_filename(url) return url
def normalize_url(self): """Performs URL normalization. This uses the :attr:`normalize_url_spec` to check if the URL params are what they should be and redirects or fails depending on the HTTP method used if it's not the case. :return: ``None`` or a redirect response """ if not self.normalize_url_spec or not any(self.normalize_url_spec.itervalues()): return spec = { 'args': self.normalize_url_spec.get('args', {}), 'locators': self.normalize_url_spec.get('locators', set()), 'preserved_args': self.normalize_url_spec.get('preserved_args', set()), } # Initialize the new view args with preserved arguments (since those would be lost otherwise) new_view_args = {k: v for k, v in request.view_args.iteritems() if k in spec['preserved_args']} # Retrieve the expected values for all simple arguments (if they are currently present) for key, getter in spec['args'].iteritems(): if key in request.view_args: new_view_args[key] = getter(self) # Retrieve the expected values from locators for getter in spec['locators']: value = getter(self) if value is None: raise NotFound('The URL contains invalid data. Please go to the previous page and refresh it.') new_view_args.update(get_locator(value)) # Get all default values provided by the url map for the endpoint defaults = set(itertools.chain.from_iterable(r.defaults for r in current_app.url_map.iter_rules(request.endpoint) if r.defaults)) def _convert(v): # some legacy code has numeric ids in the locator data, but still takes # string ids in the url rule (usually for confId) return unicode(v) if isinstance(v, (int, long)) else v provided = {k: _convert(v) for k, v in request.view_args.iteritems() if k not in defaults} new_view_args = {k: _convert(v) for k, v in new_view_args.iteritems()} if new_view_args != provided: if request.method in {'GET', 'HEAD'}: try: return redirect(url_for(request.endpoint, **dict(request.args.to_dict(), **new_view_args))) except BuildError as e: if current_app.debug: raise Logger.get('requestHandler').warn('BuildError during normalization: %s', e) raise NotFound else: raise NotFound('The URL contains invalid data. Please go to the previous page and refresh it.')
def url_for(endpoint, *targets, **values): """Wrapper for Flask's url_for() function. The `target` argument allows you to pass some object having a `locator` property returning a dict. For details on Flask's url_for, please see its documentation. The important special arguments you can put in `values` are: _external: if set to `True`, an absolute URL is generated _scheme: a string specifying the desired URL scheme (only with _external) - use _secure if possible! _anchor: if provided this is added as #anchor to the URL. """ if targets: locator = {} for target in targets: if target: # don't fail on None or mako's Undefined locator.update(get_locator(target)) intersection = set(locator.keys()) & set(values.keys()) if intersection: raise ValueError('url_for kwargs collide with locator: %s' % ', '.join(intersection)) values.update(locator) for key, value in values.items(): # Avoid =True and =False in the URL if isinstance(value, bool): values[key] = int(value) values.setdefault('_external', False) values = dict(sorted(values.items())) url = _url_for(endpoint, **values) if g.get('static_site' ) and 'custom_manifests' in g and not values.get('_external'): # for static sites we assume all relative urls need to be # mangled to a filename # we should really fine a better way to handle anything # related to offline site urls... from indico.modules.events.static.util import url_to_static_filename url = url_to_static_filename(endpoint, url) # mark asset as used so that generator can include it g.used_url_for_assets.add(url) return url
def url_for(endpoint, *targets, **values): """Wrapper for Flask's url_for() function. The `target` argument allows you to pass some object having a `locator` property returning a dict. For details on Flask's url_for, please see its documentation. The important special arguments you can put in `values` are: _external: if set to `True`, an absolute URL is generated _scheme: a string specifying the desired URL scheme (only with _external) - use _secure if possible! _anchor: if provided this is added as #anchor to the URL. """ if targets: locator = {} for target in targets: if target: # don't fail on None or mako's Undefined locator.update(get_locator(target)) intersection = set(locator.iterkeys()) & set(values.iterkeys()) if intersection: raise ValueError('url_for kwargs collide with locator: %s' % ', '.join(intersection)) values.update(locator) for key, value in values.iteritems(): # Avoid =True and =False in the URL if isinstance(value, bool): values[key] = int(value) url = _url_for(endpoint, **values) if g.get('static_site') and 'custom_manifests' in g and not values.get('_external'): # for static sites we assume all relative urls need to be # mangled to a filename # we should really fine a better way to handle anything # related to offline site urls... from indico.modules.events.static.util import url_to_static_filename url = url_to_static_filename(endpoint, url) # mark asset as used so that generator can include it g.used_url_for_assets.add(url) return url
def normalize_url(self): """Performs URL normalization. This uses the :attr:`normalize_url_spec` to check if the URL params are what they should be and redirects or fails depending on the HTTP method used if it's not the case. :return: ``None`` or a redirect response """ if current_app.debug and self.normalize_url_spec is RH.normalize_url_spec: # in case of ``class SomeRH(RH, MixinWithNormalization)`` # the default value from `RH` overwrites the normalization # rule from ``MixinWithNormalization``. this is never what # the developer wants so we fail if it happens. the proper # solution is ``class SomeRH(MixinWithNormalization, RH)`` cls = next((x for x in inspect.getmro(self.__class__) if (x is not RH and x is not self.__class__ and hasattr(x, 'normalize_url_spec') and getattr(x, 'normalize_url_spec', None) is not RH.normalize_url_spec)), None) if cls is not None: raise Exception( 'Normalization rule of {} in {} is overwritten by base RH. Put mixins with class-level ' 'attributes on the left of the base class'.format( cls, self.__class__)) if not self.normalize_url_spec or not any( self.normalize_url_spec.itervalues()): return spec = { 'args': self.normalize_url_spec.get('args', {}), 'locators': self.normalize_url_spec.get('locators', set()), 'preserved_args': self.normalize_url_spec.get('preserved_args', set()), 'endpoint': self.normalize_url_spec.get('endpoint', None) } # Initialize the new view args with preserved arguments (since those would be lost otherwise) new_view_args = { k: v for k, v in request.view_args.iteritems() if k in spec['preserved_args'] } # Retrieve the expected values for all simple arguments (if they are currently present) for key, getter in spec['args'].iteritems(): if key in request.view_args: new_view_args[key] = getter(self) # Retrieve the expected values from locators prev_locator_args = {} for getter in spec['locators']: value = getter(self) if value is None: raise NotFound( 'The URL contains invalid data. Please go to the previous page and refresh it.' ) locator_args = get_locator(value) reused_keys = set(locator_args) & prev_locator_args.viewkeys() if any(locator_args[k] != prev_locator_args[k] for k in reused_keys): raise NotFound( 'The URL contains invalid data. Please go to the previous page and refresh it.' ) new_view_args.update(locator_args) prev_locator_args.update(locator_args) # Get all default values provided by the url map for the endpoint defaults = set( itertools.chain.from_iterable( r.defaults for r in current_app.url_map.iter_rules(request.endpoint) if r.defaults)) def _convert(v): # some legacy code has numeric ids in the locator data, but still takes # string ids in the url rule (usually for confId) return unicode(v) if isinstance(v, (int, long)) else v provided = { k: _convert(v) for k, v in request.view_args.iteritems() if k not in defaults } new_view_args = {k: _convert(v) for k, v in new_view_args.iteritems()} if new_view_args != provided: if request.method in {'GET', 'HEAD'}: endpoint = spec['endpoint'] or request.endpoint try: return redirect( url_for( endpoint, **dict(request.args.to_dict(), **new_view_args))) except BuildError as e: if current_app.debug: raise logger.warn('BuildError during normalization: %s', e) raise NotFound else: raise NotFound( 'The URL contains invalid data. Please go to the previous page and refresh it.' )
def test_get_locator_none(): with pytest.raises(TypeError): get_locator(object()) with pytest.raises(TypeError): get_locator(None)
def normalize_url(self): """Performs URL normalization. This uses the :attr:`normalize_url_spec` to check if the URL params are what they should be and redirects or fails depending on the HTTP method used if it's not the case. :return: ``None`` or a redirect response """ if current_app.debug and self.normalize_url_spec is RH.normalize_url_spec: # in case of ``class SomeRH(RH, MixinWithNormalization)`` # the default value from `RH` overwrites the normalization # rule from ``MixinWithNormalization``. this is never what # the developer wants so we fail if it happens. the proper # solution is ``class SomeRH(MixinWithNormalization, RH)`` cls = next((x for x in inspect.getmro(self.__class__) if (x is not RH and x is not self.__class__ and hasattr(x, 'normalize_url_spec') and getattr(x, 'normalize_url_spec', None) is not RH.normalize_url_spec)), None) if cls is not None: raise Exception('Normalization rule of {} in {} is overwritten by base RH. Put mixins with class-level ' 'attributes on the left of the base class'.format(cls, self.__class__)) if not self.normalize_url_spec or not any(self.normalize_url_spec.itervalues()): return spec = { 'args': self.normalize_url_spec.get('args', {}), 'locators': self.normalize_url_spec.get('locators', set()), 'preserved_args': self.normalize_url_spec.get('preserved_args', set()), 'endpoint': self.normalize_url_spec.get('endpoint', None) } # Initialize the new view args with preserved arguments (since those would be lost otherwise) new_view_args = {k: v for k, v in request.view_args.iteritems() if k in spec['preserved_args']} # Retrieve the expected values for all simple arguments (if they are currently present) for key, getter in spec['args'].iteritems(): if key in request.view_args: new_view_args[key] = getter(self) # Retrieve the expected values from locators prev_locator_args = {} for getter in spec['locators']: value = getter(self) if value is None: raise NotFound('The URL contains invalid data. Please go to the previous page and refresh it.') locator_args = get_locator(value) reused_keys = set(locator_args) & prev_locator_args.viewkeys() if any(locator_args[k] != prev_locator_args[k] for k in reused_keys): raise NotFound('The URL contains invalid data. Please go to the previous page and refresh it.') new_view_args.update(locator_args) prev_locator_args.update(locator_args) # Get all default values provided by the url map for the endpoint defaults = set(itertools.chain.from_iterable(r.defaults for r in current_app.url_map.iter_rules(request.endpoint) if r.defaults)) def _convert(v): # some legacy code has numeric ids in the locator data, but still takes # string ids in the url rule (usually for confId) return unicode(v) if isinstance(v, (int, long)) else v provided = {k: _convert(v) for k, v in request.view_args.iteritems() if k not in defaults} new_view_args = {k: _convert(v) for k, v in new_view_args.iteritems()} if new_view_args != provided: if request.method in {'GET', 'HEAD'}: endpoint = spec['endpoint'] or request.endpoint try: return redirect(url_for(endpoint, **dict(request.args.to_dict(), **new_view_args))) except BuildError as e: if current_app.debug: raise logger.warn('BuildError during normalization: %s', e) raise NotFound else: raise NotFound('The URL contains invalid data. Please go to the previous page and refresh it.')