Tourist Attractions - Error when adding locations

I’ve completed all of the steps for the Tourist Attractions project. Everything is working except for adding a new location. I keep getting the following error:

_too many values to unpack (expected 1)

It points to line 19 in app.py assigning [(name, action)] = request.form.items()
Here is my code for app.py:

from flask import Flask, render_template, request, redirect, url_for
from locations import Locations
from forms import AddLocationForm

app = Flask(__name__)
app.config['SECRET_KEY'] = 'SECRET_PROJECT'

visit = Locations()
categories = {"recommended": "Recommended", "tovisit": "Places To Go", "visited": "Visited!!!", }

UP_ACTION = "\u2197"
DEL_ACTION = "X"

@app.route("/<category>", methods=["GET", "POST"])
def locations(category):
  locations = visit.get_list_by_category(category)
  ## Check the request for form data and process
  if request.method == "POST":
    [(name, action)] = request.form.items()

    if action == UP_ACTION:
      visit.moveup(name)
    elif action == DEL_ACTION:
      visit.delete(name)
  ## Return the main template with variables
  return render_template("locations.html", category=category, categories=categories, locations=locations, add_location=AddLocationForm())

@app.route("/add_location", methods=["POST"])
def add_location():
  ## Validate and collect the form data
  add_form = AddLocationForm()
  category = add_form.category.data
  if add_form.validate_on_submit():
      name=add_form.name.data
      description=add_form.description.data
      category=add_form.category.data
      visit.add(name, description, category)

  ## Redirect to locations route function
  return redirect(url_for("locations", category=category, _external=True, _scheme='https'))

@app.route("/")
def index():

  ## Redirect to locations route function
  return redirect(url_for("locations", category="recommended", _external=True, _scheme='https'))

I’ve tried debugging locally and get HTTPStatus.BAD_REQUEST

If I navigate back to the “Recommended” page by typing the URL in my browser, it does in fact show that the location has been added.

My full project is on GitHub: http://github.com/mattRicotta/tourist-attractions

That probably means either you’re unpacking it in not quite the right way or your data is not coming packaged as you expect it. Solving either one will fix the problem. If you’re doing it locally, when you set to debug you can throw in a print statement to print the object your trying to unpack and even check the object type. (I’ll ignore the third possibility, which is that it may not be the instruction you were purely given… in either case this is a good thing to debug so it’s win-win to figure it out).

For this type of thing, I usually just practice unpacking in a small test file or on the console with small dummy data that’s in the same object form to make sure my technique is ok… then I double check the pipeline of how the data is coming in.

Additionally, since it seems like you know how to run this locally, I really think that’s the best way to start from the very beginning, as you can verify the code you’re building (even if it runs well).

1 Like

Thanks for your help. I am very, very new to running Flask locally as in I just learned how to do it today. I removed the form action for the time being and added a few print statements with a breakpoint afterwards to help me just see the contents of request.form.items():

Print statements added to locations() route:

    print("TYPE: ", type(request.form.items()))
    i = 1
    for item in request.form.items():
      print("ITEM #", i, ": ", item)
      i += 1

This is my output in the terminal:

TYPE:  <class 'generator'>
ITEM # 1 :  ('csrf_token', 'Ijc2OWYwN2Y4YjJmMTU1ZTI1N2VkMGYzY2VmM2ZjYjk5YzcwOTc0NzEi.YErLwA.jgnM9TK0c5Y4ab4m17lAKAgPK_k')
ITEM # 2 :  ('name', 'Test Location')
ITEM # 3 :  ('description', 'Test Description')
ITEM # 4 :  ('category', 'recommended')
ITEM # 5 :  ('submit', 'Add Location')

This looks like it’s trying to force five different items into a tuple with a length of 2. This makes sense with the error I’m getting. Again, I’m pretty new to Python and even newer to Flask so this is the best conclusion I can draw.

Troubleshooting what is actually going wrong is a whole other beast.

If it helps, this is the template for the form:

