django-gisserver 1.4.1__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 (73) hide show
  1. {django_gisserver-1.4.1.dist-info → django_gisserver-2.0.dist-info}/METADATA +23 -13
  2. django_gisserver-2.0.dist-info/RECORD +66 -0
  3. {django_gisserver-1.4.1.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 +63 -60
  8. gisserver/exceptions.py +47 -9
  9. gisserver/extensions/__init__.py +4 -0
  10. gisserver/{parsers/fes20 → extensions}/functions.py +11 -5
  11. gisserver/extensions/queries.py +261 -0
  12. gisserver/features.py +267 -240
  13. gisserver/geometries.py +34 -39
  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 +129 -305
  18. gisserver/operations/wfs20.py +428 -336
  19. gisserver/output/__init__.py +10 -48
  20. gisserver/output/base.py +198 -143
  21. gisserver/output/csv.py +81 -85
  22. gisserver/output/geojson.py +63 -72
  23. gisserver/output/gml32.py +310 -281
  24. gisserver/output/iters.py +207 -0
  25. gisserver/output/results.py +71 -30
  26. gisserver/output/stored.py +143 -0
  27. gisserver/output/utils.py +75 -154
  28. gisserver/output/xmlschema.py +86 -47
  29. gisserver/parsers/__init__.py +10 -10
  30. gisserver/parsers/ast.py +320 -0
  31. gisserver/parsers/fes20/__init__.py +15 -11
  32. gisserver/parsers/fes20/expressions.py +89 -50
  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 +336 -128
  37. gisserver/parsers/fes20/sorting.py +107 -34
  38. gisserver/parsers/gml/__init__.py +12 -11
  39. gisserver/parsers/gml/base.py +6 -3
  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 +11 -11
  58. gisserver/templates/gisserver/wfs/feature_field.html +2 -2
  59. gisserver/templatetags/gisserver_tags.py +20 -0
  60. gisserver/types.py +375 -258
  61. gisserver/views.py +206 -75
  62. django_gisserver-1.4.1.dist-info/RECORD +0 -53
  63. gisserver/parsers/base.py +0 -149
  64. gisserver/parsers/fes20/query.py +0 -275
  65. gisserver/parsers/tags.py +0 -102
  66. gisserver/queries/__init__.py +0 -34
  67. gisserver/queries/adhoc.py +0 -181
  68. gisserver/queries/base.py +0 -146
  69. gisserver/queries/stored.py +0 -205
  70. gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml +0 -20
  71. gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml +0 -14
  72. {django_gisserver-1.4.1.dist-info → django_gisserver-2.0.dist-info}/LICENSE +0 -0
  73. {django_gisserver-1.4.1.dist-info → django_gisserver-2.0.dist-info}/top_level.txt +0 -0
gisserver/queries/base.py DELETED
@@ -1,146 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from django.db.models import QuerySet
4
-
5
- from gisserver.exceptions import InvalidParameterValue
6
- from gisserver.features import FeatureType
7
- from gisserver.output import FeatureCollection, SimpleFeatureCollection
8
- from gisserver.parsers import fes20
9
- from gisserver.types import split_xml_name
10
-
11
-
12
- class QueryExpression:
13
- """WFS base class for all queries.
14
- This object type is defined in the WFS spec (as <fes:AbstractQueryExpression>).
15
-
16
- The subclasses can override the following logic:
17
-
18
- * :meth:`get_type_names` defines which types this query applies to.
19
- * :meth:`compile_query` defines how to filter the queryset.
20
-
21
- For full control, these methods can also be overwritten instead:
22
-
23
- * :meth:`get_queryset` defines the full results.
24
- * :meth:`get_hits` to return the collection for RESULTTYPE=hits.
25
- * :meth:`get_results` to return the collection for RESULTTYPE=results
26
- """
27
-
28
- handle = ""
29
- value_reference = None
30
-
31
- def bind(
32
- self,
33
- all_feature_types: dict[str, FeatureType],
34
- value_reference: fes20.ValueReference | None,
35
- ):
36
- """Bind the query to presentation-layer logic"""
37
- self.all_feature_types = all_feature_types
38
- self.value_reference = value_reference
39
-
40
- def check_permissions(self, request):
41
- """Verify whether the user has access to view these data sources"""
42
- for feature_type in self.get_type_names():
43
- feature_type.check_permissions(request)
44
-
45
- def resolve_type_name(self, type_name, locator="typename") -> FeatureType:
46
- """Find the feature type for a given name.
47
- This is an utility that cusstom subclasses can use.
48
- """
49
- # Strip the namespace prefix. The Python ElementTree parser does
50
- # not expose the used namespace prefixes, so text-values can't be
51
- # mapped against it. As we expose just one namespace, just strip it.
52
- xmlns, type_name = split_xml_name(type_name)
53
-
54
- try:
55
- return self.all_feature_types[type_name]
56
- except KeyError:
57
- raise InvalidParameterValue(
58
- locator, f"Typename '{type_name}' doesn't exist in this server."
59
- ) from None
60
-
61
- def get_hits(self) -> FeatureCollection:
62
- """Run the query, return the number of hits only.
63
-
64
- Override this method in case you need full control over the response data.
65
- Otherwise, override :meth:`compile_query` or :meth:`get_queryset`.
66
- """
67
- querysets = self.get_querysets()
68
- return FeatureCollection(
69
- results=[
70
- # Include empty feature collections,
71
- # so the selected feature types are still known.
72
- SimpleFeatureCollection(feature_type=ft, queryset=qs.none(), start=0, stop=0)
73
- for ft, qs in querysets
74
- ],
75
- number_matched=sum(qs.count() for ft, qs in querysets),
76
- )
77
-
78
- def get_results(self, start_index=0, count=100) -> FeatureCollection:
79
- """Run the query, return the full paginated results.
80
-
81
- Override this method in case you need full control over the response data.
82
- Otherwise, override :meth:`compile_query` or :meth:`get_queryset`.
83
- """
84
- stop = start_index + count
85
-
86
- # The querysets are not executed yet, until the output is reading them.
87
- querysets = self.get_querysets()
88
- return FeatureCollection(
89
- results=[
90
- SimpleFeatureCollection(feature_type, qs, start=start_index, stop=stop)
91
- for feature_type, qs in querysets
92
- ]
93
- )
94
-
95
- def get_querysets(self) -> list[tuple[FeatureType, QuerySet]]:
96
- """Construct the querysets that return the database results."""
97
- results = []
98
- for feature_type in self.get_type_names():
99
- queryset = self.get_queryset(feature_type)
100
- results.append((feature_type, queryset))
101
-
102
- return results
103
-
104
- def get_queryset(self, feature_type: FeatureType) -> QuerySet:
105
- """Generate the queryset for the specific feature type.
106
-
107
- This method can be overwritten in subclasses to define the returned data.
108
- However, consider overwriting :meth:`compile_query` instead of simple data.
109
- """
110
- queryset = feature_type.get_queryset()
111
-
112
- # Apply filters
113
- compiler = self.compile_query(feature_type, using=queryset.db)
114
-
115
- if self.value_reference is not None:
116
- if feature_type.resolve_element(self.value_reference.xpath) is None:
117
- raise InvalidParameterValue(
118
- "valueReference",
119
- f"Field '{self.value_reference.xpath}' does not exist.",
120
- )
121
-
122
- # For GetPropertyValue, adjust the query so only that value is requested.
123
- # This makes sure XPath attribute selectors are already handled by the
124
- # database query, instead of being a presentation-layer handling.
125
- field = compiler.add_value_reference(self.value_reference)
126
- queryset = compiler.filter_queryset(queryset, feature_type=feature_type)
127
- return queryset.values("pk", member=field)
128
- else:
129
- return compiler.filter_queryset(queryset, feature_type=feature_type)
130
-
131
- def get_type_names(self) -> list[FeatureType]:
132
- """Tell which type names this query applies to.
133
-
134
- This method needs to be defined in subclasses.
135
- """
136
- raise NotImplementedError(
137
- f"{self.__class__.__name__}.get_type_names() should be implemented."
138
- )
139
-
140
- def compile_query(self, feature_type: FeatureType, using=None) -> fes20.CompiledQuery:
141
- """Define the compiled query that filters the queryset.
142
-
143
- Subclasses need to define this method, unless
144
- :meth:`get_queryset` is completely overwritten.
145
- """
146
- raise NotImplementedError()
@@ -1,205 +0,0 @@
1
- """Handle (stored)query objects.
2
-
3
- These definitions follow the WFS spec.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from dataclasses import dataclass
9
-
10
- from django.db.models import Q, QuerySet
11
-
12
- from gisserver.exceptions import InvalidParameterValue, MissingParameterValue, NotFound
13
- from gisserver.features import FeatureType
14
- from gisserver.operations.base import Parameter
15
- from gisserver.output import FeatureCollection
16
- from gisserver.parsers import fes20
17
- from gisserver.types import XsdTypes
18
-
19
- from .base import QueryExpression
20
-
21
-
22
- class QueryExpressionText:
23
- """Define the body of a stored query.
24
-
25
- This object type is defined in the WFS spec.
26
- It may contain a wfs:Query or wfs:StoredQuery element.
27
- """
28
-
29
- return_feature_types: list[str] | None = None
30
- language: str = fes20.Filter.query_language
31
- is_private: bool = False
32
-
33
-
34
- @dataclass
35
- class StoredQueryDescription:
36
- """WFS metadata of a stored query.
37
- This object type is defined in the WFS spec.
38
- """
39
-
40
- id: str = None
41
- title: str | None = None
42
- abstract: str | None = None
43
- parameters: dict[str, XsdTypes] | None = None
44
- expressions: list = None # TODO: support multiple body expressions
45
-
46
-
47
- class StoredQuery(QueryExpression):
48
- """Base class for stored queries.
49
-
50
- This represents all predefined queries on the server.
51
- A good description can be found at:
52
- https://mapserver.org/ogc/wfs_server.html#stored-queries-wfs-2-0
53
-
54
- The implementation logic is fully defined by the :class:`QueryExpression`
55
- base class. For a simple implementation, the following data should be
56
- overwritten:
57
-
58
- * :meth:`get_type_names` to define which type this query references.
59
- * :meth:`compile_query` to define the queryset filter.
60
-
61
- For advanced overriding, see the :class:`QueryExpression` base class,
62
- or the :class:`GetFeatureById` implementation.
63
- """
64
-
65
- # Official WFS docs have an 'id' and 'parameters' property for the
66
- # StoredQuery class, but these are avoided here to give subclasses full
67
- # control over which properties to store. E.g. "id" conflicts with Django
68
- # model subclasses that stores the query.
69
- meta: StoredQueryDescription
70
-
71
- def __init__(self, **parameters):
72
- self.parameters = parameters
73
-
74
- @classmethod
75
- def extract_parameters(cls, KVP) -> dict[str, str]:
76
- """Extract the arguments from the key-value-pair (=HTTP GET) request."""
77
- args = {}
78
- for name, _xsd_type in cls.meta.parameters.items():
79
- try:
80
- args[name] = KVP[name]
81
- except KeyError:
82
- raise MissingParameterValue(
83
- name, f"Stored query {cls.meta.id} requires an '{name}' parameter"
84
- ) from None
85
-
86
- # Avoid unexpected behavior, check whether the client also sends adhoc query parameters
87
- for name in ("filter", "bbox", "resourceID"):
88
- if name not in args and KVP.get(name.upper()):
89
- raise InvalidParameterValue(
90
- name, "Stored query can't be combined with adhoc-query parameters"
91
- )
92
-
93
- return args
94
-
95
-
96
- class StoredQueryRegistry:
97
- """Registry of functions to be callable by <fes:Query>."""
98
-
99
- def __init__(self):
100
- self.stored_queries = {}
101
-
102
- def __bool__(self):
103
- return bool(self.stored_queries)
104
-
105
- def __iter__(self):
106
- return iter(self.stored_queries.values())
107
-
108
- def register(self, meta: StoredQueryDescription | None = None, **meta_kwargs):
109
- """Register a custom class that handles a stored query"""
110
-
111
- def _metadata_dec(query: type[StoredQuery]):
112
- query.meta = meta or StoredQueryDescription(**meta_kwargs)
113
- self.stored_queries[query.meta.id] = query
114
- return query
115
-
116
- return _metadata_dec
117
-
118
- def resolve_query(self, query_id) -> type[StoredQuery]:
119
- """Find the stored procedure using the ID."""
120
- try:
121
- return self.stored_queries[query_id]
122
- except KeyError:
123
- raise InvalidParameterValue(
124
- "STOREDQUERY_ID", f"Stored query does not exist: {query_id}"
125
- ) from None
126
-
127
-
128
- stored_query_registry = StoredQueryRegistry()
129
-
130
-
131
- class StoredQueryParameter(Parameter):
132
- """Special parameter parsing for the 'STOREDQUERY_ID' parameter"""
133
-
134
- def __init__(self):
135
- super().__init__(name="STOREDQUERY_ID", parser=stored_query_registry.resolve_query)
136
-
137
- def value_from_query(self, KVP: dict):
138
- """Customize the request parsing to read custom parameters too."""
139
- stored_query_class = super().value_from_query(KVP)
140
- if stored_query_class is None:
141
- return None
142
-
143
- parameters = stored_query_class.extract_parameters(KVP)
144
- return stored_query_class(**parameters)
145
-
146
-
147
- @stored_query_registry.register(
148
- id="urn:ogc:def:query:OGC-WFS::GetFeatureById",
149
- title="Get feature by identifier",
150
- abstract="Returns the single feature that corresponds with the ID argument",
151
- parameters={"ID": XsdTypes.string},
152
- # expressions=[QueryExpressionText],
153
- )
154
- class GetFeatureById(StoredQuery):
155
- """The stored query for GetFeatureById.
156
-
157
- This is essentially the same as:
158
-
159
- <wfs:Query xmlns:wfs='..." xmlns:fes='...'>
160
- <fes:Filter><fes:ResourceId rid='{ID}'/></fes:Filter>
161
- </wfs:Query>
162
-
163
- Except that the response is supposed to contain only the item itself.
164
- """
165
-
166
- def __init__(self, ID):
167
- super().__init__(ID=ID)
168
- try:
169
- type_name, id = ID.rsplit(".", 1)
170
- except ValueError:
171
- # Always report this as 404
172
- raise NotFound("ID", "Expected typeName.id for ID parameter") from None
173
-
174
- self.type_name = type_name
175
- self.id = id
176
-
177
- def get_type_names(self) -> list[FeatureType]:
178
- """Tell which type names this query applies to."""
179
- feature_type = self.all_feature_types[self.type_name]
180
- return [feature_type]
181
-
182
- def get_queryset(self, feature_type: FeatureType) -> QuerySet:
183
- """Override to implement ID type checking."""
184
- try:
185
- return super().get_queryset(feature_type)
186
- except (ValueError, TypeError) as e:
187
- raise InvalidParameterValue("ID", f"Invalid ID value: {e}") from e
188
-
189
- def get_results(self, *args, **kwargs) -> FeatureCollection:
190
- """Override to implement 404 checking."""
191
- collection = super().get_results(*args, **kwargs)
192
-
193
- # Directly attempt to collect the data.
194
- # Avoid having to do that in the output renderer.
195
- if collection.results[0].first() is None:
196
- # WFS 2.0.2: Return NotFound instead of InvalidParameterValue
197
- raise NotFound("ID", f"Feature not found with ID {self.id}.")
198
-
199
- return collection
200
-
201
- def compile_query(self, feature_type: FeatureType, using=None) -> fes20.CompiledQuery:
202
- """Create the internal query object that will be applied to the queryset."""
203
- compiler = fes20.CompiledQuery(feature_type=feature_type)
204
- compiler.add_lookups(Q(pk=self.id), type_name=self.type_name)
205
- return compiler
@@ -1,20 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <wfs:DescribeStoredQueriesResponse
3
- xmlns="http://www.opengis.net/wfs/2.0"
4
- xmlns:app="{{ app_xml_namespace }}"
5
- xmlns:wfs="http://www.opengis.net/wfs/2.0"
6
- xmlns:xs="http://www.w3.org/2001/XMLSchema"
7
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
8
- xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd">
9
-
10
- {% for query_meta in stored_queries %}
11
- <StoredQueryDescription id="{{ query_meta.id }}">
12
- <Title>{{ query_meta.title }}</Title>
13
- <Abstract>{{ query_meta.abstract }}</Abstract>
14
- {% for name, type in query_meta.parameters.items %}
15
- <Parameter name="{{ name }}" type="{{ type.with_prefix }}"/>{% endfor %}
16
- <QueryExpressionText isPrivate="true" language="urn:ogc:def:queryLanguage:OGC-WFS::WFS_QueryExpression" returnFeatureTypes="{% for feature in feature_types %}{% if not forloop.first %} {% endif %}{{ feature.name }}{% endfor %}"/>
17
- </StoredQueryDescription>
18
- {% endfor %}
19
-
20
- </wfs:DescribeStoredQueriesResponse>
@@ -1,14 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <wfs:ListStoredQueriesResponse
3
- xmlns="http://www.opengis.net/wfs/2.0"
4
- xmlns:app="{{ app_xml_namespace }}"
5
- xmlns:wfs="http://www.opengis.net/wfs/2.0"
6
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
7
- xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd">
8
-
9
- <StoredQuery id="urn:ogc:def:query:OGC-WFS::GetFeatureById">
10
- <Title>Get feature by identifier</Title>{% for feature_type in feature_types %}
11
- <ReturnFeatureType>{{ feature_type.name }}</ReturnFeatureType>{% endfor %}
12
- </StoredQuery>
13
-
14
- </wfs:ListStoredQueriesResponse>