Passing UUID from one view to another

Hi,

I have 5 views:

  • AccueilView and EntreeView;
  • AdherentView, AncienView and SympathisantView.

I want to create a user_identifier on AccueilView, that will be saved in the database (Consentement model). Then users reach the EntreeView, which allow them to chose in which category they fit. When they have chosen it, they’re sent to the corresponding view among the three other (AdherentView, AncienView and SympathisantView).

I want to create a UUID (user_identifier) at the first step, and pass it through the other views to one of the three views. So far, I’ve suceeded in creating and storing the user_identifer inside AccueilView, but I’m not sure how to proceed after that. I’ve written the following views but it stops working at EntreeView: whatever I chose I keep being sent back to accueil.

Any suggestions would be very welcome.

views.py:

class AccueilView(FormView):
    model = Consentement
    form_class = AccueilForm
    template_name = 'form1/accueil.html'
    success_url = '/entree/'

    def form_valid(self, form):
        qcon01_consent = form.cleaned_data['qcon01_consent']
        user_identifier = str(uuid.uuid4())
        response = HttpResponse()
        response.set_cookie('user_identifier', user_identifier)
        consent = Consentement(user_identifier=user_identifier, qcon01_consent=qcon01_consent)
        consent.save()
        return super().form_valid(form)


class EntreeView(FormView):
    model = Categorie
    form_class = EntreeForm
    template_name = 'form1/entree.html'
    success_url = 'form1'

    def form_valid(self, form):
        answer = form.cleaned_data['categorie']
        user_identifier = self.request.COOKIES.get('user_identifier', None)
        if user_identifier is None:
            return redirect('accueil')
        self.request.session[user_identifier] = answer
        if answer == 'sympathisant':
            return redirect('sympathisant')
        elif answer == 'ancien':
            return redirect('ancien')
        else:
            return redirect('adherent')


def adherent_view(request):

    if request.method == 'POST':
        form = AdherentForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('/conclusion/')
    else:
        form = AdherentForm()

    context = {'form': form}
    return render(request, 'form1/adherent.html', context)


def ancien_view(request):

    if request.method == 'POST':
        form = AncienForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('/conclusion/')
    else:
        form = AncienForm()

    context = {'form': form}
    return render(request, 'form1/ancien.html', context)


def sympathisant_view(request):

    if request.method == 'POST':
        form = SympathisantForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('/conclusion/')
    else:
        form = SympathisantForm()

    context = {'form':form}
    return render(request, 'form1/sympathisant.html',context)


def conclusion_view(request):

    if request.method == 'POST':
        form = ConclusionForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('/merci/')
    else:
        form = ConclusionForm()

    context = {'form': form}
    return render(request, 'form1/conclusion.html', context)


class MerciView(TemplateView):
    template_name = 'form1/merci.html'

There are a couple ways of doing this.

  • You could pass the uuid as a url parameter from view to view. It’s actually the cleanest, but can’t reliably handle any sort of “interruption” in the flow.

  • You could store the uuid in the user’s session. That allows for more flexibility when moving from url to url, but that information only lasts as long as the session lasts.

  • You could store the uuid in the database. This is only really practical with authenticated users, but at least allows for using this sequence of events across sessions.

Your decision is, in part, going to depend upon whether someone using this is going to complete it in one sitting, whether the users are authenticated, and whether they’re required to follow this through directly or if they can stop mid-way and come back to it later.

1 Like

This is probably the way I’d like to do it. It’s related to this other thread where we discussed the possibility for the respondent to come back to the survey and finish to answer it later:

Using this UUID field could be a solution to this too - or even better, instead of a UUID, as you mentioned in the other thread, I could harvest a string of character like a “name” from the respondent and store it in the database, to use it the same way that I would use the UUID: a unique identifier that could be retrieved or entered by the respondent to repopulate the form, even on another session.

Let’s stick on the UUID for this thread. How should I proceed to pass the value of the UUID from one view to another? Or to put it another way, how to make sure that even if the view changes, the user is still filling the same object data in the database?

I know you’re not intending to just repeat the original question, so I guess I’m not following you here. You can pass the UUID using any of the three methods I outlined above - using the URL, session, or the database.