<form class="addform" action="url_for('add_location')" method="POST"> <!-- set action attribute here -->
  <!-- call hidden_tag() here -->
  {{ add_location.hidden_tag() }}
  <table>
    <colgroup>
      <col style="width: 40%">
      <col style="width: 40%">
      <col style="width: 20%">
    </colgroup>
    <tbody>
      <tr>
        <td>{{ add_location.name.label }}</td> <!-- insert location name label here -->
        <td>{{ add_location.description.label }}</td> <!-- insert location description label here -->
        <td>{{ add_location.category.label }}</td> <!-- insert location category label here -->
      </tr>
      <tr>
        <td>{{ add_location.name() }}</td> <!-- insert add_location name here -->
        <td>{{ add_location.description() }}</td> <!-- insert add_location description here -->
        <td>
          <!-- begin for loop here -->
          {% for button in add_location.category %}
            <div>{{ button() }}{{ button.label }}</div> <!-- insert button here -->
          <!-- end for loop here -->
          {% endfor %}
        </td>
      </tr>
      <tr>
        <td>{{ add_location.submit() }}</td> <!-- insert submit here -->
      </tr>
    </tbody>
  </table>
</form>

The console is my best friend for small technical python bits.

>>> test = {1:"foo",2:"bar",3:"baz"}
>>> [(x,y)] = test.items()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 1)
>>> [x, y] = test.items()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
>>> test.items()
dict_items([(1, 'foo'), (2, 'bar'), (3, 'baz')])
>>> [a,b,c] = test.items()
>>> a
(1, 'foo')
>>> b
(2, 'bar')
>>> c
(3, 'baz')

I hope this highlights how you can play to find solutions. I wouldn’t take them as things where you are stuck, these are the moments where we get a lot of great ideas of how the language works.

The solution you come up with is up to you of course, I think it’s valid to have your unique way to resolve it, and then potentially come back and interpret what the instructions may have wanted you to do. Flask in particular requires a lot of technique so this kind of exercise (of different ways of solving a problem) is useful as it builds intuition for what is less and more efficient.

Don’t lose spirit, you’ve done well to come this far with flask!

2 Likes

Thank you for all your help! This has been a learning experience indeed! In addition to Flask it’s been excellent practice for me with using environments, VSCode, and using Git to branch my project and try different attempts and compare them to my first attempt.

That said, I re-did the project from the square one in codecademy’s webapp. I copied the three files into my new branch to examine the differences in my code. I cannot, for the life of me, see what I did differently this time but it’s working now!

One strange behavior I’m noticing now - the project doesn’t work locally with HTTPS, so I changed my redirect _scheme variables to HTTP to test it locally. Locally, it works perfectly. If I run it on codecademy’s webapp using HTTP or HTTPS - like the project says to do - I get the following error:

UnboundLocalError: local variable 'category' referenced before assignment

Again, if I run it locally, it works perfectly. I’m at a loss for why, but I’m content enough to move on from this project to the next part of the syllabus.

That particular error typically occurs if you use an identifier / name for an object from outside a function and assign to an identical name within the function (the local name takes precedence and shadows the outer scope).

UnboundLocalError: local variable 'category' referenced before assignment

As an example-

x = 3
 def func():
    x = x
    pass

When creating that function you have a variable called x that is a local assignment. Because this is a local variable it effectively shadows any identical names from the surrounding scope so you can no longer refer to the x from your global namespace (the one where it was assigned to 3). Instead it the function throws an error because it tried to use a variable that has not been assigned yet.

You’d need to know a bit about namespaces and scope for that to make sense but long story short you should try to avoid situations where you reference the outer scope and assign to something with the same name in the function.

I’ve not used flask so I’ll not try and decipher how this occurs in your code but that’s something that you’d want to look for.

This is the block of code that is causing the error:

@app.route("/add_location", methods=["POST"])
def add_location():
  ## Validate and collect the form data
  add_form = AddLocationForm()
  if add_form.validate_on_submit():
      name=add_form.name.data
      description=add_form.description.data
      category=add_form.category.data
      visit.add(name, description, category)

  ## Redirect to locations route function
  return redirect(url_for("locations", category=category, _external=True, _scheme='https'))

