django-gisserver 1.5.0__py3-none-any.whl → 2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/METADATA +14 -4
- django_gisserver-2.0.dist-info/RECORD +66 -0
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/WHEEL +1 -1
- gisserver/__init__.py +1 -1
- gisserver/compat.py +23 -0
- gisserver/conf.py +7 -0
- gisserver/db.py +56 -47
- gisserver/exceptions.py +26 -2
- gisserver/extensions/__init__.py +4 -0
- gisserver/{parsers/fes20 → extensions}/functions.py +10 -4
- gisserver/extensions/queries.py +261 -0
- gisserver/features.py +220 -156
- gisserver/geometries.py +32 -37
- gisserver/management/__init__.py +0 -0
- gisserver/management/commands/__init__.py +0 -0
- gisserver/management/commands/loadgeojson.py +291 -0
- gisserver/operations/base.py +122 -308
- gisserver/operations/wfs20.py +423 -337
- gisserver/output/__init__.py +9 -48
- gisserver/output/base.py +178 -139
- gisserver/output/csv.py +65 -74
- gisserver/output/geojson.py +34 -35
- gisserver/output/gml32.py +254 -246
- gisserver/output/iters.py +207 -0
- gisserver/output/results.py +52 -26
- gisserver/output/stored.py +143 -0
- gisserver/output/utils.py +75 -170
- gisserver/output/xmlschema.py +85 -46
- gisserver/parsers/__init__.py +10 -10
- gisserver/parsers/ast.py +320 -0
- gisserver/parsers/fes20/__init__.py +13 -27
- gisserver/parsers/fes20/expressions.py +82 -38
- gisserver/parsers/fes20/filters.py +111 -43
- gisserver/parsers/fes20/identifiers.py +44 -26
- gisserver/parsers/fes20/lookups.py +144 -0
- gisserver/parsers/fes20/operators.py +331 -127
- gisserver/parsers/fes20/sorting.py +104 -33
- gisserver/parsers/gml/__init__.py +12 -11
- gisserver/parsers/gml/base.py +5 -2
- gisserver/parsers/gml/geometries.py +69 -35
- gisserver/parsers/ows/__init__.py +25 -0
- gisserver/parsers/ows/kvp.py +190 -0
- gisserver/parsers/ows/requests.py +158 -0
- gisserver/parsers/query.py +175 -0
- gisserver/parsers/values.py +26 -0
- gisserver/parsers/wfs20/__init__.py +37 -0
- gisserver/parsers/wfs20/adhoc.py +245 -0
- gisserver/parsers/wfs20/base.py +143 -0
- gisserver/parsers/wfs20/projection.py +103 -0
- gisserver/parsers/wfs20/requests.py +482 -0
- gisserver/parsers/wfs20/stored.py +192 -0
- gisserver/parsers/xml.py +249 -0
- gisserver/projection.py +357 -0
- gisserver/static/gisserver/index.css +12 -1
- gisserver/templates/gisserver/index.html +1 -1
- gisserver/templates/gisserver/service_description.html +2 -2
- gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml +9 -9
- gisserver/templates/gisserver/wfs/feature_field.html +2 -2
- gisserver/templatetags/gisserver_tags.py +20 -0
- gisserver/types.py +322 -259
- gisserver/views.py +198 -56
- django_gisserver-1.5.0.dist-info/RECORD +0 -54
- gisserver/parsers/base.py +0 -149
- gisserver/parsers/fes20/query.py +0 -285
- gisserver/parsers/tags.py +0 -102
- gisserver/queries/__init__.py +0 -37
- gisserver/queries/adhoc.py +0 -185
- gisserver/queries/base.py +0 -186
- gisserver/queries/projection.py +0 -240
- gisserver/queries/stored.py +0 -206
- gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml +0 -20
- gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml +0 -14
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/LICENSE +0 -0
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Entry point to handle queries.
|
|
2
|
+
|
|
3
|
+
WFS defines 2 query types:
|
|
4
|
+
- Adhoc queries are constructed directly from request parameters.
|
|
5
|
+
- Stored queries are defined first, and executed later.
|
|
6
|
+
|
|
7
|
+
Both use the FES (Filter Encoding Syntax) filtering logic internally.
|
|
8
|
+
|
|
9
|
+
The objects in this module closely follow the WFS spec.
|
|
10
|
+
By using the same type definitions, a lot of code logic follows naturally.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import ClassVar
|
|
16
|
+
|
|
17
|
+
from django.db.models import Q, QuerySet
|
|
18
|
+
|
|
19
|
+
from gisserver.exceptions import (
|
|
20
|
+
ExternalValueError,
|
|
21
|
+
InvalidParameterValue,
|
|
22
|
+
OperationNotSupported,
|
|
23
|
+
)
|
|
24
|
+
from gisserver.features import FeatureType
|
|
25
|
+
from gisserver.parsers import fes20, wfs20
|
|
26
|
+
from gisserver.parsers.ast import BaseNode
|
|
27
|
+
from gisserver.parsers.query import CompiledQuery
|
|
28
|
+
from gisserver.projection import FeatureProjection
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class QueryExpression(BaseNode):
|
|
32
|
+
"""WFS base class for all queries.
|
|
33
|
+
This object type is defined in the WFS spec (as ``<fes:AbstractQueryExpression>``).
|
|
34
|
+
|
|
35
|
+
The WFS server can initiate queries in multiple ways.
|
|
36
|
+
This class provides the common interface for all these query types;
|
|
37
|
+
whether the request provided "ad-hoc" parameters or called a stored procedure.
|
|
38
|
+
Each query type has its own way of generating the actual database statement to perform.
|
|
39
|
+
|
|
40
|
+
The subclasses can override the following logic:
|
|
41
|
+
|
|
42
|
+
* :meth:`get_type_names` defines which types this query applies to.
|
|
43
|
+
* :meth:`build_query` defines how to filter the queryset.
|
|
44
|
+
|
|
45
|
+
For full control, these methods can also be overwritten instead:
|
|
46
|
+
|
|
47
|
+
* :meth:`get_queryset` defines the full results.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
query_locator: ClassVar[str] = None
|
|
51
|
+
|
|
52
|
+
# QueryExpression
|
|
53
|
+
handle: str = ""
|
|
54
|
+
|
|
55
|
+
# Projection parameters (overwritten by subclasses)
|
|
56
|
+
# In the WFS spec, this is only part of the operation/presentation.
|
|
57
|
+
# For Django, we'd like to make this part of the query too.
|
|
58
|
+
property_names: list[wfs20.PropertyName] | None = None # PropertyName
|
|
59
|
+
value_reference: fes20.ValueReference | None = None # GetPropertyValue call
|
|
60
|
+
|
|
61
|
+
def bind(
|
|
62
|
+
self,
|
|
63
|
+
feature_types: list[FeatureType],
|
|
64
|
+
value_reference: fes20.ValueReference | None = None,
|
|
65
|
+
):
|
|
66
|
+
"""Bind the query to context outside its tag definition.
|
|
67
|
+
|
|
68
|
+
:param feature_types: The corresponding feature types for :meth:`get_type_names`.
|
|
69
|
+
:param value_reference: Which field is returned (by ``GetPropertyValue``)
|
|
70
|
+
"""
|
|
71
|
+
# Store the resolved feature types
|
|
72
|
+
self.feature_types = feature_types
|
|
73
|
+
|
|
74
|
+
# for GetPropertyValue
|
|
75
|
+
if value_reference is not None:
|
|
76
|
+
self.value_reference = value_reference
|
|
77
|
+
|
|
78
|
+
def get_queryset(self) -> QuerySet:
|
|
79
|
+
"""Generate the queryset for the specific feature type.
|
|
80
|
+
|
|
81
|
+
This method can be overwritten in subclasses to define the returned data.
|
|
82
|
+
However, consider overwriting :meth:`build_query` instead of simple data.
|
|
83
|
+
"""
|
|
84
|
+
if len(self.feature_types) > 1:
|
|
85
|
+
raise OperationNotSupported("Join queries are not supported", locator="typeNames")
|
|
86
|
+
|
|
87
|
+
# To apply the filters, an internal CompiledQuery object is created.
|
|
88
|
+
# This collects all steps, to create the final QuerySet object.
|
|
89
|
+
# The build_query() method may return an Q object, or fill the compiler itself.
|
|
90
|
+
compiler = CompiledQuery(self.feature_types)
|
|
91
|
+
q_object = self.build_query(compiler)
|
|
92
|
+
if q_object is not None:
|
|
93
|
+
compiler.add_lookups(q_object)
|
|
94
|
+
|
|
95
|
+
# While property names are projection, this hook should
|
|
96
|
+
# make it possible to perform extra query adjustments for complex expressions.
|
|
97
|
+
if self.property_names:
|
|
98
|
+
for property_name in self.property_names:
|
|
99
|
+
property_name.decorate_query(compiler)
|
|
100
|
+
|
|
101
|
+
if self.value_reference is not None:
|
|
102
|
+
try:
|
|
103
|
+
self.value_reference.parse_xpath(self.feature_types)
|
|
104
|
+
except ExternalValueError as e:
|
|
105
|
+
raise InvalidParameterValue(
|
|
106
|
+
f"Field '{self.value_reference.xpath}' does not exist.",
|
|
107
|
+
locator="valueReference",
|
|
108
|
+
) from e
|
|
109
|
+
|
|
110
|
+
# For GetPropertyValue, adjust the query so only that value is requested.
|
|
111
|
+
# This makes sure XPath attribute selectors are already handled by the
|
|
112
|
+
# database query, instead of being a presentation-layer handling.
|
|
113
|
+
# This supports cases like: ``addresses/Address[street="Oxfordstrasse"]/number``
|
|
114
|
+
field = self.value_reference.build_rhs(compiler)
|
|
115
|
+
|
|
116
|
+
queryset = compiler.get_queryset()
|
|
117
|
+
return queryset.values("pk", member=field)
|
|
118
|
+
else:
|
|
119
|
+
return compiler.get_queryset()
|
|
120
|
+
|
|
121
|
+
def get_type_names(self) -> list[str]:
|
|
122
|
+
"""Tell which type names this query applies to.
|
|
123
|
+
|
|
124
|
+
This method needs to be defined in subclasses.
|
|
125
|
+
"""
|
|
126
|
+
raise NotImplementedError(
|
|
127
|
+
f"{self.__class__.__name__}.get_type_names() should be implemented."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def get_projection(self) -> FeatureProjection:
|
|
131
|
+
"""Tell how the query should be displayed."""
|
|
132
|
+
raise NotImplementedError()
|
|
133
|
+
|
|
134
|
+
def build_query(self, compiler: CompiledQuery) -> Q | None:
|
|
135
|
+
"""Define the compiled query that filters the queryset."""
|
|
136
|
+
raise NotImplementedError()
|
|
137
|
+
|
|
138
|
+
def as_kvp(self):
|
|
139
|
+
"""Translate the POST request into KVP GET parameters. This is needed for pagination."""
|
|
140
|
+
params = {}
|
|
141
|
+
if self.property_names:
|
|
142
|
+
params["PROPERTYNAME"] = ",".join(p.xpath for p in self.property_names)
|
|
143
|
+
return params
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Projection tag parsing for WFS 2.0"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
from gisserver.exceptions import (
|
|
9
|
+
InvalidParameterValue,
|
|
10
|
+
OperationParsingFailed,
|
|
11
|
+
OperationProcessingFailed,
|
|
12
|
+
)
|
|
13
|
+
from gisserver.parsers.ast import BaseNode, tag_registry
|
|
14
|
+
from gisserver.parsers.query import CompiledQuery
|
|
15
|
+
from gisserver.parsers.xml import NSElement, xmlns
|
|
16
|
+
from gisserver.types import XPathMatch
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ResolveValue(Enum):
|
|
20
|
+
"""The wfs:ResolveValueType enum, used by :class:`StandardResolveParameters`."""
|
|
21
|
+
|
|
22
|
+
local = "local"
|
|
23
|
+
remote = "remote"
|
|
24
|
+
all = "all"
|
|
25
|
+
none = "none"
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def _missing_(cls, value):
|
|
29
|
+
raise OperationParsingFailed(f"Invalid resolve value: {value}", locator="resolve")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
@tag_registry.register("PropertyName", xmlns.wfs)
|
|
34
|
+
class PropertyName(BaseNode):
|
|
35
|
+
"""The ``<wfs:PropertyName>`` element in the projection clause.
|
|
36
|
+
|
|
37
|
+
This parses and handles the syntax::
|
|
38
|
+
|
|
39
|
+
<wfs:PropertyName>ns0:tagname</wfs:PropertyName>
|
|
40
|
+
|
|
41
|
+
More advanced syntax is not supported, such as::
|
|
42
|
+
|
|
43
|
+
<wfs:PropertyName resolve="all" resolvePath="valueOf(relatedTo)">valueOf(registerEntry)</wfs:PropertyName>
|
|
44
|
+
|
|
45
|
+
Note this element exists in the WFS namespace, not the FES namespace!
|
|
46
|
+
The ``<fes:PropertyName>`` is an old FES 1.x element that has been replaced by ``<fes:ValueReference>``.
|
|
47
|
+
The old ``<fes:PropertyName>`` is still supported as an alias by this server.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
xpath: str # Officially only a 'QName'
|
|
51
|
+
xpath_ns_aliases: dict[str, str]
|
|
52
|
+
|
|
53
|
+
# Unused, yet included for completeness.
|
|
54
|
+
# These are available in the XML syntax, and override
|
|
55
|
+
# the default given in the <wfs:GetFeature> element.
|
|
56
|
+
resolve: ResolveValue = ResolveValue.none
|
|
57
|
+
resolve_depth: int | None = None
|
|
58
|
+
resolve_path: str | None = None
|
|
59
|
+
resolve_timeout: int = 300
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_xml(cls, element: NSElement):
|
|
63
|
+
"""Parse the XML tag."""
|
|
64
|
+
depth = parse_resolve_depth(element.attrib.get("resolveDepth", None))
|
|
65
|
+
return cls(
|
|
66
|
+
xpath=element.text,
|
|
67
|
+
resolve=ResolveValue[element.attrib.get("resolve", "none")],
|
|
68
|
+
resolve_depth=depth,
|
|
69
|
+
resolve_path=element.attrib.get("resolvePath"),
|
|
70
|
+
resolve_timeout=element.get_int_attribute("resolveTimeout", 300),
|
|
71
|
+
xpath_ns_aliases=element.ns_aliases,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def parse_xpath(self, feature_types: list) -> XPathMatch:
|
|
75
|
+
"""Convert the XPath into the required ORM query elements."""
|
|
76
|
+
if self.resolve_path:
|
|
77
|
+
raise OperationProcessingFailed("resolvePath is not supported", locator="propertyName")
|
|
78
|
+
if "valueOf(" in self.xpath or (self.resolve_path and "valueOf(" in self.resolve_path):
|
|
79
|
+
raise OperationProcessingFailed(
|
|
80
|
+
"valueOf(..) syntax is not supported", locator="propertyName"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Can resolve against XSD paths, find the correct DB field name
|
|
84
|
+
return feature_types[0].resolve_element(self.xpath, self.xpath_ns_aliases)
|
|
85
|
+
|
|
86
|
+
def decorate_query(self, compiler: CompiledQuery):
|
|
87
|
+
"""Update the low-level query based on this property projection."""
|
|
88
|
+
# This will also validate the name because it resolves the ORM path.
|
|
89
|
+
# The actual limiting of fields happens inside the decorate_queryset() of the renderer.
|
|
90
|
+
xpath_match = self.parse_xpath(compiler.feature_types)
|
|
91
|
+
if xpath_match.orm_filters:
|
|
92
|
+
# Apply any xpath [attr=value] lookups.
|
|
93
|
+
compiler.add_extra_lookup(xpath_match.orm_filters)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def parse_resolve_depth(depth: str | None) -> int | None:
|
|
97
|
+
"""Parse the resolve depth value."""
|
|
98
|
+
try:
|
|
99
|
+
return None if depth is None or depth == "*" else int(depth)
|
|
100
|
+
except (ValueError, TypeError) as e:
|
|
101
|
+
raise InvalidParameterValue(
|
|
102
|
+
"ResolveDepth must be an integer or '*'", locator="resolveDepth"
|
|
103
|
+
) from e
|