Sorry, I had to go back and re-read the other thread. I had forgotten much of what we had talked about.

If you’re using the database with some not-user-type information, then for each page, the user would need to enter their “unique identifier” to retrieve the information for that page. I’m sure that’s not what you want.

I’d suggest you have a “home” page, where someone would enter in their name. That page can then retrieve the uuid, and check the database to see what stage they’re at when filling out the form.

You would use that information to construct a url to be used as the “success url” for the view handling the form.

You would include the uuid as a url parameter for each view - with code checking for the presence of the uuid as a parameter. If it’s not supplied, you may want to return them to the home page. If it is supplied, but it’s not an active form, you might want to display a page telling them they need to start over - or whatever is appropriate. That flow logic would be up to you.

1 Like

I’m getting back to this issue as I didn’t succeed to resolve it so far. Let’s see if reformulating publicly what I’m trying to accomplish will help me to better grasp it :slight_smile:

What I’m trying to do

The website is an online survey containing several pages.

  • Accueil is the landing page. It contains two different elements:

    • A GDPR / privacy disclaimer, which the user needs to agree on to access the rest of the survey.

    • A area dedicated to enter the form according to two different modalities:

      • A user entering the survey for the first time will be asked to chose a personal id (a string of characters) before clicking on “yes” to approve the disclaimer. It will then lead to the Entree page, while clicking on “no” to refuse the privacy terms will lead to the Dommage page;

      • Right below it, there is an area dedicated to retrieve a previously and partially answered survey, in order for a returning user to finish its submission. The user is asked to enter the personal id chosen during his previous session. if an existing id is provided, the user is redirected to the corresponding survey page (either Adherent, Ancien or Sympathisant, according to his profile, chosen at the Entree page), and the survey is pre-filled with the data submitted during his previous session (retrieved from the database).

  • The Entree page contains a simple form for the user to chose a profile among three choices (Adherent, Ancien or Sympathisant). Once the profile is selected, the user clicks on the Enter button and is redirected to the corresponding survey page. If no profile is selected, an error is raised.

  • Adherent, Ancien and Sympathisant pages are similar. They’re the backbone of the survey and contain a lot of questions to be answered by the users. There are two buttons at the end of the page:

    • Envoyer is an input button for definitive submission, when the survey is supposed to be complete. If the form is valid, the user is redirected to the Conclusion page.

    • Enregistrer is a button that saves the current state of submission if the user wants to interrupt his submission for the moment and continue later. Then, when he’ll come back, he’ll just have to enter the personal id defined at the Accueil and will be redirected on this page, pre-filled with data from the database.

    (NB: Not sure if this button is the proper solution. The idea is to implement a process to save the data in the database while bypassing the possible validation errors due to an incomplete form).

Once the form is sent by clicking on Envoyer, the survey is considered to be finished. The corresponding personal id won’t allow the user to access its submitted data anymore.

  • The Conclusion page is a complentary group of a few questions, stored in another database, and intentionnaly not linked to the previous page (Adherent, Ancien and Sympathisant) to preserve anonymity. At the end of the page, the form is submitted by clicking on a button labeled Envoyer. Clicking on it redirects to the Merci page.

  • The Merci page is the last page of the survey, where a short “Thank you” message is displayed. No more action is required from the user.

  • Dommage is a page that contains a short message and displays a link sending back to the Accueil page, in case the user landed here by mistake.

What works and what doesn’t so far

All pages are ready and individually functioning, except for:

  1. Passing data from one page to another;
  2. Redirecting the user on the proper page from the Entree page;
  3. The system allowing the user to retrieve data and answer the survey in several sessions.

1. Passing data from one page to another

All data submitted on the Accueil page (basically a boolean field representing the user agreement) and the Adherent, Ancien or Sympathisant pages need to be stored in the same table in the database, and a user’s submission needs to be on the same line in this table, no matter the page on which the data is submitted. It’s not an issue for the three principal pages (a single user is concerned by one and only one of these pages), but I didn’t succeed yet at feeding the same line in the same table while changing pages from Accueil to Adherent, Ancien or Sympathisant (Not sure if i’m being explicit enough here!). I know there’s some sort of cookie management involved, but what I’ve tried so far doesn’t work: no cookie is created, hence no cookie is passed from one page to the other.

