mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-13 17:53:12 +00:00
Fixes #3967 If there was no site configured, the listing views in the API would break with an AttributeError. Now, the following happens if there is no site record: - In the Admin API, all pages are returned but links in the response do not include a domain name - In the Public API, no pages are returned
235 lines
7.5 KiB
Python
235 lines
7.5 KiB
Python
from __future__ import absolute_import, unicode_literals
|
|
|
|
from django.conf import settings
|
|
from django.utils.six.moves.urllib.parse import urlparse
|
|
|
|
from wagtail.wagtailcore.models import Page
|
|
from wagtail.wagtailcore.utils import resolve_model_string
|
|
|
|
|
|
class BadRequestError(Exception):
|
|
pass
|
|
|
|
|
|
def get_base_url(request=None):
|
|
base_url = getattr(settings, 'WAGTAILAPI_BASE_URL', request.site.root_url if request and request.site else None)
|
|
|
|
if base_url:
|
|
# We only want the scheme and netloc
|
|
base_url_parsed = urlparse(base_url)
|
|
|
|
return base_url_parsed.scheme + '://' + base_url_parsed.netloc
|
|
|
|
|
|
def get_full_url(request, path):
|
|
base_url = get_base_url(request) or ''
|
|
return base_url + path
|
|
|
|
|
|
def pages_for_site(site):
|
|
pages = Page.objects.public().live()
|
|
pages = pages.descendant_of(site.root_page, inclusive=True)
|
|
return pages
|
|
|
|
|
|
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)
|
|
|
|
|
|
def filter_page_type(queryset, page_models):
|
|
qs = queryset.none()
|
|
|
|
for model in page_models:
|
|
qs |= queryset.type(model)
|
|
|
|
return qs
|
|
|
|
|
|
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)
|