Unidentified form flask Jinja template attribute model - python

I'm trying to create an edit form in flask, to compliment my working add and delete form - but I'm getting an error about items not being defined.
The template I have:
% extends "main/_base.html" %}
{% block title %}Edit Item{% endblock %}
{% from "main/_formshelper.html" import render_field %}
{% block content %}
<form action="{{ url_for('main.edit_item', items_id=item.Items.id) }}" method="post" name="edititemsform">
{{ form.csrf_token }}
<div class="form-group">
{{ form.name.label }}
{{ render_field(form.name, class="form-control", value=item.name) }}
</div>
<div class="form-group">
{{ form.notes.label }}
{{ render_field(form.notes, class="form-control", value=item.notes) }}
</div>
<input type="submit" value="Edit Item" class="btn btn-lg btn-success">
</form>
{% endblock %}
The view that drives it:
#main_blueprint.route("/edit_item/<int:items_id>", methods=["GET", "POST"])
def edit_item(items_id):
form = EditItemsForm(request.form)
item_with_user = db.session.query(Items).filter(Items.id == items_id).first()
if item_with_user is not None:
if request.method == 'POST':
if form.validate_on_submit():
try:
item = Items.query.get(items_id)
item.name = form.name.data
item.notes = form.notes.data
db.session.commit()
flash('Item edited')
return redirect(url_for('main.all_items'))
except:
db.session.rollback()
flash('Item edit error')
return render_template('main/edit_item.html', item=item_with_user, form=form)
else:
flash('Item does not exist')
return redirect(url_for('main.all_items'))
The error I get is :
jinja2.exceptions.UndefinedError: 'app.main.models.Items object' has no attribute 'Items'
This is my model which I think is the root of the issue, is it that is looking for Items inside my model Items?
from app import db
class Items(db.Model):
__tablename__ = 'items'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
notes = db.Column(db.String, nullable=True)
def __init__(self, name, notes):
self.name = name
self.notes = notes
def __repr__(self):
return '<id {}>'.format(self.id)

When creating the URL for the form's action parameter, you are referencing an incorrect attribute. You've already passed an Items object to the template and now you just need to request the id attribute and pass it to url_for.
<form
action="{{ url_for('main.edit_item', items_id=item.id) }}"
method="post"
name="edititemsform"
>
<!-- ... -->
</form>
In order not to manually assign a value to every input field within the template, I recommend that you simply assign the object to be edited to the form. The validate_on_submit function then checks whether the request is of the POST type and whether the input is valid. Then you can use populate_obj to update all existing attributes. The database entry is then saved by commit.
#main_blueprint.route("/edit_item/<int:items_id>", methods=["GET", "POST"])
def edit_item(items_id):
item = Items.query.get_or_404(items_id)
form = EditItemsForm(request.form, obj=item)
if form.validate_on_submit():
form.populate_obj(item)
try:
db.session.commit()
flash('Item edited')
return redirect(url_for('main.all_items'))
except:
db.session.rollback()
flash('Item edit error')
return render_template('main/edit_item.html', **locals())

Related

django redirect to form view and autofill with previously entered values

