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.
Files changed (74) hide show
  1. {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/METADATA +14 -4
  2. django_gisserver-2.0.dist-info/RECORD +66 -0
  3. {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/WHEEL +1 -1
  4. gisserver/__init__.py +1 -1
  5. gisserver/compat.py +23 -0
  6. gisserver/conf.py +7 -0
  7. gisserver/db.py +56 -47
  8. gisserver/exceptions.py +26 -2
  9. gisserver/extensions/__init__.py +4 -0
  10. gisserver/{parsers/fes20 → extensions}/functions.py +10 -4
  11. gisserver/extensions/queries.py +261 -0
  12. gisserver/features.py +220 -156
  13. gisserver/geometries.py +32 -37
  14. gisserver/management/__init__.py +0 -0
  15. gisserver/management/commands/__init__.py +0 -0
  16. gisserver/management/commands/loadgeojson.py +291 -0
  17. gisserver/operations/base.py +122 -308
  18. gisserver/operations/wfs20.py +423 -337
  19. gisserver/output/__init__.py +9 -48
  20. gisserver/output/base.py +178 -139
  21. gisserver/output/csv.py +65 -74
  22. gisserver/output/geojson.py +34 -35
  23. gisserver/output/gml32.py +254 -246
  24. gisserver/output/iters.py +207 -0
  25. gisserver/output/results.py +52 -26
  26. gisserver/output/stored.py +143 -0
  27. gisserver/output/utils.py +75 -170
  28. gisserver/output/xmlschema.py +85 -46
  29. gisserver/parsers/__init__.py +10 -10
  30. gisserver/parsers/ast.py +320 -0
  31. gisserver/parsers/fes20/__init__.py +13 -27
  32. gisserver/parsers/fes20/expressions.py +82 -38
  33. gisserver/parsers/fes20/filters.py +111 -43
  34. gisserver/parsers/fes20/identifiers.py +44 -26
  35. gisserver/parsers/fes20/lookups.py +144 -0
  36. gisserver/parsers/fes20/operators.py +331 -127
  37. gisserver/parsers/fes20/sorting.py +104 -33
  38. gisserver/parsers/gml/__init__.py +12 -11
  39. gisserver/parsers/gml/base.py +5 -2
  40. gisserver/parsers/gml/geometries.py +69 -35
  41. gisserver/parsers/ows/__init__.py +25 -0
  42. gisserver/parsers/ows/kvp.py +190 -0
  43. gisserver/parsers/ows/requests.py +158 -0
  44. gisserver/parsers/query.py +175 -0
  45. gisserver/parsers/values.py +26 -0
  46. gisserver/parsers/wfs20/__init__.py +37 -0
  47. gisserver/parsers/wfs20/adhoc.py +245 -0
  48. gisserver/parsers/wfs20/base.py +143 -0
  49. gisserver/parsers/wfs20/projection.py +103 -0
  50. gisserver/parsers/wfs20/requests.py +482 -0
  51. gisserver/parsers/wfs20/stored.py +192 -0
  52. gisserver/parsers/xml.py +249 -0
  53. gisserver/projection.py +357 -0
  54. gisserver/static/gisserver/index.css +12 -1
  55. gisserver/templates/gisserver/index.html +1 -1
  56. gisserver/templates/gisserver/service_description.html +2 -2
  57. gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml +9 -9
  58. gisserver/templates/gisserver/wfs/feature_field.html +2 -2
  59. gisserver/templatetags/gisserver_tags.py +20 -0
  60. gisserver/types.py +322 -259
  61. gisserver/views.py +198 -56
  62. django_gisserver-1.5.0.dist-info/RECORD +0 -54
  63. gisserver/parsers/base.py +0 -149
  64. gisserver/parsers/fes20/query.py +0 -285
  65. gisserver/parsers/tags.py +0 -102
  66. gisserver/queries/__init__.py +0 -37
  67. gisserver/queries/adhoc.py +0 -185
  68. gisserver/queries/base.py +0 -186
  69. gisserver/queries/projection.py +0 -240
  70. gisserver/queries/stored.py +0 -206
  71. gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml +0 -20
  72. gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml +0 -14
  73. {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/LICENSE +0 -0
  74. {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