Esempio n. 1
0
    def get_queryset(self, feature_type: FeatureType) -> QuerySet:
        """Generate the queryset for the specific feature type.

        This method can be overwritten in subclasses to define the returned data.
        However, consider overwriting :meth:`compile_query` instead of simple data.
        """
        queryset = feature_type.get_queryset()

        # Apply filters
        compiler = self.compile_query(feature_type, using=queryset.db)

        if self.value_reference is not None:
            if feature_type.resolve_element(
                    self.value_reference.xpath) is None:
                raise InvalidParameterValue(
                    "valueReference",
                    f"Field '{self.value_reference.xpath}' does not exist.",
                )

            # For GetPropertyValue, adjust the query so only that value is requested.
            # This makes sure XPath attribute selectors are already handled by the
            # database query, instead of being a presentation-layer handling.
            field = compiler.add_value_reference(self.value_reference)
            queryset = compiler.filter_queryset(queryset,
                                                feature_type=feature_type)
            return queryset.values("pk", member=field)
        else:
            return compiler.filter_queryset(queryset,
                                            feature_type=feature_type)
Esempio n. 2
0
class PlacesWFSView(WFSView):
    """An simple view that uses the WFSView against our test model."""

    xml_namespace = "http://example.org/gisserver"
    service_description = ServiceDescription(
        # While not tested directly, this is still validated against the XSD
        title="Places",
        abstract="Unittesting",
        keywords=["django-gisserver"],
        provider_name="Django",
        provider_site="https://www.example.com/",
        contact_person="django-gisserver",
    )
    feature_types = [
        FeatureType(
            Restaurant.objects.all(),
            fields="__all__",
            keywords=["unittest"],
            other_crs=[RD_NEW],
            metadata_url="/feature/restaurants/",
        ),
        FeatureType(
            Restaurant.objects.all(),
            name="mini-restaurant",
            keywords=["unittest", "limited-fields"],
            other_crs=[RD_NEW],
            metadata_url="/feature/restaurants-limit/",
        ),
        DeniedFeatureType(Restaurant.objects.none(), name="denied-feature"),
    ]
Esempio n. 3
0
    def render_feature(self, feature_type: FeatureType,
                       instance: models.Model) -> bytes:
        """Render the output of a single feature"""

        # Get all instance attributes:
        properties = self.get_properties(feature_type.xsd_type, instance)

        if feature_type.show_name_field:
            name = feature_type.get_display_value(instance)
            geometry_name = b'"geometry_name":%b,' % orjson.dumps(name)
        else:
            geometry_name = b""

        return (b"    {"
                b'"type":"Feature",'
                b'"id":%b,'
                b"%b"
                b'"geometry":%b,'
                b'"properties":%b'
                b"}") % (
                    orjson.dumps(f"{feature_type.name}.{instance.pk}"),
                    geometry_name,
                    self.render_geometry(feature_type, instance),
                    orjson.dumps(properties, default=_json_default),
                )
Esempio n. 4
0
    def render_xml_field(self,
                         feature_type: FeatureType,
                         xsd_element: XsdElement,
                         value,
                         extra_xmlns="") -> str:
        """Write the value of a single field."""
        if xsd_element.is_many:
            if value is None:
                # No tag for optional element (see PropertyIsNull), otherwise xsi:nil node.
                if xsd_element.min_occurs == 0:
                    return ""
                else:
                    return f'      <{xsd_element.xml_name} xsi:nil="true"{extra_xmlns} />\n'
            else:
                # Render the tag multiple times
                if xsd_element.type.is_complex_type:
                    # If the retrieved QuerySet was not filtered yet, do so now. This can't
                    # be done in get_value() because the FeatureType is not known there.
                    value = feature_type.filter_related_queryset(value)

                return "".join(
                    self._render_xml_field(feature_type,
                                           xsd_element,
                                           value=item,
                                           extra_xmlns=extra_xmlns)
                    for item in value)
        else:
            return self._render_xml_field(feature_type,
                                          xsd_element,
                                          value,
                                          extra_xmlns=extra_xmlns)
Esempio n. 5
0
    def get_prefetch_queryset(
        cls,
        feature_type: FeatureType,
        feature_relation: FeatureRelation,
        output_crs: CRS,
    ) -> models.QuerySet | None:
        """Generate a custom queryset that's used to prefetch a relation."""
        # Multiple elements could be referencing the same model, just take first that is filled in.
        if feature_relation.related_model is None:
            return None

        return feature_type.get_related_queryset(feature_relation)