2. Redirecting from Entree to Adherent/Ancien/Sympathisant

Edit: this point is solved. I leave the code for reference (and maybe it’ll be useful to someone else!).

models.py

class CategorieChoice(models.Model):
    choix = models.CharField(
        max_length=100,
        unique=True
        )
        
class Repondant(models.Model):
    categorie = models.ForeignKey(
        'CategorieChoice',
        on_delete=models.CASCADE,
        related_name="general_categorie",
        null=True
        )

urls.py

urlpatterns = [    
    path('', views.AccueilView.as_view(), name='accueil'),
    path('entree/', views.EntreeView.as_view(), name='entree'),
    path('adherent/', views.adherent_view, name='adherent'),
    path('ancien/', views.ancien_view, name='ancien'),
    path('sympathisant/', views.sympathisant_view, name='sympathisant'),
    path('conclusion/', views.conclusion_view, name='conclusion'),
    path('merci/', views.MerciView.as_view(), name='merci'),
    path('dommage/', views.DommageView.as_view(), name='dommage'),
    ]

views.py

class EntreeView(FormView):
    model = Repondant
    form_class = EntreeForm
    template_name = 'form1/entree.html'
    success_url = '/adherent/'

    def form_valid(self, form):
        categorie_choice = form.cleaned_data['categorie']
        answer = categorie_choice.choix if categorie_choice else None
        
        if answer and 'sympathisant' in answer:
            return redirect('sympathisant')
        elif answer and 'ex-adherent' in answer:
            return redirect('ancien')
        else:
            return redirect('adherent')

entree.html

<div name="categorie">
{{ form.categorie.errors }}
{{ form.categorie.label_tag }}
{{ form.categorie }}
</div>

3. Answering the survey in different sessions

I didn’t start to try and implement this yet. I figure that I’d better solve my previous issue before.


I think that’s good enough for now or I’ll end up writing a book about my little survey website! Solving the issue #2 would a good start (Edit: done!). Then I’d like to tackle the first issue, which this thread is about. Any advice are very welcome, thank you for reading this message so far!

I notice you create the response but don’t return it in your form_valid method so I wonder if the cookie is not being set on the client because of this. I am not sure but perhaps if you returned this response instead of the super().form_valid(form) that you do currently.

1 Like

Passing data from one page to another.

Assume you’re storing this data in one or more models, with a primary key of personal_id in every model.

Then, your urls would be something like:

    path('adherent/<str:pk>/', views.adherent_view, name='adherent'),
    path('ancien/<str:pk>/', views.ancien_view, name='ancien'),
    path('sympathisant/<str:pk>/', views.sympathisant_view, name='sympathisant'),

The individual views can then get their data using that pk arg to the view.

Your redirects then become: redirect('sympathisant', pk=pk), where the pk here is the corresponding arg in the view.

Or, if you’re rendering a page with a url tag, you might render something like {% url 'sympathisant' pk %}, when you’ve passed that pk arg to the render in the context.

1 Like

There’s progress! I don’t seem to be able to edit my reference post so here’s a new post.

1. Passing data from one page to another

I’ve solved it by simply using sessions. Here’s how:

views.py

class EntreeView(FormView):
    model = CategorieChoice
    form_class = EntreeForm
    template_name = 'form1/entree.html'

    def form_valid(self, form):
        categorie = form.cleaned_data['categorie']
        self.request.session['categorie'] = categorie
        if categorie and 'sympathisantE' in categorie:
            return redirect('sympathisant')
        elif categorie and 'ex-adhérentE' in categorie:
            return redirect('ancien')
        else:
            return redirect('adherent')

def adherent_view(request):
    if request.method == 'POST':
        form = AdherentForm(request.POST)
            if form.is_valid():
                identifiant = request.session.get('categorie')
                form.instance.categorie = categorie
                form.save()
                return redirect('/conclusion/')
    else:
        form = AdherentForm()

    context = {'form': form}
    return render(request, 'form1/adherent.html', context)

3. Answering the survey in different sessions

Not sure this one will be solved in 2023, but hey, trying as hard as I can :slight_smile:
Here’s what I came up with:

Two buttons are proposed to the user:

template.html:

<input type="submit" name="envoyer" value="Envoyer" >
<input type="submit" name="enregistrer" value="Enregistrer">

And I’m trying to set the behaviour of the page depending on which button is clicked.

  • If “Envoyer” is clicked, the validity of the form is checked, and if it is valid, submitted data is recorded in the database and the user is redirected to the conclusion page.
  • If “Enregistrer” is clicked, submitted data is recorded in the database - even if not all questions are answered - and the user is redirected to the same page.

views.py:

def adherent_view(request):
    if request.method == 'POST':
        form = AdherentForm(request.POST)
        if 'envoyer' in request.POST:
            if form.is_valid():
                categorie = request.session.get('categorie')
                form.instance.categorie = categorie
                form.save()
                return redirect('/conclusion/')
        elif 'enregistrer' in request.POST:
            categorie = request.session.get('categorie')
            form.instance.categorie = categorie
            form.save()
            return redirect('adherent')
    else:
        form = AdherentForm()

    context = {'form': form}
    return render(request, 'form1/adherent.html', context)

However this doesn’t work as I’m redirected to the same page, displaying error messages for the unanswered questions. Is there something wrong, or something missing, in this code?

Happy New Year to everyone :slight_smile:

When a model form is saved, it validates the form if it hasn’t already been validated. (See the save method and Form.errors)

You’ve got a couple ways to work around this:

  • Create two forms - one with the fields being required as necessary and one without. This second form would also be saving the form to a model that allows all fields to be null. Perform the test for which button was pressed before binding the post data to the form, and bind the appropriate form after checking the button.

  • Alter the __init__ method of the form to allow a parameter to be passed to identify whether or not fields are to be required. Change your binding call to pass that parameter accordingly.

  • Create two separate views that the different buttons would submit to and handle the responses appropriately. (See <input type="submit"> - HTML: HyperText Markup Language | MDN)

I’m sure there are other options - these are just some of the ideas that come to mind.

1 Like

Thanks for your input!

Following your first suggestion, I tried this:

def adherent_view(request):
    if request.method == 'POST':
        if 'envoyer' in request.POST:
            form = AdherentForm(request.POST)
            if form.is_valid():
                categorie = request.session.get('categorie')
                identifiant = request.session.get('identifiant')
                form.instance.categorie = categorie
                form.instance.identifiant = identifiant
                form.save()
                return redirect('/conclusion/')
        elif 'enregistrer' in request.POST:
            form = AdherentOptForm(request.POST)
            categorie = request.session.get('categorie')
            identifiant = request.session.get('identifiant')
            form.instance.categorie = categorie
            form.instance.identifiant = identifiant
            form.save()
            return redirect('adherent')
    else:
        form = AdherentForm()

    context = {'form': form}
    return render(request, 'form1/adherent.html', context)

AdherentOptForm is a new ModelForm linked to the new AdherentOptionnel model. both are pretty much clones from AdherentForm and Adherent model, except that I made sure every field was optional both in the model and in the form.

However validation errors still show up when I click on Envoyer. Did I miss something?

You know I’m going to be asking you to provide more details here… like the form and the error being reported… :wink:

1 Like

Sure and sorry about that :man_facepalming:

Here’s forms.py. I only left a few fields to illustrate as they contain a lot of fields.

class AdherentForm(forms.ModelForm):
    qdem01 = forms.ModelChoiceField(
        queryset=Genre.objects.order_by('id'),
        label=('Quel est votre genre ? *'),
        empty_label='Indiquez votre genre',
        required=False,
        )
    qdem05 = forms.ModelMultipleChoiceField(
        queryset=Statut.objects.order_by('id'),
        widget=forms.CheckboxSelectMultiple,
        label=('Quel est votre statut actuel ? (Plusieurs réponses possibles) *'),
        required=False,
        )

    class Meta:
        model = Repondant
        fields = [
            'categorie',
            'qdem01',
            'qdem01rx',
            'qdem02',
            'qdem02b',
            'qdem03',
            'qdem04',
            'qdem05'
            ]
        widgets = {
            'qdem02b': forms.RadioSelect,
            'qdem03': forms.RadioSelect,
            'qdem04': forms.RadioSelect,
            'qdem06': forms.RadioSelect
            }

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('label_suffix', '')
        super(AdherentForm, self).__init__(*args, **kwargs)


