Kept getting werkzeug.routing.BuildError - python-3.x

I tried running the code and got this werkzeug.routing.BuildError: Could not build url for endpoint 'book'. Did you mean 'sign_in' instead?
Also, if i try to access the /main route, it says URL not found on server. Can somebody please help me.
import os
import csv
from flask import Flask, session, render_template, request
from flask_session import Session
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
app = Flask(__name__)
# Check for environment variable
if not os.getenv("DATABASE_URL"):
raise RuntimeError("DATABASE_URL is not set")
# Configure session to use filesystem
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
# Set up database
engine = create_engine(os.getenv("DATABASE_URL"))
db = scoped_session(sessionmaker(bind=engine))
f = open("books.csv")
reader = csv.reader(f)
for isbn, title, author, year in reader:
db.execute("INSERT INTO books (ISBN, title, author, year) VALUES (:ISBN, :title, :author, :year)",
{"ISBN": isbn, "title": title, "author": author, "year": year})
print(f"Added flight from {isbn} to {year} lasting {title} minutes.")
db.commit()
#app.route("/")
def sign_in():
return render_template("Sign.html")
#app.route("/main" method = "POST")
def book():
name = request.form.get("name")
return render_template("books.html", username=name)
This is the sign.html document
{% extends "layout.html" %}
{% block title %}Sign in{% endblock %}
{% block body %}
<div class="main">
<p class="sign" align="center">Sign in</p>
<form action= "{{ url_for('book') }}" class="form1" method="post">
<input name = "name" class="un " type="text" align="center" placeholder="Username">
<input class="pass" type="password" align="center" placeholder="Password">
<button class="submit" align="center">Sign in</button>
</form>
</div>
{% endblock %}
This is the books.html document
{% extends "layout.html" %}
{% block title %} {{username}} {% end block%}
{% block body%}
{% end block %}

#app.route("/main" method = "POST")
There are two things wrong with this line:
no comma after "/main" (it should cause syntax error, so I assume it just got lost in the snippet)
allowed methods for an endpoint are defined with keyword argument methods, as a iterable of strings, so in your code it should go like methods = ["POST"] instead of method = "POST".
However, in newest version of Flask (1.1.2), the second issue should cause TypeError, so maybe you're using some older version which doesn't handle such case and the result is that the endpoint doesn't exist.

Related

How do I update Django database from field choices?