I have following scenario.
User fills out a form
If the user clicks the "continue" button and the form is valid the user will be redirected to a summary view
In the summary view the user checks the input again. He can either continue or go back.
If he continues the data will be saved in the database, if he goes back he can edit the form.
Since in step 4 the user is at the view summary I have to redirect him to the home view. I don´t want the user to fill out the entire form again, the previously entered data should be autofilled if he goes back.
Something special: I am using django-tagify2 for one input in the form to get tags rather then text. If the user goes back the tags should be rendered correctly in the tagify specific form.
So here are my files:
home.html
{% extends "messenger/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="message-container">
<form method="POST" autocomplete="off">
{% csrf_token %}
{{ form|crispy }}
<button name="sendmessage" class="btn btn-outline-info" type="submit">Continue</button>
</form>
</div>
{% endblock content %}
summary.html
{% extends "messenger/base.html" %}
{% block content %}
<h4>Zusammenfassung</h4>
<p><b>Empfänger: </b>{{ receiver }}</p>
<br>
<p><b>Betreff: </b>{{ subject }}</p>
<br>
<p><b>Nachricht: </b>{{ message }}</p>
<button>Edit</button>
<button>Continue</button>
{% endblock content %}
home view
#login_required(login_url='login')
def home(request):
if request.method == 'POST' and 'sendmessage' in request.POST:
message_form = MessageForm(request.POST)
if message_form.is_valid():
receiver_list = message_form['receiver'].value().split(';')
subject = message_form['subject'].value()
message = message_form['textmessage'].value()
#create sessions and send data to next view
session_receiver = receiver_list
request.session['session_receiver'] = session_receiver
session_subject = subject
request.session['session_subject'] = session_subject
session_message = message
request.session['session_message'] = session_message
return redirect('summary')
else:
message_form = MessageForm()
return render(request, 'messenger/home.html', {'form': message_form})
summary view
def summary(request):
receiver = request.session.get('session_receiver')
subject = request.session.get('session_subject')
message = request.session.get('session_message')
return render(request, 'messenger/summary.html', {'receiver':receiver,
'subject':subject,
'message':message})
So what is the best way to do this?
Can I use the session variables to set the fields in the form?
I don´t want to change the logic in the app. I want a home/summary/success view/template where I can loop as long is I want between home and summary until the user is happy with his entered form data
How about checking request.session when there is get request to home view? Then you can bind message_form = MessageForm() to session data.
You can check out htmx and django-htmx. You can do what you want easily without session by swapping HTML with context.
I played around with the session values and views and finally got a way to redirect to other views with prefilled form fields based on session values.
#login_required(login_url='login')
def home(request):
if request.method == 'POST' and 'sendmessage' in request.POST:
message_form = MessageForm(request.POST)
if message_form.is_valid():
ad_group = message_form['ad_group'].value().split(';')
ad_user = message_form['ad_user'].value().split(';')
subject = message_form['subject'].value()
message = message_form['textmessage'].value()
#create sessions and send data to next view
session_ad_group = ad_group
request.session['session_ad_group'] = session_ad_group
session_ad_user = ad_user
request.session['session_ad_user'] = session_ad_user
session_subject = subject
request.session['session_subject'] = session_subject
session_message = message
request.session['session_message'] = session_message
return redirect('summary')
else:
if request.session.get('session_subject'):
message_form = MessageForm(initial={'ad_group': request.session.get('session_ad_group'),
'ad_user': request.session.get('session_ad_user'),
'subject': request.session.get('session_subject'),
'textmessage': request.session.get('session_message')})
return render(request, 'messenger/home.html', {'form': message_form})
else:
message_form = MessageForm()
return render(request, 'messenger/home.html', {'form': message_form})
def summary(request):
ad_group = request.session.get('session_ad_group')
ad_user = request.session.get('session_ad_user')
subject = request.session.get('session_subject')
message = request.session.get('session_message')
if request.method == 'POST' and 'edit' in request.POST:
message_form = MessageForm(initial={'ad_group':ad_group, 'ad_user': ad_user,
'subject':subject, 'textmessage':message})
return render(request, 'messenger/home.html', {'form': message_form})
return render(request, 'messenger/summary.html', {'ad_group':ad_group,
'ad_user': ad_user,
'subject':subject,
'message':message})
Template
{% extends "messenger/base.html" %}
{% block content %}
<h2>Zusammenfassung</h2>
<div class="border-top pt-3">
<p><b>AD-Gruppe: </b>{{ ad_group }}</p>
<p><b>AD-User: </b>{{ ad_user }}</p>
<br>
<p><b>Betreff: </b>{{ subject }}</p>
<br>
<p><b>Nachricht: </b>{{ message }}</p>
<div class="buttoncontainer">
<form name="edit" action="" method="post">
{% csrf_token %}
<button class="btn edit_btn" formaction="{% url 'messenger-home' %}">Zurück</button>
</form>
<form name="senden" action="" method="post">
{% csrf_token %}
<button class="btn continue_btn" formaction="{% url 'send_messages' %}">Nachricht senden</button>
</form>
</div>
</div>
{% endblock content %}

jinja2.exceptions.UndefinedError: 'intern_to_update' is undefined

I'm trying to build a web app using Python Flask that can perform CRUD operations for a database that already exists in PostgreSQL. Below I'm trying to perform the "Update Operation"
Here is my table for the database;
class Intern(db.Model):
intern_id = db.Column(db.Integer, primary_key=True)
intern_name = db.Column(db.String(150), nullable=True)
date_created = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return '<Name %r>' % self.intern_id
Following is my update function in "app.py";
#app.route('/update/<int:intern_id>', methods=['POST', 'GET'])
def update(intern_id):
intern_to_update = Intern.query.get_or_404(intern_id)
if request.method == "POST":
intern_to_update.intern_name = request.form['name']
try:
db.session.commit()
return redirect('/successful')
except:
return "There was a problem updating... Please try again!"
else:
return render_template('update.html', intern_to_update=intern_to_update)
Following is my successful function in "app.py". This function creates the intern.
#app.route('/successful', methods=['POST', 'GET'])
def successful():
if request.method == "POST":
intern_name = request.form["intern_name"]
new_intern = Intern(intern_name=intern_name)
if not intern_name:
show_error = "You need to fill out the forms required. Please try again."
return render_template("fail.html", show_error=show_error)
try:
db.session.add(new_intern)
db.session.commit()
return redirect('/successful')
except:
return "There was an error adding the new intern! Please try again"
else:
interns = Intern.query.order_by(Intern.date_created)
return render_template("successful.html", interns=interns)
Here is my "update.html" file;
{% extends 'base.html' %}
{% block title %}Update{% endblock %}
{% block content %}
<div class="container">
<h1>Update Operation</h1>
<br/><br/>
<form action="/update/{{intern_to_update.intern_id}}" method="POST">
<div class="mb-3">
<b><p> {% block info %} Please enter the credentials below {% endblock %} </p></b>
</div>
<div class="mb-3">
<input type="text" placeholder="Intern Name" name="name" value="{{intern_to_update.name}}">
</div>
<button type="submit" class="btn btn-primary" value="Update Name">Create</button>
</form>
<br/>
<div class="mb-3">
<b><p> Thank you, your operation is successfully completed! </p></b>
</div>
</div>
{% endblock %}
Lastly, below is where the update link is coming from in "base.html" file;
<li class="nav-item">
<a class="nav-link" href="{{url_for('update', intern_id = intern_to_update.intern_id)}}">Update</a>
</li>
There are no errors whatsoever with creating an intern. So my "successful" function is working without any mistakes. But I can't even see if my "update" function is working. Can someone please tell me why I'm getting this error I couldn't figure out? I checked other solutions for other users, which is usually adding "return render_template" for ELSE (GET) part for their code but I already did that.

Issue with csrf_token while using Flask and WTForms

I'm trying to set up a basic "Contact" form for my website, which will basically take in values and then put them into a CSV file. The issue I'm having is that the entries cannot be validated cause they're missing a csrf_token?
Here's the relevant code from my app.py:
#app.route('/contact_end', methods=['POST'])
def handle_contact():
form = ContactForm()
print(form.name.data)
if form.validate_on_submit():
print("yup")
with open('data/messages.csv', 'a') as f:
print("oh shit")
writer = csv.writer(f)
writer.writerow([form.name.data, form.email.data, form.message.data])
print("waddup")
return redirect(url_for('contact_handler.html'), name=form.name.data)
print(form.errors)
return render_template('contact.html', form=form)
It skips over the if statement as it never ends out printing the "yup", and instead it prints out the error:
{'csrf_token': ['The CSRF token is missing.']}
The template that this connects to is:
{% extends "base_template.html" %}
{% block title %}Contact us {% endblock %}
{% block content %}
<p>Feel free to use the contact form below to send us any questions you might have.</p></br>
<form action="/contact_end" method="post">
{{ form.csrf_token }}
<label>Your Name <input type="text" name="name"/></label></br>
<label>Your Email <input type="text" name="email"/></label></br>
<label>Your Name <textarea name="message"></textarea></label></br>
<button type="submit">Send</button>
<button type="reset">Clear</button>
</form>
{% endblock %}
I've tried messing with form.csrf_token and .hidden_tags(), but with no success.
As well, this is the initial part of app.py that brings you to the page in the first place, the one above is the endpoint for the form:
#app.route('/contact')
def contact():
return render_template('contact.html', form=form)
Finally, here's my ContactForm class:
class ContactForm(FlaskForm):
print("yep")
name = StringField('Name', validators=[InputRequired()])
email = EmailField('Email', validators=[InputRequired(), Email()])
message = TextAreaField('Message', validators=[InputRequired()])
I've made sure to set my secret key, as well. Anyone have any idea why this isn't working? Many thanks.
You still need to create the form instance in your contact() function:
#app.route('/contact')
def contact():
form = ContactForm()
return render_template('contact.html', form=form)

flask wtforms-alchemy QuerySelectField ValueError: too many values to unpack (expected 2)

i am trying to create sign up form using flask-wtf, and wtforms-alchemy
on this form i try create selectfield, which the value is query from my models.
but i'am always get this error:
ValueError: too many values to unpack (expected 2)
this my code:
from flask_wtf import FlaskForm
from wtforms_sqlalchemy.fields import QuerySelectField
def choose_domicile():
return Domicile.query
class RegisterForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
domicile = QuerySelectField(u'Domicile', query_factory=choose_domicile)
#app.route('/signup', methods=['GET', 'POST'])
def signup():
form = RegisterFormView()
try:
if form.validate_on_submit():
new_user = Data(name=form.name.data, domicile=form.domicile.data)
db.session.add(new_user)
db.session.commit()
return "Success"
except:
return "Failed"
return render_template('signup.html', form=form)
and this my signup.html:
{% block content %}
<div class="container">
<form class="form-signin" method="POST" action="/signup">
<h2 class="form-signin-heading">Sign Up</h2>
{{ form.hidden_tag() }}
{{ wtf.form_field(form.name) }}
{{ form.domicile }}
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign Up</button>
</form>
</div>
{% endblock %}
my package versions is flask-wtf==0.14.2 and wtforms-alchemy==0.16.7
i have try to follow this Why does Flask WTForms and WTForms-SQLAlchemy QuerySelectField produce too many values to unpack? , but i am still confused how to do that.
You can define a function as following which can be passed as a get_pk paramater.
def get_pk(obj):
return str(obj)
and then do the following change
domicile = QuerySelectField(u'Domicile', query_factory=choose_domicile, get_pk=get_pk)
I am assuming that you have define __str__ in model class else return pk in get_pk

Multiple Django Crispy Forms In One View

When placing 2 forms in a view using the form|crispy filter and this answer to handle 2 forms in a single view: Proper way to handle multiple forms on one page in Django I am getting this error.
views.py:
def test_form(request):
if not request.user.is_authenticated():
return redirect(settings.LOGIN_URL)
title = 'test form'
row_control_form = RowControlForm(request.POST or None)
entry_form = EntryForm(request.POST or None)
context = {
'title': title,
'row_control_form': row_control_form,
'entry_form': entry_form,
}
if 'row_control_submit' in request.POST:
if row_control_form.is_valid():
row_control_form.save()
if 'entry_submit' in request.POST:
if entry_form.is_valid():
entry_form.save()
return render(request, "timesheet/test_form.html", context)
forms.py
class RowControlForm(forms.ModelForm):
class Meta:
model = RowControl
fields = ['month_control_record', 'department', 'activity', 'notes']
def clean(self):
cleaned_data = self.cleaned_data
# Ensures row is unique
try:
RowControl.objects.get(month_control_record=cleaned_data['month_control_record'],
department=cleaned_data['department'],
activity=cleaned_data['activity'],
notes=cleaned_data['notes'])
except RowControl.DoesNotExist:
pass
else:
raise ValidationError('This row already exists')
# Always return cleaned data
return cleaned_data
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['row_control', 'date', 'hours']
def clean(self):
cleaned_data = self.cleaned_data
# Ensures data is unique (only 1 hours entry for each date and row_control)
try:
Entry.objects.get(row_control=cleaned_data['row_control'],
date=cleaned_data['date'])
except Entry.DoesNotExist:
pass
else:
raise ValidationError('This entry already exists')
# Always return cleaned data
return cleaned_data
test_form.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="col-md-6 col-md-offset-3">
<h1 class="page-header"> Form Test </h1>
<form method="POST" action="{{ request.path }}">
{% csrf_token %}
{{ row_control_form|crispy }}
<button class="btn btn-primary" type="submit" value="Submit" name="row_control_submit" ><i class="fa fa-lg fa-floppy-o"></i> Save</button> </form>
</br>
</div>
<div class="col-md-6 col-md-offset-3">
<h1 class="page-header"> Form Test </h1>
<form method="POST" action="{{ request.path }}">
{% csrf_token %}
{{ entry_form|crispy }}
<button class="btn btn-primary" type="submit" value="Submit" name="entry_submit" ><i class="fa fa-lg fa-floppy-o"></i> Save</button> </form>
</br>
</div>
{% endblock %}
To provide context to the error:
Line 42 of forms.py is:
Entry.objects.get(row_control=cleaned_data['row_control'],
EDIT: Further investigation has shown that the issue is that both form validations are being run no matter which submit button is pressed, the request.POST when submitting valid data for the RowControlForm is:
<QueryDict: {'csrfmiddlewaretoken': ['HffmmbI31Oe0tItYDfYC4MoULQHL0KvF'], 'notes': ['Cool'], 'row_control_submit': ['Submit'], 'month_control_record': ['1'], 'department': ['1'], 'activity': ['1']}>
Therefore entry_submit is not in the request.POST and that validation should not run yet it is?
Firstly, you need to fix this line of your form's clean method
def clean(self):
...
Entry.objects.get(row_control=cleaned_data['row_control'],
You can't assume that row_control will be in the cleaned_data. You either need to add a check if 'row_control' in cleaned_data or catch the KeyError, then update the rest of the method appropriately. You should fix this, even though you didn't see this error until you put multiple forms on one page. It shouldn't be possible to cause a 500 server error by leaving a value out of a POST request. Users could do this even if there is only one form on the page.
Validation is running for both forms, because you are instantiating both forms with the post data, regardless of which submit button was pressed.
row_control_form = RowControlForm(request.POST or None)
entry_form = EntryForm(request.POST or None)
You should only use the POST data for the form you wish to submit.
row_control_form = RowControlForm()
entry_form = EntryForm()
if 'row_control_submit' in request.POST:
row_control_form = RowControlForm(request.POST)
if row_control_form.is_valid():
if 'entry_submit' in request.POST:
entry_form = EntryForm(request.POST)
if entry_form.is_valid():
entry_form.save()
Finally, it's good practice to redirect the user once they have successfully submitted a valid form.

Categories

Resources