class AdherentOptForm(forms.ModelForm):
    qdem01_genre = forms.ModelChoiceField(
        queryset=Genre.objects.order_by('id'),
        label=('Quel est votre genre ? *'),
        empty_label='Indiquez votre genre',
        required=False,
        )
    qdem05_statut = forms.ModelMultipleChoiceField(
        queryset=Statut.objects.order_by('id'),
        widget=forms.CheckboxSelectMultiple,
        label=('Quel est votre statut actuel ? (Plusieurs réponses possibles) *'),
        required=False,
        )

    class Meta:
        model = RepondantOptionnel
        fields = [
            'categorie',
            'qdem01',
            'qdem01rx',
            'qdem02',
            'qdem02b',
            'qdem03',
            'qdem04',
            'qdem05'
            ]
        widgets = {
            'qdem02b': forms.RadioSelect,
            'qdem03': forms.RadioSelect,
            'qdem04': forms.RadioSelect,
            'qdem06': forms.RadioSelect
            }

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('label_suffix', '')
        super(AdherentOptForm, self).__init__(*args, **kwargs)

I tested the behaviour by answering only qdem01 (“Quel est votre genre”), and voluntarily left every other field unanswered. Then I clicked on “Enregistrer” and the following error popped-up for what would be the next mandatory field on my original field, qdem02:

Screenshot_20240104_230444

Then if I answer to qdem02, the same error pops-up on qdem02b, then on qdem03, etc. until all “originally mandatory” fields are provided with answers.

That error appears to be a browser-validation message and not a Django validation message.

You’re using this form on the GET, when the page is first retrieved.

This means that the form you are sending out to the browser when the page is requested is the form with all the required fields. Since you have browser-validation happening, it’s not being submitted back to Django for its validation.

I think you want to send AdherentOptForm for the GET.

Of course! Thanks for pointing it out. It works as expected now.

Retrieving the form

Going forward, I’ve written the reprendre_view view that should allow the user to retrieve the bound form with previously entered data (to keep answering the survey until all questions are answered, before they click on “Send” instead of “Save” when it’s complete).

I’m using the identifiant UUID generated at an earlier step, and expect the following behaviour:

  • Users are invited to provide the unique identifiant they’ve been provided earlier;
  • When clicking on “Send”, the view checks if an object with the same identifiant exists in the database;
  • If the test is positive, the corresponding bound form is displayed and users can keep filling it up (there are three potential corresponding views, users are redirected to the appropriate one depending on the value of the categorie field);
  • If the test is negative, users are redirected to the accueil view.

Here’s my code:

views.py

def reprendre_view(request):
    if request.method == 'POST':
        form = AdherentOptForm(request.POST)
        if not form.is_valid():
            print(form.errors)
        if form.is_valid():
            identifiant = form.cleaned_data['identifiant']
            repondant = get_object_or_404(RepondantOptionnel, identifiant=identifiant)
            categorie = repondant.categorie
            if categorie and 'UnE sympathisantE' in categorie:
                return redirect('sympathisant')
            elif categorie and 'UnE ex-adhérentE' in categorie:
                return redirect('ancien')
            elif categorie and 'UnE adhérentE' in categorie:
                return redirect('adherent')
            else:
                return redirect('accueil')

    else:
        form = ReprendreForm()

    return render(request, 'form1/reprendre.html', {'form': form})

forms.py

class ReprendreForm(forms.ModelForm):

    identifiant = forms.UUIDField(
        label=('Saisissez votre identifiant'),
        required=True
        )

    class Meta:
        model = RepondantOptionnel
        fields = ['identifiant', 'categorie']

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('label_suffix', '')
        super(ReprendreForm, self).__init__(*args, **kwargs)

reprendre.html

  <div id="form_all" class="container">
    
    <div name="identifiant" class="questionWrapper">
      {{ form.identifiant.as_field_group }}
    </div>
    
    <input type="submit" value="Reprendre la saisie" class="btn btn-success">

  </div>