I'm developing a CRM using django and for my lead details I am trying to display my choices in the html form but also have it update the database when a new choice is selected and saved. Currently, I am able to display the choice that was selected upon lead creation, but I don't know how to allow the agent to change the choice and have that new choice update the database. I am still in the process of learning Django so theres still a lot i'm learning as i go.
views.py
class LeadDetailView(LoginRequiredMixin, DetailView):
template_name = "lead-details.html"
queryset = Lead.objects.all()
context_object_name = "leads"
models.py
class Lead(models.Model):
PROBLEM_LEAD = 'Problem Lead'
PAY_TOO_FAR = 'Payments Too Far'
PAY_TOO_SMALL = 'Payments Too Small'
NPI = 'No Payment Information'
ACTIVE = 'Active Deal'
NO_DEAL = 'No Deal'
choices_lead_status = [
(PROBLEM_LEAD, 'Problem Lead'),
(PAY_TOO_FAR,'Payments Too Far'),
(PAY_TOO_SMALL,'Payments Too Small'),
(NPI,'No Payment Information'),
(ACTIVE,'Active Deal'),
(NO_DEAL,'No Deal')
]
choices_lead_owner = [
("Settlement, New",'Settlement, New'),
("Lead, Sales",'Lead, Sales'),
]
lead_status = models.CharField(max_length=100, choices=choices_lead_status, blank=True)
lead_owner = models.CharField(max_length=100, choices=choices_lead_owner, blank=True)
agent = models.ForeignKey(Agent, null=True, on_delete=models.SET_NULL, blank=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
html
# this displays the lead_owner but i also need it to iterate through the choices to allow proper selection
<div id="select">
<div class="form-group">
<label for="input-select">Lead </label>
<select class="form-control" id="input-select" >
<option>{{ leads.lead_owner}}</option>
<!-- <option>Lead, Sales</option>
<option>Settlement, New</option>
<option>Corden, James</option>
<option>Rower, Charles</option>
<option>Has to be dynamic</option> -->
</select>
</div>
</div>
There are a few things to take into account and more than one way (as always) to accomplish it. Here's my suggestions:
First, update your HTML to use Django tags with a for loop and iterate your lead_owner choices. If the value matches the existing Lead_owner value, it's pre-selected:
<div id="select">
<div class="form-group">
<label for="input-select">Lead Owner</label>
<select class="form-control" id="input-select" name="lead_owner">
{% for choice in leads.choices_lead_owner %}
{% if choice.0 == leads.lead_owner %}
<option value="{{ choice.0 }}" selected>{{ choice.1 }}</option>
{% else %}
<option value="{{ choice.0 }}">{{ choice.1 }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
Submitting and updating the form can be done as such by inheriting from your LeadDetailView. I included the imports as well:
from django.views.generic.edit import UpdateView
from django import forms
class LeadUpdateView(LoginRequiredMixin, UpdateView):
model = Lead
form_class = LeadUpdateForm
template_name = "lead-update.html"
success_url = reverse_lazy('lead_detail')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['leads'] = self.object
return context
Now, you can create a new template and extend your existing HTML template like so by :
{% extends 'lead-details.html' %}
{% block content %}
<h1>Update Lead</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}
Hope this helps.
Edited for your question:
You could use the existing view and form.
You can edit the LeadDetailView to be something like this:
class LeadDetailView(LoginRequiredMixin, DetailView):
template_name = "lead-details.html"
queryset = Lead.objects.all()
context_object_name = "leads"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = LeadOwnerForm(instance=context['leads'])
return context
def post(self, request, *args, **kwargs):
lead = self.get_object()
form = LeadOwnerForm(request.POST, instance=lead)
if form.is_valid():
form.save()
messages.success(request, "Lead updated successfully!")
else:
messages.error(request, "Lead update failed.")
return redirect('lead_detail', pk=lead.pk)
And, then the form can be adjusted like so:
{% block content %}
{{ leads.first_name }} {{ leads.last_name }}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}
Another approach would be using Ajax, which is a bit more complicated but offers some solid benefits:
The page does not need to refresh upon submitting the form. The backend will respond with Json too, which is something I prefer as your app evolves.
Ajax means that you'll ask for less data, typically, to be returned to the frontend. It doesn't need to send back the entire template, but instead only the updated data or data you're choosing to return.
Ajax can be implemented with limited Javascript experience, but having JS experience allows you to do more with it.
I recommend getting to become familiar with implementing the above and then evolve your app to use Ajax, but that's me. I also prefer to use function based views instead of class based views when starting with Django, so I can really see what's happening before it's mostly hidden in class based views. But, Django is all about doing more with less, so learning CBV's out of the gate is not a bad thing at all.

Flask-wtf Recaptcha always returns "the response parameter is missing"

