How to configure __init__ in FlaskForm to have same functionalities?

Question:

So I want to pass a variable to a flask form, in order to do it I’ve decided to modify the __init__ of SongForm, but when I do it the album field loses its properties in the html. Does anyone know how do i have to correct my __init__

Here is forms.py:

class SongForm(FlaskForm):
    name = StringField("Name", validators=[DataRequired(),Length(max=40)])
    cover = StringField("Cover", validators=[DataRequired(),Length(max=120)])
    content=StringField("Content", validators=[DataRequired(),Length(max=120)])
    release_date=DateField("Release Date", validators=[DataRequired()])
    genre=SelectField("Genre", choices=Genre.list)
    
    album = None
    
    submit = SubmitField("Upload")
    
    def __init__(self, username, *args, **kwargs):
        
        super(SongForm, self).__init__(*args, **kwargs)
        self.album = SelectField("Album", choices=Album.get_albums(username), validate_choice=True )
        self.album.label = 'Album'

NB:Album.get_albums() is a function that executes a query and gets a list from database. So it has to receive the parameter username in the constructor.
Or if there is a better way to pass a parameter without breaking the form.

Here is routes.py:

from flask import Blueprint, render_template, redirect, url_for, flash
from flask_login import login_user, logout_user, login_required, current_user

from Codice.models import *
from Codice.database import *
from Codice.artist.forms import *

# Blueprint Configuration
artist = Blueprint('artist', __name__, static_folder='static',
                 template_folder='templates')

@artist.route('/insertsong', methods=['GET','POST'])
@login_required
def insertsong():
    form = ModifyProfileForm() # <-- don't mind this form will be removed later
    form2 = SongForm(current_user.username)
    
    return render_template('insertsong.html', form = form, form2=form2, user = current_user)

Here is part of insertsong.html code:

<div class="form-outline form-white mb-4">
     {{ form2.album(class="form-control form-control-lg")}}
     {{ form2.album.label(class="form-label")}} 
</div>

But when I execute it breaks showing this error TypeError: 'str' object is not callable:

Traceback (most recent call last):
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask/app.py", line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask/app.py", line 2080, in wsgi_app
    response = self.handle_exception(e)
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask/app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask/app.py", line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask/app.py", line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask/app.py", line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask_login/utils.py", line 303, in decorated_view
    return current_app.ensure_sync(func)(*args, **kwargs)
  File "/home/profscrew/Desktop/Progetto_Basi_Dati-master/Codice/artist/routes.py", line 28, in insertsong
    return render_template('insertsong.html', form = form, form2=form2, user = current_user)
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask/templating.py", line 148, in render_template
    return _render(
  File "/home/profscrew/.local/lib/python3.9/site-packages/flask/templating.py", line 128, in _render
    rv = template.render(context)
  File "/home/profscrew/.local/lib/python3.9/site-packages/jinja2/environment.py", line 1291, in render
    self.environment.handle_exception()
  File "/home/profscrew/.local/lib/python3.9/site-packages/jinja2/environment.py", line 926, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/home/profscrew/Desktop/Progetto_Basi_Dati-master/Codice/artist/templates/insertsong.html", line 1, in top-level template code
    {% extends 'menu.html' %}
  File "/home/profscrew/Desktop/Progetto_Basi_Dati-master/Codice/templates/menu.html", line 1, in top-level template code
    {% extends "base.html" %}
  File "/home/profscrew/Desktop/Progetto_Basi_Dati-master/Codice/templates/base.html", line 35, in top-level template code
    {% block pagebody %}{% endblock %}
  File "/home/profscrew/Desktop/Progetto_Basi_Dati-master/Codice/templates/menu.html", line 86, in block 'pagebody'
    {% block content %}
  File "/home/profscrew/Desktop/Progetto_Basi_Dati-master/Codice/artist/templates/insertsong.html", line 24, in block 'content'
    {{ form2.album.label(class="small mb-1")}}
TypeError: 'str' object is not callable

Although if (class="") is removed from the html code it executes but it shows up as plain text and not a SelectField

PS: Thank you for the help.

Asked By: ProfScrew

||

Answers:

You have tried to set the label property (incorrectly) to the same value which you’ve just set in the previous line via the label parameter, i.e. this second line is redundant.

Anyway, here’s why you are getting the error. Consider the following code:

def __init__(self, username, *args, **kwargs):
        
    super(SongForm, self).__init__(*args, **kwargs)
    self.album = SelectField("Album", choices=Album.get_albums(username), validate_choice=True )
    self.album.label = 'Album' # here is your error

A field’s label property isn’t a str it’s an instance of a Label class, which is why you’re getting the TypeError: 'str' object is not callable error.

An instance of this class is created and assigned to the label property when you pass in a value for the label parameter in a field’s constructor e.g.

name = StringField("Name", validators=[DataRequired(),Length(max=40)])

If you want to change a field’s label at runtime you’d do something like the following:

# within the form class
self.album.label = Label(self.album.id, 'Album Name')

# or on a form instance
form2.album.label = Label(form2.album.id, 'Album Name')

Personally, I wouldn’t have any database calls in a form’s definition, I’d set the choices at runtime. e.g.

class SongForm(FlaskForm):
    name = StringField("Name", validators=[DataRequired(),Length(max=40)])
    cover = StringField("Cover", validators=[DataRequired(),Length(max=120)])
    content = StringField("Content", validators=[DataRequired(),Length(max=120)])
    release_date = DateField("Release Date", validators=[DataRequired()])
    genre = SelectField("Genre")
    album = SelectField("Album", validate_choice=True)


@artist.route('/insertsong', methods=['GET','POST'])
@login_required
def insertsong():
    form = ModifyProfileForm() # <-- don't mind this form will be removed later
    
    form2 = SongForm()
    
    # Set your choices at runtime
    form2.genre.choices = Genre.list
    form2.album.choices = Album.get_albums(username)
    
    return render_template('insertsong.html', form = form, form2=form2, user = current_user)    
Answered By: pjcunningham