Get a string variable from views to urls in Django - python-3.x

I am a beginner at Python and Django, I am trying to solve a problem where when a user searches if that search is valid, I want to get that search = var_X, in my URL like:
www.website.com/search/var_X
or something like,
www.website.com/search/<df.item>
Views.py
def views_search(request):
temp_dict = {}
if request.method == 'POST':
var_X = request.POST['var_X']
try:
df = wsc.Summary(temp_dict, var_X)
except Exception as e:
df = "ERROR"
return render(request,'search.html', {'df' : df})
else:
return render(request,'home.html', {'var_X' : "Not Found"})
Urls
from django.urls import path
from . import views
from requests import *
urlpatterns = [
path('search/<var_X>', views.views_search, name="url-search"),
]
HTML
<form action="{% url 'url-search' var_X %}"class="d-flex" method="POST">
{% csrf_token %}
<input class="form-control me-2" type="search" placeholder="Enter String" aria-label="Search" name="var_X">
<button class="btn btn-outline-secondary" type="submit">Search</button>
</form>

When Django renders a template it converts all template tags into html.
You won't be able to set the value of var_X in {% url 'url-search' var_X %} using python code on the client side.
My suggestion is to use javascript to set the form's action using the onclick html attribute.
<button ... onclick="this.form.action = {% url 'url-search' %} + document.getElementById('id-of-input-element').value;>"
This also means removing the action attribute from the input element.
From
<form action="{% url 'url-search' var_X %}"class="d-flex" method="POST">
to
<form class="d-flex" method="POST">
Because I don't know what your code does (because I don't know what df is. Use the following as only an example of what you could achieve.
(In my code snippets entering a search goes back to the same page to display results.)
urls.py
urlpatterns = [
path('search/', views.views_search, name="url-search"),
path('search/<str:var_X>', views.views_search)
]
The route 'search/' is needed in order to make {% url 'url-search' %} work on first page load.
The route 'search/<str:var_X>' matches any value after search/ into a variable called var_X as a string in the view function.
views.py
def views_search(request, var_X=None):
if request.method == 'POST':
pass
# do the stuff you need to do with the form..
return render(
request,
"views_search.html",
{
'var_X': var_X
}
)
The parameter var_X is None by default if the url doesn't have anything after search/ else it will have the search string.
views_search.html (wtv your html template is called)
<form class="d-flex" method="POST">
{% csrf_token %}
<input id="search_input" class="form-control me-2" type="search" placeholder="Enter String" aria-label="Search" name="var_X" value="">
<button class="btn btn-outline-secondary" type="submit" onclick="this.form.action = {% url 'url-search' %} + document.getElementById('search_input').value;">Search</button>
</form>
{% if var_X %}
<p>Showing results for '{{ var_X }}'.</p>
{% endif %}
with the above html you'll get this output:
Before pressing search button
After pressing search button
This also has the benefit that if you type something directly into the url it will search that too.
Hopefully this helps steer you in the right direction for what you're looking for.

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>

Kept getting werkzeug.routing.BuildError

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.

Resources