I am trying to implement Recaptcha 2 in a flask web form, however, my form always fails to validate when I have recaptcha enabled. To dig a little deeper I printed out the errors from recaptcha and I always get "the response parameter is missing". I have looked at the source code and it seems this error corresponds to "missing-input-response", however, I can see that the green tick appears when I click the button. Here are before and after images.
I have been searching the web for hours and can't find a reason why this is happening. Here is my flask code and my html.
flask:
import os
from flask import Flask, render_template
from flask_wtf import FlaskForm
from flask_wtf.recaptcha import RecaptchaField
from wtforms import StringField, SubmitField
from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms.validators import DataRequired
from dotenv import load_dotenv
app = Flask(__name__)
# load env variables
load_dotenv(dotenv_path='app.env')
SECRET_KEY = os.getenv('CSRF_TOKEN')
app.config['SECRET_KEY'] = SECRET_KEY
app.config['RECAPTCHA_PUBLIC_KEY'] = ''
app.config['RECAPTCHA_PRIVATE_KEY'] = ''
class InputForm(FlaskForm):
email = StringField('Email-Adresse', validators=[DataRequired()])
postcode = StringField('Postleitzahl', validators=[DataRequired()])
upload = FileField('Angebotsdatei', validators=[FileRequired(), FileAllowed(['png', 'jpg', 'pdf'], "Invalid file format")])
recaptcha = RecaptchaField()
submit = SubmitField('Starten')
#app.route('/', methods=['GET', 'POST'])
def index_2():
email = None
postcode = None
upload = None
form = InputForm()
# validate form
if form.validate_on_submit():
email = form.email.data
postcode = form.postcode.data
upload = form.upload.data
form.email.data = ''
form.postcode.data = ''
form.upload = ''
return render_template(
"index_2.html",
email = email,
postcode = postcode,
upload = upload,
form = form
)
if __name__ == "__main__":
app.run(debug=True, host='127.0.0.1', port=5000)
html:
{% extends 'base.html' %}
{% block content %}
<div class="container containerform pt-3">
<div class="container">
{% if email %}
<p class="text-center">Thanks for submitting to submitting the form!</p>
{% else %}
<form method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
{{ form.email.label(
class="form-label"
) }}
{{ form.email(
class="form-control"
) }}
{{ form.postcode.label(
class="form-label"
) }}
{{ form.postcode(
class="form-control"
) }}
{{ form.upload.label(
class="form-label"
) }}
{{ form.upload(
class="form-control"
) }}
</div>
<br>
<div class="recapture-box">
{% for error in form.recaptcha.errors %}
{{ error }}
{% endfor %}
{{ form.recaptcha }}
</div>
<br>
<div class="submit-button-form pb-4">
{{ form.submit(
class="btn btn-primary btn-form"
) }}
</div>
</form>
{% endif %}
</div>
{% endblock %}
I would appreciate any pointers here.
UPDATE
I still haven't managed to fix the problem, however, the issue lies in validation. While I can click and run recaptcha, I am not able to store the validation from google, and hence my validation_on_submit() returns False.
I recommend you to review the explanation on this web page.
You can use this instead of the if form.validate_on_submit():.
captcha_response = request.form['g-recaptcha-response']
if len(captcha_response) > 0:

Delete all task related to User

