'
+ )
+ assertContains(response, connection_check_inner_html, html=True)
diff --git a/tests/test_validation.py b/tests/test_validation.py
index 23f4fd6..55021dc 100644
--- a/tests/test_validation.py
+++ b/tests/test_validation.py
@@ -2,37 +2,32 @@
import pytest
-from zgw_consumers.models.validators import NonUrlValidator, PrefixValidator
-
-
-def test_start_with_validator_starts_with_false():
- validator = PrefixValidator(prefix="/", starts_with=False)
-
- assert validator.__call__("no_leading_slash") is None
-
- with pytest.raises(ValidationError) as exc_context:
- validator.__call__("/with_leading_slash")
-
- assert "The given value cannot start with '/'" in exc_context.value
-
-
-def test_start_with_validator_starts_with_true():
- validator = PrefixValidator(prefix="/")
-
- with pytest.raises(ValidationError) as exc_context:
- validator.__call__("no_leading_slash")
-
- assert "The given value must start with '/'" in exc_context.value
-
- assert validator.__call__("/with_leading_slash") is None
+from zgw_consumers.models.validators import NonUrlValidator, validate_leading_slashes
def test_is_not_url_validator():
validator = NonUrlValidator()
- assert validator.__call__("some random text") is None
-
- with pytest.raises(ValidationError) as exc_context:
- assert validator.__call__("http://www.example.com")
-
- assert "Value cannot be a URL" in exc_context.value
+ try:
+ validator("some random text")
+ except ValidationError:
+ pytest.fail("Expected validation to pass")
+
+ with pytest.raises(
+ ValidationError,
+ match="Value cannot be a URL.",
+ ):
+ validator("http://www.example.com")
+
+
+def test_validate_leading_slashes():
+ try:
+ validate_leading_slashes("foo")
+ except ValidationError:
+ pytest.fail("Expected validation to pass")
+
+ with pytest.raises(
+ ValidationError,
+ match="The value must be a relative path.",
+ ):
+ validate_leading_slashes("/foo")
diff --git a/zgw_consumers/admin.py b/zgw_consumers/admin.py
index 4331d09..3a1f28e 100644
--- a/zgw_consumers/admin.py
+++ b/zgw_consumers/admin.py
@@ -21,6 +21,9 @@ class ServiceAdmin(admin.ModelAdmin):
@admin.display(description=_("Connection check status code"))
def get_connection_check(self, obj):
+ if obj.pk is None:
+ return "n/a"
+
return obj.connection_check
def get_fields(self, request: HttpRequest, obj: models.Model | None = None):
diff --git a/zgw_consumers/migrations/0021_service_api_connection_check_path.py b/zgw_consumers/migrations/0021_service_api_connection_check_path.py
index c17c4bf..bc5617e 100644
--- a/zgw_consumers/migrations/0021_service_api_connection_check_path.py
+++ b/zgw_consumers/migrations/0021_service_api_connection_check_path.py
@@ -1,5 +1,6 @@
# Generated by Django 4.2 on 2024-05-21 08:15
+import django.core.validators
from django.db import migrations, models
import zgw_consumers.models.validators
@@ -17,11 +18,13 @@ class Migration(migrations.Migration):
name="api_connection_check_path",
field=models.CharField(
blank=True,
- help_text="An optional API endpoint which will be used to check if the API is configured correctly and is currently up or down. This field is only used for the admin's 'Connection check status code' field.",
+ help_text="A relative URL to perform a connection test. If left blank, the API root itself is used. This connection check is only performed in the admin when viewing the service configuration.",
max_length=255,
validators=[
- zgw_consumers.models.validators.PrefixValidator(
- prefix="/", starts_with=False
+ django.core.validators.RegexValidator(
+ code="invalid",
+ message="The value must be a relative path.",
+ regex="^[^/#][^\\s]*",
),
zgw_consumers.models.validators.NonUrlValidator(),
],
diff --git a/zgw_consumers/models/services.py b/zgw_consumers/models/services.py
index 12040b3..2849d3a 100644
--- a/zgw_consumers/models/services.py
+++ b/zgw_consumers/models/services.py
@@ -21,7 +21,7 @@
from ..constants import APITypes, AuthTypes, NLXDirectories
from .abstract import RestAPIService
-from .validators import NonUrlValidator, PrefixValidator
+from .validators import NonUrlValidator, validate_leading_slashes
logger = logging.getLogger(__name__)
@@ -36,12 +36,13 @@ class Service(RestAPIService):
api_connection_check_path = models.CharField(
_("connection check endpoint"),
help_text=_(
- "An optional API endpoint which will be used to check if the API is configured correctly and "
- "is currently up or down. This field is only used for the admin's 'Connection check status code' field."
+ "A relative URL to perform a connection test. If left blank, the API root itself is used. "
+ "This connection check is only performed in the admin when viewing the service "
+ "configuration."
),
max_length=255,
validators=[
- PrefixValidator(prefix="/", starts_with=False),
+ validate_leading_slashes,
NonUrlValidator(),
],
blank=True,
@@ -132,7 +133,7 @@ def clean(self):
)
@property
- def connection_check(self) -> bool:
+ def connection_check(self) -> int | None:
from zgw_consumers.client import build_client
try:
diff --git a/zgw_consumers/models/validators.py b/zgw_consumers/models/validators.py
index b9724f2..5b344d5 100644
--- a/zgw_consumers/models/validators.py
+++ b/zgw_consumers/models/validators.py
@@ -1,51 +1,14 @@
from django.core.exceptions import ValidationError
-from django.core.validators import URLValidator
-from django.utils.deconstruct import deconstructible
+from django.core.validators import RegexValidator, URLValidator
from django.utils.translation import gettext_lazy as _
+validate_leading_slashes = RegexValidator(
+ regex=r"^[^/#][^\s]*",
+ message="The value must be a relative path.",
+ code="invalid",
+)
-@deconstructible
-class PrefixValidator:
- code = "invalid"
- def __init__(
- self,
- prefix: str,
- message: str = None,
- code: str = None,
- starts_with: bool = True,
- ):
- self.prefix = prefix
- self.starts_with = starts_with
-
- if code is not None:
- self.code = code
-
- if message is not None:
- self.message = message
- else:
- self.message = _(
- "The given value {must_or_cannot} start with '{prefix}'"
- ).format(
- must_or_cannot="must" if self.starts_with else "cannot",
- prefix=self.prefix,
- )
-
- def __call__(self, value: str) -> bool:
- if value.startswith(self.prefix) != self.starts_with:
- raise ValidationError(self.message, code=self.code, params={"value": value})
-
- def __eq__(self, other):
- return (
- isinstance(other, self.__class__)
- and self.prefix == other.prefix
- and (self.message == other.message)
- and (self.code == other.code)
- and (self.starts_with == other.starts_with)
- )
-
-
-@deconstructible
class NonUrlValidator(URLValidator):
message = _("Value cannot be a URL")