from urllib.parse import urlsplit from django.conf import settings from django.utils.encoding import force_str from wagtail.coreutils import resolve_model_string from wagtail.models import Page, Site class BadRequestError(Exception): pass def get_base_url(request=None): base_url = getattr(settings, "WAGTAILAPI_BASE_URL", None) if base_url is None and request: site = Site.find_for_request(request) if site: base_url = site.root_url if base_url: # We only want the scheme and netloc base_url_parsed = urlsplit(force_str(base_url)) return base_url_parsed.scheme + "://" + base_url_parsed.netloc def get_full_url(request, path): if path.startswith(("http://", "https://")): return path base_url = get_base_url(request) or "" return base_url + path def get_object_detail_url(router, request, model, pk): url_path = router.get_object_detail_urlpath(model, pk) if url_path: return get_full_url(request, url_path) def page_models_from_string(string): page_models = [] for sub_string in string.split(","): page_model = resolve_model_string(sub_string) if not issubclass(page_model, Page): raise ValueError("Model is not a page") page_models.append(page_model) return tuple(page_models) class FieldsParameterParseError(ValueError): pass def parse_fields_parameter(fields_str): """ Parses the ?fields= GET parameter. As this parameter is supposed to be used by developers, the syntax is quite tight (eg, not allowing any whitespace). Having a strict syntax allows us to extend the it at a later date with less chance of breaking anyone's code. This function takes a string and returns a list of tuples representing each top-level field. Each tuple contains three items: - The name of the field (string) - Whether the field has been negated (boolean) - A list of nested fields if there are any, None otherwise Some examples of how this function works: >>> parse_fields_parameter("foo") [ ('foo', False, None), ] >>> parse_fields_parameter("foo,bar") [ ('foo', False, None), ('bar', False, None), ] >>> parse_fields_parameter("-foo") [ ('foo', True, None), ] >>> parse_fields_parameter("foo(bar,baz)") [ ('foo', False, [ ('bar', False, None), ('baz', False, None), ]), ] It raises a FieldsParameterParseError (subclass of ValueError) if it encounters a syntax error """ def get_position(current_str): return len(fields_str) - len(current_str) def parse_field_identifier(fields_str): first_char = True negated = False ident = "" while fields_str: char = fields_str[0] if char in ["(", ")", ","]: if not ident: raise FieldsParameterParseError( "unexpected char '%s' at position %d" % (char, get_position(fields_str)) ) if ident in ["*", "_"] and char == "(": # * and _ cannot have nested fields raise FieldsParameterParseError( "unexpected char '%s' at position %d" % (char, get_position(fields_str)) ) return ident, negated, fields_str elif char == "-": if not first_char: raise FieldsParameterParseError( "unexpected char '%s' at position %d" % (char, get_position(fields_str)) ) negated = True elif char in ["*", "_"]: if ident and char == "*": raise FieldsParameterParseError( "unexpected char '%s' at position %d" % (char, get_position(fields_str)) ) ident += char elif char.isalnum() or char == "_": if ident == "*": # * can only be on its own raise FieldsParameterParseError( "unexpected char '%s' at position %d" % (char, get_position(fields_str)) ) ident += char elif char.isspace(): raise FieldsParameterParseError( "unexpected whitespace at position %d" % get_position(fields_str) ) else: raise FieldsParameterParseError( "unexpected char '%s' at position %d" % (char, get_position(fields_str)) ) first_char = False fields_str = fields_str[1:] return ident, negated, fields_str def parse_fields(fields_str, expect_close_bracket=False): first_ident = None is_first = True fields = [] while fields_str: sub_fields = None ident, negated, fields_str = parse_field_identifier(fields_str) # Some checks specific to '*' and '_' if ident in ["*", "_"]: if not is_first: raise FieldsParameterParseError( "'%s' must be in the first position" % ident ) if negated: raise FieldsParameterParseError("'%s' cannot be negated" % ident) if fields_str and fields_str[0] == "(": if negated: # Negated fields cannot contain subfields raise FieldsParameterParseError( "unexpected char '(' at position %d" % get_position(fields_str) ) sub_fields, fields_str = parse_fields( fields_str[1:], expect_close_bracket=True ) if is_first: first_ident = ident else: # Negated fields can't be used with '_' if first_ident == "_" and negated: # _,foo is allowed but _,-foo is not raise FieldsParameterParseError( "negated fields with '_' doesn't make sense" ) # Additional fields without sub fields can't be used with '*' if first_ident == "*" and not negated and not sub_fields: # *,foo(bar) and *,-foo are allowed but *,foo is not raise FieldsParameterParseError( "additional fields with '*' doesn't make sense" ) fields.append((ident, negated, sub_fields)) if fields_str and fields_str[0] == ")": if not expect_close_bracket: raise FieldsParameterParseError( "unexpected char ')' at position %d" % get_position(fields_str) ) return fields, fields_str[1:] if fields_str and fields_str[0] == ",": fields_str = fields_str[1:] # A comma can not exist immediately before another comma or the end of the string if not fields_str or fields_str[0] == ",": raise FieldsParameterParseError( "unexpected char ',' at position %d" % get_position(fields_str) ) is_first = False if expect_close_bracket: # This parser should've exited with a close bracket but instead we # hit the end of the input. Raise an error raise FieldsParameterParseError( "unexpected end of input (did you miss out a close bracket?)" ) return fields, fields_str fields, _ = parse_fields(fields_str) return fields def parse_boolean(value): """ Parses strings into booleans using the following mapping (case-sensitive): 'true' => True 'false' => False '1' => True '0' => False """ if value in ["true", "1"]: return True elif value in ["false", "0"]: return False else: raise ValueError("expected 'true' or 'false', got '%s'" % value)