I am creating a todo app. I want to delete all task related to a particular user from Tasklist table after login but i got an error ' deleteAll() missing 1 required positional argument: 'id' ". How can i delete all task. Thanks in advance.
Model.py
from django.contrib.auth.models import User
from django.db import models
class TaskList(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
task = models.CharField(max_length=100, null=False, blank=False)
complete = models.BooleanField(default=False)
taskCreate = models.DateTimeField(auto_now=True)
Views.py
def deleteAll(request, id):
TaskList.objects.filter(id=request.user.tasklist_set.filter(id)).delete()
return redirect('todolist')
Html
{% extends 'home.html' %}
{% block content %}
{%if user.is_authenticated%}
<h2>Welcome {{ user.get_username| title }}</h2>
<button type="submit">Logout</button>
<form method="post">
{% csrf_token %}
<label for="task">
<input name="task" placeholder="add today's task" id="task" required>
<button type="submit">add</button>
</label>
</form>
<form method="get" >
{% csrf_token %}
<div>
{% for todo in task %}
{% if todo.complete %}
<li class="list-group-item todo-completed"><h6>{{ todo.task|linebreaks}} </h6></li>
{% else %}
<h6 class="list">{{ todo.task|linebreaks}}</h6>
{% endif %}
{% endfor %}
<button> Delete All </button>
<button> Delete Completed </button>
</div>
</form>
{% endif %}
{% endblock %}
First Change in your template Delete All button. Because your view requires a user id.
<button type="button"> Delete All </button>
Then simplify the deleteAll functions db query. You need to filter all tasks of current user. so you can just use user__id__exact = id:
def deleteAll(request, id):
TaskList.objects.filter(user__id__exact = id).delete()
return redirect('todolist')
Alternative Solution:
You don't need to send id. because you can get the current user id in request argument.
You just need to change the filter:
# view
#login_required
def deleteAll(request):
TaskList.objects.filter(user = request.user).delete()
return redirect('todolist')
# In template use:
<button type="button"> Delete All </button>
# urlpattern
path('delete/', deleteAll, name='delete')
I hope this will help you. Thank you. Happy Coding :)
This is a very common mistake that people do. :)
You have defined a function which takes request and an argument named as id as shown here:
def deleteAll(request, id):
TaskList.objects.filter(id=request.user.tasklist_set.filter(id)).delete()
return redirect('todolist')
But you have to pass an argument into deleteAll. To do that you have to type in the argument value.
You can do so by entering the value after {% url 'delete' %} in the line
Delete All
For example:
Delete All
I hope this helped, if not, feel free to comment and clarify the query.
I suppose "id" refers to the user's id, that is request.user.pk.
There are two issues, or two solutions:
About the Error
You have specified that your view expects the argument id, therefore you would need to add it to the URL in the template.
def deleteAll(request, id): # <<<< "id" is specified as argument
Delete All <!-- no "id" is specified -->
If you have an argument in a view, you need to specify it in the urls.py (which you obviously did) and subsequently whereever you need it reversed:
Delete All
You need the request template processor to have the request in your template context.
However, you actually don't need the ID because you know it in the view already. This is the "other solution".
About the ID
def deleteAll(request, id):
TaskList.objects.filter(id=request.user.tasklist_set.filter(id)).delete()
return redirect('todolist')
can be rewritten to
from django.contrib.auth.decorators import login_required
#login_required
def deleteAll(request):
TaskList.objects.filter(id=request.user.pk).delete()
return redirect('todolist')
You need to remove the id argument in the url in your urls.py, as well.

Why the wtforms not generating error when the Validators condtion is not met?

