如何避免Django URLField添加尾部斜杠?

时间:2013-04-23 03:11:47

标签: django django-forms

Django URLField喜欢在用户输入的末尾添加一个尾部斜杠(/),强制所有的URL都与额外的字符一起存储,这是错误的。如何停止此行为并保存用户提交的URL?

3 个答案:

答案 0 :(得分:3)

https://github.com/django/django/blob/master/django/forms/fields.py检查to_python的{​​{1}}。

您可以看到它几乎在方法URLField的末尾有一行url_fields[2] = '/'。它在url的末尾附加一个尾部斜杠to_python。您可以在此行之前看到将此作为注释的逻辑。

如果给出一些查询参数,则必须使用此斜杠。

如果您想避免此行为,请编写自己的字段,该字段从/延伸并覆盖自定义类中的URLField

答案 1 :(得分:2)

我也一直在努力解决这个问题,因为它会导致某些网址出现问题。例如,http://www.nasa.gov/mission_pages/kepler/news/kepler-62-kepler-69.html/失败,但它没有斜杠。

为了扩展akshar的答案,我们解释了执行此操作的方法here。例如,在我的models.py文件中定义它并在我的模型中设置url = NoSlashURLField()而不是models.URLField()会删除斜杠:

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

答案 2 :(得分:0)

对于那些使用通常的Django管理表单为他们的站点,并使用South进行数据库迁移的人,您可能希望使用以下方法而不是stonefury。他的方法改变了模型字段,除非你添加一些特殊代码,否则会混淆南方。以下方法仅更改了 admin 代码,使South无法保持幸福。

在您的应用中的某个位置定义此类:

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

然后编辑您的admin.py文件,如下所示:

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,
        }
    }