Loop through multiplechoicefield

Hi I am new to Django and have a question about a form I have built.

The form I have created consists of one multiplechoicefield, with the choices being a dictionary of vacations.

interests = forms.MultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple, choices=vacations.items)

The dictionary has a string as a key (states), and a list of strings as values (cities in the state). Ideally, I am envisioning looping through the keys displaying one at a time, and if the user clicks on the key, then show the values for the key and let the user click on many options out of the values for that key.

Example: Loop through states as buttons, and if the user clicks on the state, display the cities as buttons and allow the user to select multiple cities in the state and proceed to next state. Proceed to next state in the “vacation” dictionary.

All help is really appreciated! Thank you!

Sounds like this is going to involve some JavaScript. Are you currently using a JavaScript framework such as jQuery, React, Angular, or Vue?

If not, how comfortable are you with JavaScript and AJAX?

Keep in mind that once a page has been sent from the server to the browser, the server doesn’t have any involvement any more. The server can send out the list of states to the browser, but there’s going to need to be some code running in the browser that will cycle through those states and taking action on a button press.

Hi- thank you for the reply.

I know how to use javascript (I actually created a dummy version of what I want just with HTML and javascript but I don’t know how to link that to django databases). AJAX I’m not as familiar with. And no, I am not currently using a Javascript framework. How could I use those in django to acomplish what I want?

Thank you again.

They’re separate functions. You would write your JavaScript that performs all the UX functions. Then, when you need data from the server, you issue an AJAX call to the server to send or receive data as necessary. Your Django code then defines the URLs being used by AJAX as being assigned to views that accept or generate data to be returned to the JavaScript call.

Hi, thank you for the responses. With your tips, I’ve been trying to implement AJAX with the javascript, but I am coming across an error I cant figure out.

views.py:

@login_required
def questions(request):
    if request.method == 'POST':
        person = request.user.profile
        person.interests = request.POST.['interests']
        person.save()
        messages.success(request, f'Your interests have been updated')
        return redirect('profile')

AJAX javascript script:

function subcatClicked(ID){
    $.ajax({
         url: '/update_interests/',
         data: {'interests': ID},
         type: 'POST'
       }).done(function(){
         console.log("Done");
       }); 

The error I am getting is

MultiValueDictKeyError at /questions/

‘interests’

Looking online, I tried solve this by changing this line in views:

person.interests = request.POST.[‘interests’]

to this:

person.interests = request.POST.get(‘interests’)

But that just gets me a new error which is:

IntegrityError at /questions/

NOT NULL constraint failed: users_profile.interests

If you can help me understand how to fix this problem I would appreciate it a lot. Thanks in advance.

What does your profile model look like?

Models.py:

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete = models.CASCADE)
    image = models.ImageField(default = 'default.jpg', upload_to = 'profile_pics')
    interests = models.TextField(default = "")

    def  __str__(self):
        return f'{self.user.username} Profile'

    def save(self):
        super().save()
        img = Image.open(self.image.path)

        if img.height > 300 or img.width > 300:
            output_size = (300,300)
            img.thumbnail(output_size)
            img.save(self.image.path)

So the second error caused by:

indicates that there isn’t a value being supplied in the POST. By default, get returns None if the key isn’t present. To avoid that specific error, you would want to use:
person.interests = request.POST.get('interests', "")

But, that doesn’t address why it’s null to begin with.

First, I really hope you’re using a version of jQuery newer than 1.9 - in which case you should be using method: 'POST' and not type: 'POST'.

Give these changes a try.

Ken

Made the changes, thank you.

With the line you suggested, it is entering whatever comes second in the parentheses, which in this case

person.interests = request.POST.get('interests', "")

is empty. How can I get it to add what 'interests' maps to? I actually want to concatenate it to the existing textfield, creating a list of strings in the textfield.

Probably the easiest thing I can think to do to verify what’s going across the wire is to look at the network tab in the browser’s developer tools to see exactly what’s being posted in the ajax call. My guess would be that what you’re actually sending isn’t what you think you’re sending.

So I see now that, actually, I’m getting an error in the browser:

“$.ajax is not a function”

I downloaded jquery-django using pip, but for some reason it is not identifying my ajax calls. In my INSTALLED_APPS I have 'django.contrib.staticfiles' and 'jquery' already.

But are you including the jquery scripts in your templates being rendered out to the browser?

What should I include as the src in my javascript script?

Sorry, I’m not following what you’re asking here.

Typically, javascript scripts and css files are rendered in templates as standard script and link tags in your html. From the Django perspective, they’re static files - just like any other static file that may be handled.

Thank you for all the help and quick responses so far, I appreciate it a lot.

Here is my entire template. The error I’m getting is in the ajax call all the way at the bottom in the subcatClicked(ID) function.

{% extends "survey/base.html" %}
{% block content %}

