Django: redirect user to provide more information and on success redirect to previous URL

django redirect to previous page
django redirect url
django redirect with parameters
django redirect with context
django redirect to homepage
django redirect to another view
django redirect to external url
django redirect() with get parameters

I know that I can use decorators such as @login_required and @permission_required() or enclose a view in a function like login_required() to redirect a user to sort of provide more information (log in in that case). On success the user is redirected to the URL he was trying to access in the first place (automatically using the logic ?next=/ in the URL).

Now I would like to apply the ?next=/ logic to another case. My user is logged in and wants to claim a piece on the website. To successfully do this he must have provided his address. In the view I check if all the address fields are not empty. If one of the fields is empty I redirect the user to a default UpdateView (form). If the user filled in the fields (clicks the submit button) I would like to redirect him to the URL he came from (trying to claim the piece). Due to this redirect the process of checking if all address fields are not empty would start over and if it succeeds this time the piece is claimed.

How does the ?next=/ logic apply in such a case?

views.py

from django.views.generic import RedirectView
from django.http import QueryDict
class ClaimRedirectView(RedirectView):
    REQUIRED_FIELDS = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'state_province', 'location', 'country']

    permanent = False

    def get_redirect_url(self, *args, **kwargs):
        claimant = get_object_or_404(Creator, user=self.request.user)
        missing_fields = [fields.name for fields in claimant._meta.get_fields(include_hidden=False) if fields.name in self.REQUIRED_FIELDS and not getattr(claimant, fields.attname, None)]

        if not missing_fields:
            return reverse('my-claimed')

        messages.info(self.request, 'Please fill in your complete address to proceed')
        next = self.request.get_full_path()

        path = reverse('creator-update', kwargs={'slug': claimant.slug})
        #q = QueryDict('next=%s' % next)
        q = 'next=' + next

        return '%s?%s' % (path, q)

class CreatorUpdate(LoginRequiredMixin, UpdateView):
    model = Creator
    slug_field = 'slug'
    fields = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'location', 'state_province', 'country']

    # these two methods only give access to the users own profile but not the others
    def user_passes_test(self, request):
        if request.user.is_authenticated:
            self.object = self.get_object()
            return self.object.user == request.user
        return False

    def dispatch(self, request, *args, **kwargs):
        if not self.user_passes_test(request):
            return redirect_to_login(request.get_full_path())
        return super(CreatorUpdate, self).dispatch(request, *args, **kwargs)

urls.py

path('claim/<uuid:pk>', login_required(views.ClaimRedirectView.as_view()), name='claim')
path('creator/<slug:slug>/update/', views.CreatorUpdate.as_view(), name='creator-update')

creator_form.html

{% extends "base_generic.html" %}

{% block content %}

{% if messages %}
  {% for message in messages %}
  <p{% if message.tags %} class="{{ message.tags }}"{% endif %}><strong>{{ message }}</strong></p>
  {% endfor %}
{% endif %}

<form action="" method="post">
{% csrf_token %}
{{ form.as_ul }}
<input type="submit" value="Submit">
</form>

{% endblock %}

When implementing the above ClaimRedirectView as suggested by @ I get to the form to fill in more information and see the correct url (having the next logic). But when filling in the form I do not get directed to the next part of the url. May this have something to do with the form (generic UpdateView) itself?

Since the view redirects the request a better alternative will be to use a RedirectView and next isn't added to the url config hence the error.

It should be a querystring and the CreatorUpdate.get_absolute_url should be able to retrieve the param from the GET dict. i.e request.GET.get('next')

from django.http import QueryDict

class ClaimRedirectView(RedirectView):
    REQUIRED_FIELDS = ['first_name', 'last_name', ...]

    permanent = False

    def get_redirect_url(self, *args, **kwargs):
        claimant = get_object_or_404(Creator, user=self.request.user)
        missing_fields = [
            f.name for fields in claimant._meta.get_fields(include_hidden=False)
            if f.name in REQUIRED_FIELDS and not getattr(claimant, f.attname, None)
        ]

        if not missing_fields:
            return reverse('my-claimed')

        messages.info(request, 'Please fill in your complete address to proceed')
        next = request.get_full_path()

        path = reverse('creator-update', kwargs={'slug': claimant.slug}))
        q = QueryDict('next=%s' % next)

        return '%s?%s' % (path, q.urlencode())