Esempio n. 6
0
 def decorate_queryset(cls, feature_type: FeatureType, queryset, output_crs,
                       **params):
     """Update the queryset to let the database render the GML output."""
     value_reference = params["valueReference"]
     match = feature_type.resolve_element(value_reference.xpath)
     if match.child.is_geometry:
         # Add 'gml_member' to point to the pre-rendered GML version.
         return queryset.values(
             "pk",
             gml_member=AsGML(get_db_geometry_target(match, output_crs)))
     else:
         return queryset
Esempio n. 7
0
 def decorate_queryset(self, feature_type: FeatureType, queryset,
                       output_crs, **params):
     """Update the queryset to let the database render the GML output.
     This is far more efficient then GeoDjango's logic, which performs a
     C-API call for every single coordinate of a geometry.
     """
     queryset = super().decorate_queryset(feature_type, queryset,
                                          output_crs, **params)
     # If desired, the entire FeatureCollection could be rendered
     # in PostgreSQL as well: https://postgis.net/docs/ST_AsGeoJSON.html
     match = feature_type.resolve_element(feature_type.geometry_field_name)
     return queryset.defer(feature_type.geometry_field_name).annotate(
         _as_db_geojson=AsGeoJSON(get_db_geometry_target(match, output_crs),
                                  precision=16))
Esempio n. 8
0
class FlattenedWFSView(PlacesWFSView):
    """An advanced view that has a custom type definition for a foreign key."""

    feature_types = [
        FeatureType(
            Restaurant.objects.all(),
            fields=[
                "id",
                "name",
                field("city-id", model_attribute="city_id"),
                field("city-name", model_attribute="city.name"),
                "location",
                "rating",
                "is_open",
                "created",
            ],
        ),
    ]
Esempio n. 9
0
class ComplexTypesWFSView(PlacesWFSView):
    """An advanced view that has a custom type definition for a foreign key."""

    feature_types = [
        FeatureType(
            Restaurant.objects.all(),
            fields=[
                "id",
                "name",
                field("city", fields=["id", "name"]),
                "location",
                "rating",
                "is_open",
                "created",
            ],
            other_crs=[RD_NEW],
        ),
    ]
Esempio n. 10
0
    def get_properties(
        self,
        feature_type: FeatureType,
        xsd_type: XsdComplexType,
        instance: models.Model,
    ) -> dict:
        """Collect the data for the 'properties' field.

        This is based on the original XSD definition,
        so the rendering is consistent with other output formats.
        """
        props = {}
        for xsd_element in xsd_type.elements:
            if not xsd_element.is_geometry:
                value = xsd_element.get_value(instance)
                if xsd_element.type.is_complex_type:
                    # Nested object data
                    if value is None:
                        props[xsd_element.name] = None
                    else:
                        value_xsd_type = cast(XsdComplexType, xsd_element.type)
                        if xsd_element.is_many:
                            # If the retrieved QuerySet was not filtered yet, do so now.
                            # This can't be done in get_value() because the FeatureType
                            # is not known there.
                            value = feature_type.filter_related_queryset(value)

                            # "..._to_many relation; reverse FK, M2M or array field.
                            props[xsd_element.name] = [
                                self.get_properties(feature_type,
                                                    value_xsd_type, item)
                                for item in value
                            ]
                        else:
                            props[xsd_element.name] = self.get_properties(
                                feature_type, value_xsd_type, value)
                else:
                    # Scalar value, or list (for ArrayField).
                    props[xsd_element.name] = self._format_geojson_value(value)

        return props
Esempio n. 11
0
class ComplexTypesWFSView(PlacesWFSView):
    """An advanced view that has a custom type definition for a foreign key and M2M relation."""

    feature_types = [
        FeatureType(
            Restaurant.objects.all(),
            fields=[
                "id",
                "name",
                field("city", fields=["id", "name"]),
                "location",
                "rating",
                "is_open",
                "created",
                field("opening_hours",
                      fields=["weekday", "start_time", "end_time"]),
                "tags",  # array field
            ],
            other_crs=[RD_NEW],
        ),
    ]