<form id="regForm" method = "POST" action="">
  {% csrf_token %}
  <div class = "tab">
    <h1> Here are a few questions to get you started</h1>
    <button type="button" class = "bigButton bigButton1" id="okBtn" onclick="okNext(1)">OK</button>
  </div>

  {% for tag, subcats in questionList.items %}
  <div class = "tab">
    <h1> Are you interested in a(n) {{tag}} group?</h1>
    <button type = "button" class = "bigButton bigButton1" id="noBtn" onclick="noNext(1)">No</button>
    <button type = "button" class = "bigButton bigButton1" id="maybeBtn" onclick = yesNext()>Maybe</button>
    <button type = "button"class = "bigButton bigButton1" id="yesBtn" onclick= "yesNext()">Yes</button>
    <div>
      <button type = "button" class = "button button1" id="prevBtn1" onclick="okNext(-1)">Previous</button>
    </div>
  </div>

      <div class = "subcat">
        <h1>Click on the ones you're interested in</h1>
          {% for subcat in subcats %}
          <input type="button" class = "subcatButton subcatButton1" id="subBtns_{{subcat}}" onclick = subcatClicked("{{subcat}}") value = "{{subcat}}">
          {% endfor %}
          <div>
              <button type="button" class = "button button1" id="nextBtn" onclick="inSubcatNext(1)">Next</button>
          </div>
      </div>

  <div class = "endtab">
    <h1>Great, thanks for answering!</h1>
    <button type="submit" class = "Button Button1" id="exitBtn" href="{% url 'survey-home'%}" >Exit</button>
  </div>

  {% endfor %}


  <p>
    <div style="text-align:center;margin-top:40px;">
      <span class="step"></span>
      <span class="step"></span>
      <span class="step"></span>
      <span class="step"></span>
    </div>
  </p>

</form>

<script src="{{ STATIC_URL }}jquery.js">
var currentTab = 0; // Current tab is set to be the first tab (0)
var currentSubcat = 0;
showTab(currentTab); // Display the current tab

function showTab(n) {
  var x = document.getElementsByClassName("tab");
  x[n].style.display = "block";

    if(n == x.length-1){
       document.getElementById("noBtn").style.display = "Submit";
       document.getElementById("yesBtn").style.display = "Submit";
    }
    else{
        document.getElementById("noBtn").style.display = "inline";
        document.getElementById("yesBtn").style.display = "inline";
    }

  // ... and run a function that displays the correct step indicator:
  fixStepIndicator(n)
}

function showSubcat(n){
  var y = document.getElementsByClassName("subcat");
  y[n].style.display = "block";

  // ... and run a function that displays the correct step indicator:
  fixStepIndicator(n)
}

function okNext(n){
  var x = document.getElementsByClassName("tab");
  var y = document.getElementsByClassName("subcat");
  x[currentTab].style.display = "none";
  if(y[currentSubcat].style.display != "none"){
      y[currentSubcat].style.display = "none"
  }
  currentTab = currentTab + n;
  if(currentSubcat != 0){
    currentSubcat = currentSubcat + n;
  }

  showTab(currentTab);
}

function noNext(n) {
  var x = document.getElementsByClassName("tab");
  var y = document.getElementsByClassName("subcat");

  // Hide the current tab:
  x[currentTab].style.display = "none";
  y[currentSubcat].style.display = "none";
  currentTab = currentTab + n;
  currentSubcat = currentSubcat + n;

  if (currentTab >= x.length) {
    document.getElementById("regForm").submit();
    return false;
  }

  showTab(currentTab);
}

function yesNext(){

  var y = document.getElementsByClassName("subcat");
  var x = document.getElementsByClassName("tab");

  showSubcat(currentSubcat)
 }

function fixStepIndicator(n) {
  var i, x = document.getElementsByClassName("step");
  for (i = 0; i < x.length; i++) {
    x[i].className = x[i].className.replace(" active", "");
  }
  x[n].className += " active";
}

function inSubcatNext(n){

  var y = document.getElementsByClassName("subcat");
  var x = document.getElementsByClassName("tab");

  y[currentSubcat].style.display = "none";
  x[currentTab].style.display = "none";

  currentTab = currentTab + 1;
  currentSubcat = currentSubcat + 1;

  showTab(currentTab);
}

function subcatClicked(ID){
    //ajax call to server to send the subcategory clicked by the user to the database to 
    //register
    $.ajax({
         url: '/update_interests/',
         data: {'interests': ID},
         method: 'POST'
       }) 

  if(document.getElementById(ID).style.backgroundColor != "blue"){
    document.getElementById(ID).style.backgroundColor = "blue"
  }
  else{
    document.getElementById(ID).style.backgroundColor = "white"
  }
}
</script>
{% endblock content%}

What am I missing to get the ajax call recognized?

What does your survey/base.html template look like?

Also, I had assumed from the use of the $.ajax call in your earlier snippet that you were currently using jQuery, but the rest of your code looks like standard JavaScript. I’m guessing then that you’re not using jQuery for anything at the moment?

Thank you, I realized that on my survey/base.html I was using the slim version of jquery, which didn’t support ajax.

Fixing that issue, the error I come across is:
POST http://localhost:8000/questions/ 403 (Forbidden)

Does this have to with the csrf token?

UPDATE: I found I had to pass through the csrf token in the ajax call for every POST.

Thank you for all the help!