And the urls.py with a configuration the <next:next> isn't a kwarg i.e not a named group.

path('creator/<slug:slug>/update/', views.CreatorUpdate.as_view(), name='creator-update')
path('claim/<uuid:pk>', login_required(views.claim), name='claim')

To use the next as the redirect path on CreatorUpdate view.

class CreatorUpdate(LoginRequiredMixin, UpdateView):
    model = Creator
    slug_field = 'slug' # <-- This is already the default
    fields = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'location', 'state_province', 'country']

    # This should be done using the `get_queryset`
    def get_queryset(self):
        qs = super().get_queryset()
        # if request.user.is_authenticated: # Using the LoginRequiredMixin mixin users will already be authenticated.
        return qs.filter(user=self.request.user)

    def form_valid(self, form):
        next = self.request.GET.get('next')
        if next:
            return redirect(next)
        return super().form_valid(form)

The Ultimate Guide to Django Redirects – Real Python, All the way from the low-level details of the HTTP protocol to the high-level way of In Django, you redirect the user to another URL by returning an instance of of your Django project, the URL /redirect/ now redirects to /redirect-success/ . URL. You can also create a permanent redirect by passing the keyword argument  Django relies on user input in some cases (e.g. django.contrib.auth.views.login() and i18n) to redirect the user to an “on success” URL. The security check for these redirects (namely django.utils.http.is_safe_url()) considered some URLs with basic authentication credentials “safe” when they shouldn’t be.

Just like in the native login_required, you save the path to the next GET parameter:

def claim(request, pk):
    claimant = Creator.objects.get(user=request.user)
    if claimant.first_name != None and claimant.last_name != None and claimant.street != None and claimant.street_number != None and claimant.zip_code != None and claimant.location != None and claimant.state_province != None::
        (...)
    else:
        next = request.get_full_path()
        return HttpResponseRedirect(reverse('creator-update', kwargs={'slug': claimant.slug, 'next': next}))

and then in the creator-update view you check if there is next and redirect to that after a successful update. You might store the URL in a hidden input field.

I suggest checking the Django internals regarding this, specifically django.contrib.auth.user_passes_test, django.contrib.auth.views.redirect_to_login and django.contrib.auth.views.LoginView. It's no magic at all, everything there is very straightforward. For LoginView you should be familiar with class-based views.

Django - Page Redirection, How do I redirect one page to another in Django? redirect_to_login(next, login_url=None, redirect_field_name='next')¶ Redirects to the login page, and then back to another URL after a successful login. Required arguments: next: The URL to redirect to after a successful login. Optional arguments: login_url: The URL of the login page to redirect to. Defaults to settings.LOGIN_URL if not supplied.

Putting together parts of my original code and suggestions from @jackotonye I got it working with the following code:

views.py

from django.contrib import messages
from django.views.generic import RedirectView
class ClaimRedirectView(RedirectView):
    REQUIRED_FIELDS = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'state_province', 'location', 'country']

    permanent = False

    def get_redirect_url(self, pk, *args, **kwargs):
        claimant = get_object_or_404(Creator, user=self.request.user)
        missing_fields = [fields.name for fields in claimant._meta.get_fields(include_hidden=False) if fields.name in self.REQUIRED_FIELDS and not getattr(claimant, fields.attname, None)]

        if not missing_fields:
            piece_instance = PieceInstance.objects.get(pk=pk)
            piece_instance.claimant = self.request.user
            piece_instance.date_claimed = datetime.date.today()
            piece_instance.status = 'c'
            piece_instance.save()
            return reverse('my-claimed')

        messages.info(self.request, 'Please fill in your complete address to proceed')
        next = self.request.get_full_path()

        path = reverse('creator-update', kwargs={'slug': claimant.slug})
        q = 'next=' + next

        return '%s?%s' % (path, q)