I’m pretty comfortable with scopes from experience with other languages, though there are a lot of other processes going on with this flask project that make it a little complex to resolve.

Submitting the webform is what triggers add_location() to run and it also assigns ‘validate_on_submit()’ to True, so ‘category’ should be assigned a variable when this code runs.

What is peculiar is that the same exact code runs just fine on my computer, but throws an error in the codecademy webapp - where I don’t have access to a console.

Which line throws an error? Could you have a situation in which add_form.validate_on_submit() evaluates to False? Then you’d be attempting to use variables that aren’t yet assigned. If the cc editor is being cryptic a little logging/printing would help you find out at least where the error is thrown.

This is where the error occurs:

​return redirect(url_for("locations", category=category, _external=True, _scheme="https")

I assume it is because ‘category’ has not been assigned a value for whatever reason. I’m not sure how to debug within the codecademy webapp, or if that is even possible.

As I stated, if I run the same app from VSCode locally, it runs fine and doesn’t raise this error.

I just meant start throwing in some print statements or something along those lines to check on the state of the code prior to the error.

Testing the boolean value of add_form.validate_on_submit() would probably be the first stop as I’d assume that’s why nothing is being assigned. That might be wrong but start somewhere and start digging if you wanted to know more. As for a difference between running it locally and on cc there could be a lot of differences be that simple versioning issues or something else entirely, like I say I’m not even familiar with flask so I don’t know where to start with that one.

2 Likes

I’m going to double up on @tgrtim mentioning versions. It’s super important with flask (and python in general) to be strict about virtual environments so that you know you get consistent results wherever you are (and that adds ease of deployment as well).

2 Likes

Got it.

The issue with debugging this on the web interface is that the flask exercises display the HTML output (below). There is no terminal - at least not that I know of, so I don’t know if I can see the output of print() if I’m doing the project in my browser.

2 Likes

Good stuff. A bit trickier without simple prints I guess, especially since you can’t use standard debugging tools either. Might have to get a bit creative with debugging in that case, can you perhaps dump things to the locally hosted page?

That’s a good thought, but considering I got this project working locally - and followed the instructions to a T - I’m checking it off as done and moving on to the next part of the syllabus. Creative debugging in the web interface sounds like a rabbit hole I don’t need to go down right now with everything else I’m trying to learn :wink:

I figured this out in case anyone else is looking for the solution.

@app.route("/add_location", methods=["POST"])
def add_location():
  ## Validate and collect the form data
  add_form = AddLocationForm(csrf_enabled=False)
  # category = add_form.category.data

  if add_form.validate_on_submit():
      name=add_form.name.data
      description=add_form.description.data
      category=add_form.category.data
      visit.add(name, description, category)

  ## Redirect to locations route function
  return redirect(url_for('locations', category=category, _external=True, _scheme='https'))

Essentially, pass in csrf_enabled=False to AddLocationForm(). Defining category after add_form caused the error to go away but was not adding the location. Went back to the exercises and saw the examples had that argument.

2 Likes

This fixed the error for me and saved me a huge headache trying to debug this.

Thanks for posting the solution here.

Encountered this just now. We can now see that adding csrf_enabled=False makes it work. Well yes it will work but is removing a security check really a solution?

Yes in the last (10/10) page of the lesson/exercises, strangely, the argument csrf_enabled=False is used. However, in (7/10) of the lesson/exercises, csrf form validation is demonstrated, is not disabled, and works in the html browser!

Would be great if someone from Codecademy will look into this and truly confirm what was the issue. And to also explain why even in their own material, they had to put csrf_enabled=False in the last part (10/10) of the lesson while it is working fine in (7/10) of the lesson.

I don’t know if the issue is because there is something wrong with the CC learning environment, or because the lesson is based on an outdated version of Flask, or some other cause…

As a remark, Cross Site Request Forgery (CSRF) protection should always be enabled (when deploying). Better not to have a site running than to have it running with clear vulnerabilities.
@bbgmp @sphynx630

Some references for reading more on this:

https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html

1 Like