Cartucho
Cartucho

Reputation: 3329

How to avoid Django URLField adding the trailing slash?

Django URLField likes to add a trailing slash (/) at the end of the user input, forcing all URLs to be stored with the extra character, this is wrong. How can I stop this behavior and save URLs as submitted by users?

Upvotes: 1

Views: 1432

Answers (3)

coredumperror
coredumperror

Reputation: 9130

For those using the usual Django admin forms for their site, and also using South for DB migrations, you may want to use the following method instead of stonefury's. His method changes the model field, which confuses South unless you add some special code. The below method changes only the admin code, allowing South to remain blissfully unaware.

Define this class somewhere in your app:

class NoSlashURLFormField(forms.URLField):

    def to_python(self, value):
        def split_url(url):
            """
            Returns a list of url parts via ``urlparse.urlsplit`` (or raises a
            ``ValidationError`` exception for certain).
            """
            try:
                return list(urlsplit(url))
            except ValueError:
                # urlparse.urlsplit can raise a ValueError with some
                # misformatted URLs.
                raise ValidationError(self.error_messages['invalid'])

        if value:
            url_fields = split_url(value)
            if not url_fields[0]:
                # If no URL scheme given, assume http://
                url_fields[0] = 'http'
            if not url_fields[1]:
                # Assume that if no domain is provided, that the path segment
                # contains the domain.
                url_fields[1] = url_fields[2]
                url_fields[2] = ''
                # Rebuild the url_fields list, since the domain segment may now
                # contain the path too.
                url_fields = split_url(urlunsplit(url_fields))
            value = urlunsplit(url_fields)
        return value

Then edit your admin.py file as follows:

from your_app.path.to.noslash import NoSlashURLFormField 
from django.contrib.admin.widgets import AdminURLFieldWidget

class MyModelAdmin(admin.ModelAdmin):

    ...

    formfield_overrides = {
        models.URLField: {
            'form_class': NoSlashURLFormField,
            # Need to specify the AdminURLFieldWidget here because it would
            # otherwise get defaulted back to URLInput. 
            'widget': AdminURLFieldWidget,
        }
    }

Upvotes: 0

Adam Stone
Adam Stone

Reputation: 2006

I've been struggling with this as well, because it's causing a problem for certain urls. For example, http://www.nasa.gov/mission_pages/kepler/news/kepler-62-kepler-69.html/ fails, but it works without the slash.

To expand on akshar's answer, the method to do this is explained here. For example, defining this in my models.py file and setting url = NoSlashURLField() rather than models.URLField() in my model removes the slash:

try:
    from urllib.parse import urlsplit, urlunsplit
except ImportError:     # Python 2
    from urlparse import urlsplit, urlunsplit

class NoSlashURLField(models.URLField):
    description = "Remove the goddamn slash"
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        super(NoSlashURLField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        def split_url(url):
            """
            Returns a list of url parts via ``urlparse.urlsplit`` (or raises a
            ``ValidationError`` exception for certain).
            """
            try:
                return list(urlsplit(url))
            except ValueError:
                # urlparse.urlsplit can raise a ValueError with some
                # misformatted URLs.
                raise ValidationError(self.error_messages['invalid'])

        value = super(NoSlashURLField, self).to_python(value)
        if value:
            url_fields = split_url(value)
            if not url_fields[0]:
                # If no URL scheme given, assume http://
                url_fields[0] = 'http'
            if not url_fields[1]:
                # Assume that if no domain is provided, that the path segment
                # contains the domain.
                url_fields[1] = url_fields[2]
                url_fields[2] = ''
                # Rebuild the url_fields list, since the domain segment may now
                # contain the path too.
                url_fields = split_url(urlunsplit(url_fields))
#            if not url_fields[2]:
#                # the path portion may need to be added before query params
#                url_fields[2] = '/'
            value = urlunsplit(url_fields)
        return value

Upvotes: 2

Akshar Raaj
Akshar Raaj

Reputation: 15221

Check to_python of URLField at https://github.com/django/django/blob/master/django/forms/fields.py.

You can see it has a line url_fields[2] = '/' almost at the end of method to_python. It appends a trailing slash / at the end of url. You can see the logic for doing this as a comment before this line.

This slash is necessary in case some query params are given.

If you want to avoid this behaviour, write you own field which extends from URLField and override to_python in your custom class.

Upvotes: 3

Related Questions