2

When used with flatpages reverse() and get_abolute_url() returns different outputs:

>>> about = FlatPage.objects.get(id=2)
>>> 
>>> about
<FlatPage: /about-us/ -- About us page>
>>>
>>> about.url
>>> '/about-us/'
>>>
>>> about.get_absolute_url()
'/about-us/'
>>>
>>>
>>> reverse('django.contrib.flatpages.views.flatpage', args=[about.url])
'/%2Fabout-us/'    ## from where %2F comes from ?
>>>

Here is the sitewide urls.py:

from django.conf.urls import url, include
from django.contrib import admin
from django.contrib .flatpages import urls as flatpage_urls
# from . import blog

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include(flatpage_urls)),
]

Although, I am able to access to about page at http://127.0.0.1:8000/about-us/. From where does %2F come from ?

I was expecting both method should return same output. What's going on here ?

Update:

Here is flatpages/urls.py

from django.conf.urls import url
from django.contrib.flatpages import views

urlpatterns = [
    url(r'^(?P<url>.*)$', views.flatpage, name='django.contrib.flatpages.views.flatpage'),
]

Update 2:

Updated urls.py to:

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    # url(r'', include(flatpage_urls)),
]

urlpatterns += [
    url(r'^(?P<url>.*/)$', views.flatpage),
]
Cody
  • 2,480
  • 5
  • 31
  • 62

2 Answers2

0

It looks to me like you are trying to insert a URL inside another URL, which just seems weird.

From your code:

reverse('django.contrib.flatpages.views.flatpage', args=[about.url])

You have also clarified about.url contains /about-us/. This string will get quoted and inserted in the URL:

http://hostname.example/<here>/

Or:

http://hostname.example/%2Fabout-us%2F/

I don't understand why you are not seeing the last %2F however.

Penguin Brian
  • 1,991
  • 14
  • 25
  • If I change `url(r'', include(flatpage_urls))` to `url(r'^page', include(flatpage_urls))`, then get_absolute_url() returns `"/about-us/"` where as `reverse()` returns `"/page/about"`. Does that look right to you ? If that seems perfectly fine to you enlighten me. How would you go about this ? – Cody Apr 24 '17 at 05:55
  • It probably will help if you show us what the value of ``about.url`` is. Instead of us guessing. – Penguin Brian Apr 24 '17 at 06:00
  • about.url returns `/about-us/` – Cody Apr 24 '17 at 06:04
  • I don't understand why you don't always get a %2F at the start and end. However, if you want reverse to work properly every time, you need to pass the value, not the URL. ``reverse('django.contrib.flatpages.views.flatpage', args=['about-us'])`` – Penguin Brian Apr 24 '17 at 06:12
  • I can't say I really understand this flatpage stuff however. – Penguin Brian Apr 24 '17 at 06:14
  • Don't you think using `args=['about-us']` defeats the DRY ? – Cody Apr 24 '17 at 06:15
  • It may be that the use of the word ``url`` has been overloaded to mean different things. The ``url`` parameter within the URL is not a complete URL, that is what you are trying to generate. Sorry, don't know enough about flatpage to know the correct way of doing this. – Penguin Brian Apr 24 '17 at 06:19
  • It could be a bug in flatpage that it assumes all URLs are / – Penguin Brian Apr 24 '17 at 06:20
  • May be thats the case – Cody Apr 24 '17 at 06:23
0

django.contrib.flatpages is apparently incompatibile with reverse() with args or kwargs. The intended use is with its custom tag:

{% load flatpages %}
{% get_flatpages as flatpages %}
<ul>
    {% for page in flatpages %}
        <li><a href="{{ page.url }}">{{ page.title }}</a></li>
    {% endfor %}
</ul>

Source Flatpages > Getting a list of FlatPage objects in your templates

Why the %2f

FlatPage.get_absolute_url() merely returns whatever is in the self.url field, which in your example is a string enclosed by slashes, i.e. '/about-us/'.

def get_absolute_url(self):
    # Handle script prefix manually because we bypass reverse()
    return iri_to_uri(get_script_prefix().rstrip('/') + self.url)

On the other hand, reverse() calls _reverse_with_prefix(), which prefixes a slash / to the URL, resulting in //about-us/. Then, it falsely determines the double-slash to mean an attempted schema rewrite, and so it replaces the second slash with the URL ASCII code %2f to neutralize it.

Unfortunately, the form validator for FlatPage.url requires that offending leading /:

def clean_url(self):
    url = self.cleaned_data['url']
    if not url.startswith('/'):
        raise forms.ValidationError(
            ugettext("URL is missing a leading slash."),
            ...

You can sort of work around it by using a prefix without a slash, like:

url(r'^pages', include(django.contrib.flatpages.urls))

but this would also match pagesabout-us/. If you remove the prefix altogether like with r'^', _reverse_with_prefix() will prefix a / in an attempt to avoid relative linking.

You can hard-code the URLs like the 3rd example from https://docs.djangoproject.com/en/1.11/ref/contrib/flatpages/#using-the-urlconf, but this defeats the purpose of managing URLs in the flatpages table.

from django.contrib.flatpages import views

urlpatterns += [
    url(r'^about-us/$', views.flatpage, {'url': '/about-us/'}, name='about'),
    url(r'^license/$', views.flatpage, {'url': '/license/'}, name='license'),
]
nonzero
  • 135
  • 1
  • 1
  • 5