3

In my Django REST Framework project, I have a model class for saving services that the Django app will crawl in a background task:

class Service(models.Model):
    name = models.CharField(max_length=50)
    description = models.CharField(max_length=250)
    root_url =URLField(unique=True)

Earlier on, I had both these services and the Django application running on my local machine. Now I containerized both the services and the Django application and have them running on Docker.

So now I have a problem adding a dockerized service, because of its root URL: http://sensor-service:8080/. The error that is thrown is: 'Enter a valid URL.'.

The problem occurs both when making a POST to my Django REST API, and when adding the Service via the GUI that the Django REST Framework provides.

So, based on https://stackoverflow.com/a/49262127/5433896 and http://www.django-rest-framework.org/api-guide/serializers/#field-level-validation I tried the following. Note: the most important line is line 5: allowing a hostname not followed by a domain and/or TLD.

models.py:

class DockerServiceDNSUrlsCompatibleURLValidator(URLValidator):
    def __init__(self, schemes=None, **kwargs):
        self.host_re = '(' + self.hostname_re + self.domain_re + self.tld_re \
                        + '|localhost|' \
                        + self.hostname_re + ')'  # <=== added: "hostname not followed by a domain and/or TLD is acceptable"
        self.regex = _lazy_re_compile(r'^(?:[a-z0-9\.\-\+]*)://'  # scheme is validated separately
                                      r'(?:\S+(?::\S*)?@)?'  # user:pass authentication
                                      r'(?:' + self.ipv4_re + '|' + self.ipv6_re + '|' + self.host_re + ')'
                                      r'(?::\d{2,5})?'  # port
                                      r'(?:[/?#][^\s]*)?'  # resource path
                                      r'\Z',
                                      re.IGNORECASE)
        super().__init__(schemes, **kwargs)


class DjangoServiceDNSUrlsCompatibleFormURLField(forms.fields.URLField):
    default_validators = [DockerServiceDNSUrlsCompatibleURLValidator()]


class DjangoServiceDNSUrlsCompatibleURLField(models.URLField):
    default_validators = [DockerServiceDNSUrlsCompatibleURLValidator()]

    def formfield(self, **kwargs):
        return super(DjangoServiceDNSUrlsCompatibleURLField, self).formfield(**{
            'form_class': DjangoServiceDNSUrlsCompatibleFormURLField,
        })


class Service(models.Model):
    name = models.CharField(max_length=50)
    description = models.CharField(max_length=250)
    root_url = DjangoServiceDNSUrlsCompatibleURLField(unique=True)

serializers.py:

class ServiceSerializer(serializers.HyperlinkedModelSerializer):
    validators = [DockerServiceDNSUrlsCompatibleURLValidator()]

    def validate_root_url(self, value):
        DockerServiceDNSUrlsCompatibleURLValidator(value)  # will throw validation exception if value isn't OK.
        return value

However, the error 'Enter a valid URL.' is still thrown. Does anyone see a problem in my approach? Or does anyone - worst case, but still acceptable - know how I can turn off the URLValidation entirely on this field instead?

Sander Vanden Hautte
  • 2,138
  • 3
  • 22
  • 36

1 Answers1

7

If you have your own regex, why not just use a models.CharField instead of URLField? Like:

models.py

phone_regex = RegexValidator(
    regex=r'^\+?(?:\(?[0-9]\)?\x20?){4,14}[0-9]$',
    message="Error....")

class FooModel(models.Model):
    phone = models.CharField("Phone Number", validators=[
                         phone_regex], max_length=26, blank=True, null=True)

BTW, to customize URLValidator accept urls without 'http/https' I use below

models.py

class OptionalSchemeURLValidator(URLValidator):
    def __call__(self, value):
        if '://' not in value:
            # Validate as if it were http://
            value = 'http://' + value
        super(OptionalSchemeURLValidator, self).__call__(value)

class FooModel(models.Model):
        website = models.CharField(max_length=200, validators=[OptionalSchemeURLValidator()])

This doesn't change the uploaded value, just make the validation pass. I tried on my DRF, it works.

refer to Alasdair's answer, thanks Alasdair

C.K.
  • 4,348
  • 29
  • 43