When clicking on “Reprendre la saisie”, I got the following error: “An Repondant optionnel object with the identifiant field already exists”. I tried to modify the view with

    else:
        form = AdherentOptForm()

but with no success. Any idea what I’m doing wrong here?

I’m sorry, I don’t think I’m understanding the intended flow of events here.

Is this the view we’re looking at here?

If so, and if we’re talking about the step where the person enters the UUID, then I would create a form just for that entry. (A single form with one field to accept a UUID.)

Then, when that form has been received, you verify if the RepondantOptionnel with that UUID exists. If it doesn’t, you create it with whatever defaults are necessary. After that’s done and you have the UUID, you specify the instance when you’re creating the form.

(Or am I missing something else here?)

Yes, exactly.

Oh, right, at the moment it’s a ModelForm that refers to the RepondantOptionnel model, that doesn’t really make sense.

Something like that could make it:

class ReprendreForm(forms.Form):

    identifiant = forms.UUIDField(
        label=('Saisissez votre identifiant'),
        required=True
        )
  • If the UUID exists, I’d like to retrieve the AdherentOptForm form and the corresponding view, pre-filled with the available data corresponding to the object. With the updates to the code following your last comment, I’m able now to reach the targeted view depending on the identifiant and the categorie (adherent, ancien or sympathisant). However the form doesn’t appear as bound, and I don’t know how I can do this?

  • If the UUID doesn’t exist, there should be an error raised: “this UUID doesn’t exist, please try again”. The user can’t create UUID at this step cause the UUID has been automatically created on a previous step not described here, and this reprendre_view is just here to allow the user to submit his UUID.

I’m doing my best to be clear and simple, apologies if I’m not! :slight_smile:

So, in the most general sense, I think you’re looking at something like this:

Side note: This is illustrative and not complete or syntactically correct. It’s intended to give you an idea of a direction and not be a complete ready-to-run example.

def uuid_view(request):
  if request.method == 'POST':
    uuid_form = ReprendreForm(request.POST)
    if  uuid_form.is_valid():
      repondant = RepondantOptionnel.objects.filter(identifiant=uuid_form.identifiant).exists()
      if repondant:
        return redirect('reprende_view', args=uuid_form.identifiant)
...
def reprende_view(request, uuid):
  repondant  = RepondantOptionnel.objects.filter(identifiant=uuid_form.identifiant).exists()
  form = AdherentOptForm(request.POST, instance=repondant)
  if request.method == 'POST':
    ...
1 Like

I ended up with the following views:

def reprendre_view(request):
    if request.method == 'POST':
        form = ReprendreForm(request.POST)
        if form.is_valid():
            identifiant = str(form.cleaned_data['identifiant'])
            request.session['identifiant'] = identifiant
            repondant = get_object_or_404(RepondantOptionnel, identifiant=identifiant)
            categorie = repondant.categorie
            if categorie and 'UnE sympathisantE' in categorie:
                return redirect('sympathisant')
            elif categorie and 'UnE ex-adhérentE' in categorie:
                return redirect('ancien')
            elif categorie and 'UnE adhérentE' in categorie:
                return redirect('adherent')
            else:
                return redirect('accueil')
    else:
        form = ReprendreForm()

    return render(request, 'form1/reprendre.html', {'form': form})

and

def adherent_view(request,):
    categorie = request.session.get('categorie')
    identifiant = request.session.get('identifiant')

    repondant = None
    if identifiant:
        try:
            repondant = RepondantOptionnel.objects.get(identifiant=identifiant)
        except RepondantOptionnel.DoesNotExist:
            pass

    if request.method == 'POST':
        if 'envoyer' in request.POST:
            form = AdherentForm(request.POST, instance=repondant)
            if form.is_valid():
                form.instance.categorie = categorie
                form.instance.identifiant = identifiant
                form.save()
                return redirect('conclusion')
        elif 'enregistrer' in request.POST:
            form = AdherentOptForm(request.POST, instance=repondant)
            form.instance.categorie = categorie
            form.instance.identifiant = identifiant
            form.save()
            return redirect('adherent')
    else:
        form = AdherentOptForm(instance=repondant)

    context = {'form': form}
    return render(request, 'form1/adherent.html', context)