Repository: dov-vlaanderen/pydov
Issue: Exception when downloading grondwater filters: put without filter

<!-- You can ask questions about the DOV webservices or about the `pydov` package. If you have a question about the `pydov` Python package, please use following template. -->

* PyDOV version: master
* Python version: 3.6
* Operating System: Windows

### Description

The WFS service of the GrondwaterFilter type (gw_meetnetten:meetnetten) contains not only records for Filters, but also contains records for Putten without Filters. When one of these is in your search area and an XML document is to be downloaded, an exception is raised because the pkey_filter (filterfiche) of the record is empty.

### What I Did

```
fs = GrondwaterFilterSearch()

df_filters = fs.search(
    location=WithinDistance(Point(114284, 188712), 500, 'm'))

Traceback:
  File "C:\\Projecten\\PyDov\\pydov_git\\pydov\\types\\abstract.py", line 513, in to_df_array
    result = item.get_df_array(return_fields)
  File "C:\\Projecten\\PyDov\\pydov_git\\pydov\\types\\abstract.py", line 583, in get_df_array
    self._parse_xml_data()
  File "C:\\Projecten\\PyDov\\pydov_git\\pydov\\types\\abstract.py", line 268, in _parse_xml_data
    xml = self._get_xml_data()
  File "C:\\Projecten\\PyDov\\pydov_git\\pydov\\types\\abstract.py", line 536, in _get_xml_data
    xml = get_dov_xml(self.pkey + '.xml')
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
```


Changed files:
tests/abstract.py, pydov/search/grondwaterfilter.py, pydov/types/abstract.py

diff --git a/pydov/search/grondwaterfilter.py b/pydov/search/grondwaterfilter.py
index 5d95e53..67bdf1c 100644
--- a/pydov/search/grondwaterfilter.py
+++ b/pydov/search/grondwaterfilter.py
@@ -2,6 +2,11 @@
 """Module containing the search classes to retrieve DOV borehole data."""
 import pandas as pd
 
+from owslib.fes import (
+    Not,
+    PropertyIsNull,
+    And,
+)
 from .abstract import AbstractSearch
 from ..types.grondwaterfilter import GrondwaterFilter
 from ..util import owsutil
@@ -77,6 +82,9 @@ class GrondwaterFilterSearch(AbstractSearch):
         `location` and/or `query`. When `return_fields` is None,
         all fields are returned.
 
+        Excludes 'empty' filters (i.e. Putten without Filters) by extending
+        the `query` with a not-null check on pkey_filter.
+
         Parameters
         ----------
         location : pydov.util.location.AbstractLocationFilter or \\
@@ -123,6 +131,16 @@ class GrondwaterFilterSearch(AbstractSearch):
             tuple or set.
 
         """
+        self._pre_search_validation(location, query, return_fields)
+
+        exclude_empty_filters = Not([PropertyIsNull(
+                                     propertyname='pkey_filter')])
+
+        if query is not None:
+            query = And([query, exclude_empty_filters])
+        else:
+            query = exclude_empty_filters
+
         fts = self._search(location=location, query=query,
                            return_fields=return_fields)
 
diff --git a/pydov/types/abstract.py b/pydov/types/abstract.py
index 3284aaf..1fbbd27 100644
--- a/pydov/types/abstract.py
+++ b/pydov/types/abstract.py
@@ -239,6 +239,12 @@ class AbstractDovType(AbstractTypeCommon):
             `https://www.dov.vlaanderen.be/data/typename/id`.
 
         """
+        if typename is None or pkey is None:
+            raise ValueError(
+                "Failed to instantiate object of class %s with typename '%s' "
+                "and permkey '%s'. Typename and pkey must not be None." % (
+                    self.__class__.__name__, typename, pkey))
+
         self.typename = typename
         self.pkey = pkey
 
diff --git a/tests/abstract.py b/tests/abstract.py
index d7100f3..e496ce5 100644
--- a/tests/abstract.py
+++ b/tests/abstract.py
@@ -977,11 +977,22 @@ class AbstractTestTypes(object):
 
         """
         tree = etree.fromstring(wfs_getfeature.encode('utf8'))
-        feature_members = tree.findall('.//{http://www.opengis.net/gml}'
-                                       'featureMembers')
+        feature_members = tree.find('.//{http://www.opengis.net/gml}'
+                                    'featureMembers')
 
-        features = self.get_type().from_wfs(feature_members,
-                                            self.get_namespace())
+        if feature_members is not None:
+            fts = [ft for ft in feature_members]
 
-        for feature in features:
-            assert type(feature) is self.get_type()
+            features = self.get_type().from_wfs(fts, self.get_namespace())
+
+            for feature in features:
+                assert type(feature) is self.get_type()
+
+    def test_missing_pkey(self):
+        """Test initialising an object type with a pkey of 'None'.
+
+        Test whether a ValueError is raised.
+
+        """
+        with pytest.raises(ValueError):
+            instance = self.get_type()(None)