class CreatorUpdate(LoginRequiredMixin, UpdateView):
    model = Creator
    slug_field = 'slug'
    fields = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'location', 'state_province', 'country']

    # these two methods only give access to the users own profile but not the others
    def user_passes_test(self, request):
        if request.user.is_authenticated:
            self.object = self.get_object()
            return self.object.user == request.user
        return False

    def dispatch(self, request, *args, **kwargs):
        if not self.user_passes_test(request):
            return redirect_to_login(request.get_full_path())
        return super(CreatorUpdate, self).dispatch(request, *args, **kwargs)

    def get_success_url(self):
        if 'next' in str(self.request.META.get('HTTP_REFERER')):
            return self.request.GET.get('next', '/')
        return reverse_lazy('creator-detail', kwargs={'slug': self.object.slug})

urls.py

path('claim/<uuid:pk>', login_required(views.ClaimRedirectView.as_view()), name='claim')
path('creator/<slug:slug>/update/', views.CreatorUpdate.as_view(), name='creator-update')

The two crucial steps to success regarding the problem I had were to actually fill in and save the arguments in the ClaimRedirectView at if not missing_fields: and defining the get_success_url method in CreatorUpdate. The get_success_url manually checks if the string next is in the url and redirects accordingly.

Django shortcut functions | Django documentation, See the template loading documentation for more information on how templates are Returns an HttpResponseRedirect to the appropriate URL for the arguments passed. You can use the redirect() function in a number of ways. from django.shortcuts import redirect def my_view(request): obj = MyModel.​objects.get(. Django applications that make proper use of URL namespacing can be deployed more than once for a particular site. For example django.contrib.admin has an AdminSite class which allows you to easily deploy more than one instance of the admin .

Beginning Django: Web Application Development and Deployment with , The user will see no difference, but these are details that search engines take into account when ranking of your website. Also remember that 'name' parameter we  Now let's create a user: Navigate back to the home page of the admin site. Click the Add button next to Users to open the Add user dialogue. Enter an appropriate Username and Password/Password confirmation for your test user. Press SAVE to create the user.

Django Tips #1 redirect, Notice the /accounts/ signup/ url is set to be processed by the UserSignUp else​: # Log the user in login(self.request, user) # Redirect to success url return view to create model records or the concept of mixins, see the previous chapter,  Complete all previous tutorial topics, including Django Tutorial Part 8: User authentication and permissions. Objective: To understand how to write forms to get information from users and update the database. To understand how the generic class-based editing views can vastly simplify creating forms for working with a single model.

Mastering Django: Core, The function redirect will basically return an HttpResponseRedirect with the proper URL. I prefer to always use this shortcut so my codebase  You must add a SocialApp record per provider via the Django admin containing these app credentials. When creating the OAuth app on the side of the provider pay special attention to the callback URL (sometimes also referred to as redirect URL).

Comments
  • What you need to use is a RedirectView kite.com/python/docs/django.views.generic.base.RedirectView
  • this seems to almost get it. When I try to claim the piece without having provided all my address information it redirects me to the form to enter the information and the url points (with the next logic) to the correct next. However when clicking submit in the form I am not directed to the next part but the detail view of the user. May this have something to do with the form itself? I added some more information in the original question.
  • You'll need to update the CreatorUpdate view changing the form_valid to redirect using the next instead of the .get_absolute_url
  • I implemented these suggestions but seem not to get there. Also, it seems you are still using erroneous code, such as one of the urls (views.claim) and only request instead of self. request (which I edited in your answer). When implementing your suggestions, again I do see the correct part of the URL behind next but when filling in the form I am redirected to the form with the field I just filled in being empty. All seems fine, except for the actual redirect after successfully filling in the form.
  • The suggestions contains code that should be implemented alongside your implementation not supposed to be a verbatim implementation the urls are what you added above in the question no changes have been made only describing the expected implementation you should call save after saving the form and use the next path as the redirect url.
  • when including the next parameter to be given to the URL I get a NoReverseMatch error since the URL redirecting to the update view only takes one argument. I added some more code in the original question. Since that way I don't even reach the creator-update view, do you see a way around this?