So I am working on a form which has two inputs and a check condition. If the check condition is not satisfied raise error. Else take the value in the db.
Given below is my forms.py script.
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, validators
# Define QuoteForm below
class QuoteForm(FlaskForm):
# qauthor = StringField("Quote Author",[validators.DataRequired(message="This field is required"),validators.Length(min =3, max =100,message = "Field must be between 3 and 100 characters long.")])
qauthor = StringField("Quote Author",validators=[validators.DataRequired(message="This field is required"),validators.Length(min =3, max =100,message = "Field must be between 3 and 200 characters long.")])
qstring = StringField("Quote",validators=[validators.DataRequired(message="This field is required"),validators.Length(min =3, max =200,message = "Field must be between 3 and 200 characters long.")])
submit = SubmitField(" Add Quote")
As you can see the min length should be greater than 3 for both fileds.
I am also capturing the error in the HTML page addquote.html
<body>
<h2>QuoteForm</h2>
<form action="", method="post">
<p>
{{form.qauthor.label}} : {{form.qauthor}}<br>
{%for error in form.qauthor.errors%}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</p>
<p>
{{form.qstring.label}} : {{form.qstring}}<br>
{%for error in form.qstring.errors%}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</p>
<p>{{form.submit}}</p>
</form>
</body>
I am calling the form in my flask function. Given below.
#app.route('/addquote/', methods=['GET', 'POST'])
def add_quote():
form = QuoteForm()
if form.is_submitted():
breakpoint()
result = request.form
qqauthor = result['qauthor']
qqstring = result['qstring']
add_tab = Quotes(quoteauthor=qqauthor,quotestring=qqstring)
db.session.add(add_tab)
db.session.commit()
return render_template("addquote_confirmation.html")
return render_template("addquote.html",form=form)
Now the inputs I passed are Quote Author to be: "AT" and Quote to be: "This is a beautiful world"
Quote Author to be: *"AT" and Quote to be: "RT"
I am not getting an error for both these cases where I have mentioned the condition in my forms.py minimum length to be 3.
Why the error is not coming or validation is not happening?
Dependency:
flask_wtf=='0.14.3'
flask=='1.0.2'
You need to call the form's validate() method. This is more conveniently done calling validate_on_submit(), which is a shortcut for checking the active request is a submission (POST, PUT, PATCH, or DELETE) and the form's data is valid (see docs).
#app.route('/addquote/', methods=['GET', 'POST'])
def add_quote():
form = QuoteForm()
if form.validate_on_submit():
breakpoint()
add_tab = Quotes(quoteauthor=form.qqauthor.data, quotestring=form.qqstring.data)
db.session.add(add_tab)
db.session.commit()
return render_template("addquote_confirmation.html")
return render_template("addquote.html",form=form)
Simple full example code below, no database just print out form's data.
app.py
from flask import Flask, render_template, url_for
from flask_wtf import FlaskForm
from markupsafe import Markup
from wtforms import StringField, SubmitField, validators
class QuoteForm(FlaskForm):
author = StringField(
"Quote Author",
validators=[
validators.DataRequired(message="This field is required"),
validators.Length(min=3, max=100, message="Field must be between 3 and 200 characters long.")
]
)
quote = StringField(
"Quote",
validators=[
validators.DataRequired(message="This field is required"),
validators.Length(min=3, max=200, message="Field must be between 3 and 200 characters long.")
]
)
submit = SubmitField(" Add Quote")
app = Flask(__name__)
app.config['SECRET_KEY'] = 'MY SECRET KEY'
#app.route('/', methods=['GET'])
def index():
_quote_url = url_for('add_quote')
return Markup(f'<a href="{_quote_url}">Add Quote</a')
#app.route('/add-quote/', methods=['GET', 'POST'])
def add_quote():
form = QuoteForm()
if form.validate_on_submit():
print(f'Author: {form.author.data}')
print(f'Quote: {form.quote.data}')
return "Quote form is validated"
return render_template("add-quote.html", form=form)
if __name__ == '__main__':
app.run()
add-quote.html
<body>
<h2>QuoteForm</h2>
<form action="" method="post">
{{ form.csrf_token }}
<p>
{% for error in form.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</p>
<p>
{{ form.author.label }} : {{ form.author }}<br>
{% for error in form.author.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</p>
<p>
{{ form.quote.label }} : {{ form.quote }}<br>
{% for error in form.quote.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</p>
<p>{{ form.submit }}</p>
</form>
</body>

Can't delete an object in delete_view, in Django

I'm trying to create a delete view for my product form, my problem is with the submit button, it doesn't delete my object, it just redirects me to previous page.
This a Windows 7 machine, Python 3.7.1, Anaconda 2018.12
I've tried eliminating the line: if request.method == "POST"
And without that line the object is eliminated, so I think the problem is with the if statement or the POST method, but couldn't solve it
views.py:
from django.shortcuts import render, get_object_or_404, redirect
from .forms import ProductForm, RawProductForm
from .models import Product
def product_delete_view(request, id):
obj = get_object_or_404(Product, id=id)
if request.method == "POST":
obj.delete()
return redirect('../')
context = {
"object": obj
}
return render(request, "products\\product_delete.html", context)
product_delete.html:
{% extends 'base.html' %}
{% block try %}
<form action='.' method='POST'>{% csrf_token %}
<h1>Do you want to delete the product "{{ object.title }}"?</h1>
<p>
<input type="submit" value="Yes" />
Cancel
</p>
</form>
{% endblock %}
The idea is that when I click "Yes", the object disappears, but instead I am redirected to the page of the actual object I wanted to delete

Resources