generic UpdateView submit DisallowedRedirect

I am new to Django and am having trouble with the generic UpdateView.
Following the tutorial, this should be the simplest thing but submitting the form yields this error: DisallowedRedirect. I’m sure it is a very simple thing missing but reading the documentation over and over I can’t find it. Please help.

The generic ListView works fine, with a link to the correct URL for UpdateView and displaying the expected form. Data entered into the form does actually get saved correctly prior to the error. The offending target URL that generates the error looks just fine.

Here is the error generated by the server:

Environment:


Request Method: POST
Request URL: http://localhost:8000/glazeman/element/4

Django Version: 3.1
Python Version: 3.8.5
Installed Applications:
['glazes.apps.GlazesConfig',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\core\handlers\base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\views\generic\base.py", line 73, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\views\generic\base.py", line 101, in dispatch
    return handler(request, *args, **kwargs)
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\views\generic\edit.py", line 194, in post
    return super().post(request, *args, **kwargs)
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\views\generic\edit.py", line 142, in post
    return self.form_valid(form)
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\views\generic\edit.py", line 126, in form_valid
    return super().form_valid(form)
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\views\generic\edit.py", line 57, in form_valid
    return HttpResponseRedirect(self.get_success_url())
  File "C:\Users\Ken\AppData\Local\Programs\Python\Python38\lib\site-packages\django\http\response.py", line 468, in __init__
    raise DisallowedRedirect("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)

Exception Type: DisallowedRedirect at /glazeman/element/4
Exception Value: Unsafe redirect to URL with protocol 'glazes'

Here are the urls, models, views and template files:

urls.py

from django.urls import path

from . import views

app_name = 'glazes'
urlpatterns = [
    path('', views.index, name='index'),
    path('elements', views.ElementsView.as_view(), name='elements index'),
    path('element/<int:pk>', views.Element_detailView.as_view(), name='element detail'),
]

models.py

from django.db import models

class Element(models.Model):
    name = models.CharField(max_length=200, unique=True)
    toxic = models.BooleanField(default = False)
    quantity = models.DecimalField(max_digits=20, decimal_places=3, blank = True, null = True)         # inventory in grams
    price = models.DecimalField(max_digits=6, decimal_places=2, blank = True, null = True)             # stored as $/gram
    note = models.TextField(blank = True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['name']

views.py

from django.http import HttpResponse
from django.views import generic
from .models import Element


def index(request):
    breakpoint()
    return HttpResponse("Hello, world. You're at the index.")


class ElementsView(generic.ListView):
    model = Element
    template_name = 'glazes/elements.html'
    context_object_name = 'elements_list'
    
    def get_queryset(self):
        return Element.objects.order_by('name')

class Element_detailView(generic.UpdateView):
    model = Element
    fields = ['toxic', 'note', 'quantity']
    template_name = 'glazes/element_detail.html'
    success_url = 'glazes:element detail'

template\element_detail.html

<h1>{{ element }}</h1>
<br>
<form method = "post">
  {% csrf_token %}
  {{ form.as_p }}
  <br>
  <input type="submit" value="Save">
</form>
<br>
<a href="{% url 'glazes:element detail' element.id %}">Should do a get</a>
<br>
<a href="{% url 'glazes:elements index' %}">Back to Elements index</a>

templates/elements.html

{% if elements_list %}
    <ul>
    {% for element in elements_list %}
        <li><a href="{%url 'glazes:element detail' element.id %}">{{ element.name }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No elements are available.</p>
{% endif %}

Hi Ken,

Instead of

success_url = 'glazes:element detail'

Try

success_url = reverse_lazy('glazes:element detail)

reverse_lazy docs: https://docs.djangoproject.com/en/3.1/ref/urlresolvers/#reverse-lazy)

I’m not sure if the whitespace in the URL causes any problems, I just always use an _ to separate words. Mind you, it’s just a string so it’s probably fine.

To import reverse_lazy, use

from django.urls import reverse_lazy

Conor

Conor,

Thanks for the suggestion. I changed the view to:

success_url = reverse_lazy(‘glazes:element detail’)

and retried it. This time the failure message was:

NoReverseMatch at /glazeman/element/4

I would try changing the url’s name to not have any whitespace, just in case that has something to do with it. And to keep things simple, I would try dropping the app name from the lookup. I only say that because I have personally never used the app name in conjunction with the URL name, but that doesn’t mean it is not used nor useful.

    path('element/<int:pk>', views.Element_detailView.as_view(), name='element_detail'),

And then

success_url = reverse_lazy('element_detail')

On another note, if you append python to the three ` when writing code examples, you’ll get the nice colouring. Also works for other languages like HTML, bash etc.

I think you forgot to add the underscore. I believe it should be:
success_url = reverse_lazy('glazes:element_detail)

I sure did, Ken! Thanks for spotting the mistake. I’ve edited the answer.

Ok, so the issue here is a little more subtle than it first appears.

The root cause is that success_url itself is a class-level variable, but you’re trying to dynamically generate a different url for each invocation. (This is not the “common case” for the success url. The “expected” or “usual” pattern is that the success url will take you back to your list view.)

What you need to do in this situation is actually override the get_success_url method to have the view calculate the url at the time the view is being processed:

def get_success_url(self):
    return reverse_lazy('glazes:element detail', args=[self.object.pk])
1 Like

OK. I’ve tried that and now it is giving me an error of:

NoReverseMatch at /glazeman/element/11

Reverse for ‘element_detail’ with no arguments not found. 1 pattern(s) tried: [‘glazeman/element/(?P[0-9]+)$’]

So the generic form is not supplying a to the GET following the . This must have been tested thoroughly so it should just work. But it isn’t, so it must be something missing. I just can’t find it. The documentation doesn’t add anything to the template on the line.

Was this reply to Conor’s last reply or to mine?

If to mine, can you post your current view? (There have been a number of changes recommended and I’d like to be sure I’m looking at what you currently have generating this message.)

This is a reply to your earlier one. Now I see where to add the pk. Thank you. That makes total sense. I will try that immediately and let you know what happens.

The documentation for UpdateView reads like a GET is sent to the same URL as the POST after a successful submit.

That is true (GET to the same URL) iff the form is not valid. (Causes the form to be displayed, possibly with error messages if included in the form template.) On a valid form, it’s redirected to the success_url.

It worked perfectly! Thank you so much.

Conor,

Thanks for answering. I appreciate your help. About the code coloring, would it be like this:

‘’’python

Some code example
‘’’

I really do not have a good way to put in the code examples. Just cut and paste and then format with one of the buttons.

It’s three backtick characters - `, not the apostrophe - '. So it would be:
```

def function(self):
    return self

```
(FYI, I put the backslash - \ in front of the backticks to show the lines. Also, the usage of python on the line after the backticks is optional here - I tend to not include it.)
So yes, copy/paste your code, move the cursor before the code and insert the line of ``` and then insert the same line after your code.

You guys are great. Thank you for answering so quickly and accurately.