I'm very new to Python & Flask and as of rn I'm trying to build a website which generates random colour with a press of a button, but the idea is that you can also save those random colours on your profile and later on manage them (delete, move around, etc).
The problem I'm facing now is on my "randomise" page I can't seem to figure out how to implement a save button without rendering the whole page, randomising the colour again and therefore "saving" wrong colour into the database. Here's what I have so far:
app.py
#app.route("/l_randomize", methods=["GET", "POST"])
def l_randomize():
if request.method == "GET":
return render_template("l_randomize.html")
else:
color = '#{:02x}{:02x}{:02x}'.format(*random.sample(range(256), 3))
if 'favourite' in request.form:
db.execute("INSERT INTO fav (user_id, color) VALUES (:user_id, :color)", user_id=session["user_id"], color=color)
return render_template("l_randomized.html",color=color)
app.html
{%block body%}
<div class="container">
<h3>Your color is</h3>
<div id="l_randomized" style="background-color: {{ color }};">
<form method="post" action="/l_randomize">
<input type="submit" name = "randomize" value="Randomize" >
<input type="submit" name = "favourite" value="Favourite" >
</form>
</div>
</div>
{%endblock%}
I kind of understand the flaw in code that I already wrote and why it's rendering the template again, but I just need a hint or where to look at, so after that if statement it wouldn't refresh the page or go further. Any suggestions, comments and hints on how to implement this better are highly appreciated!
This is a lot trickier than you imagine. You will need to use some kind of asynchronous update. In this case, without learning React et al. it's probably easier to use JQuery. Essentially, you want to intercept your form submission to save the favourite colour (note, I'm in the UK so my spelling is probably inconsistent in the below example, sorry) and then only change a particular element in the DOM.
There's a lot of code below, but it makes a self-contained example - just run the script and go to 127.0.0.1:5000. The major changes:
I've switched to the Flask-SQLAlchemy ORM (Object-relational Mapper). This was partly just because it's easier for me to set up a minimal example, but it's also usually easier in app development. So, I've made two classes that will create the database tables for you.
I've implemented two JavaScript functions that use AJAX. One is attached to a button click, the other intercepts a form submission.
I've split the Flask routes up so that different events can be handled separately
Essentially:
If we don't know the user's favourite colour, we'll just give a white background.
If they select a new colour at random, we'll update the background colour only. We'll also update a hidden field in the form that saves their preference, in case that's the one they want to keep.
If they click "favourite", we'll write that to the database. If you close the page and open it again after this point, you'll notice that we load their previous favourite
Don't use render_template_string in your actual code, it just allowed me to put the template into a single script.
This is for illustrative purposes only. The intention is to connect dots on the general setup; it's missing things like form validation, amongst other things.
from flask import Flask, render_template_string, session, request
from flask_sqlalchemy import SQLAlchemy
import random
# =============================================================================
# BASIC APP INIT
# =============================================================================
db = SQLAlchemy()
app = Flask('__main__')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SECRET_KEY'] = 'change_me'
db.init_app(app)
# =============================================================================
# DATABASE MODELS
# =============================================================================
class Users(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String)
class Fav(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
color = db.Column(db.String)
with app.app_context():
db.create_all()
user = Users(username='testing') # Create a fake user
db.session.add(user)
db.session.commit()
# =============================================================================
# TEMPLATE
# =============================================================================
l_randomize_html = """
{%block body%}
<div class="container">
<h3>Your color is</h3>
<div id="l_randomized" style="background-color: {{ color }};">
<button onclick="changeColor()">Change</button>
<form method="post" action="{{ url_for('l_save_favourite') }}" id="favColorForm">
<input type="hidden" name="currentColor" id="currentColor" value="{{ color }}">
<input type="submit" name="favourite" value="Favourite" >
</form>
<div id="colorSaveResp"></div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
function changeColor() {
$.ajax({
type: 'POST',
url: "{{ url_for('change_color') }}",
success: function(color) {
$("#currentColor").val(color);
$("#l_randomized").css("backgroundColor", color);
}
});
}
</script>
<script>
$("#favColorForm").submit(function(e) {
e.preventDefault();
var form = $(this);
var url = form.attr('action');
$.ajax({
type: "POST",
url: url,
data: form.serialize(),
context: form,
success: function(resp) {
$("#dynamic_div").html(resp);
}
});
});
</script>
{%endblock%}
"""
# =============================================================================
# ROUTES
# =============================================================================
#app.route('/', methods=['GET'])
def l_randomize():
session['user_id'] = 1 # Add to the session
# See whether we have a favourite colour
favourite = Fav.query.filter_by(user_id=session['user_id']).first()
if favourite:
color = favourite.color
else:
# They don't, give a default
color = '#FFF'
return render_template_string(l_randomize_html, color=color)
#app.route('/change_color', methods=['POST'])
def change_color():
return '#{:02x}{:02x}{:02x}'.format(*random.sample(range(256), 3))
#app.route('/save_color', methods=['POST'])
def l_save_favourite():
data = request.form.to_dict()
favourite = Fav.query.filter_by(user_id=session['user_id']).first()
if not favourite:
# We don't know this user's favourite colour yet, add it
favourite = Fav(user_id=session['user_id'],
color=data['currentColor'])
db.session.add(favourite)
db.session.commit()
else:
# We'll update their favourite
favourite.color = data['currentColor']
db.session.commit()
return "Saved"
if __name__ == '__main__':
app.run(debug=True)
There's a lot there, and still plenty more that could be implemented, but it's already getting too long. Hopefully you can run it and work through the individual points one-by-one.
Related
What is the preferred way of pre-populating database (Model) objects in a Django app? I am inclined to try to script POSTing data to the relevant endpoints, but am being stymied by the CSRF protection.
This is not part of the testing framework, this is for setting up demonstration and training instances in a beta testing or production environment.
As a notional example, I'd like to populate the the "Player" database
with three entries: Alice(sender), Bob(reciever) and Charlie(eavesdropper), and I'd like to script/automate the process of creating these entries after deploying and starting the application.
I already have a form based mechanism for creating new Players. By visiting /create via a browser, there is a form that allows a person to type in the name, e.g. "Bob" and a role, e.g. "reciever", and submit the form to create the new Player instance.
Thus it makes sense to me to try to try to use the existing web API for this: e.g. make calls like
requests.post('0.0.0.0:8000/create', data={'name':'Bob', 'role':'reciever'}) in the script that pre-populates the database. However doing this results in 403 errors due to the CSRF tokens, which I don't want to disable. This problem also occurs if I just try to use a requests.Session to try to maintain the cookies between calls.
One viable solution would involve effectively managing the cookies involved to allow for posting data. However, I'm open to different ways to allow for creating model instances for initial system configuration.
Relevant code snippets:
def create(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = PlayerForm(request.POST)
# check whether it's valid:
if form.is_valid():
data = form.cleaned_data
s = models.Player()
s.name = data['name']
s.role = data['role']
s.save()
msg = "TODO: make a senisble return message"
return HttpResponse(msg)
else:
msg = "TODO: make invalid sources message"
return HttpResponse(msg)
# if a GET (or any other method) we'll create a blank form
else:
form = PlayerForm()
return render(request, 'player/create.html', {'target':'/create', 'form': form})
class Player(Model):
name = models.CharField(max_length=168)
role = models.CharField(max_length=64)
class PlayerForm(forms.Form):
name = forms.CharField(label='Name:', max_length=168)
role = forms.CharField(label='Role:', max_length=64)
Note that the 'target':'/create' is the target for the form's submit action, i.e. when the user hits "Submit" the data from the form are posted to this endpoint (which then hits the if request.method == 'POST' branch to create and save the new instance.
The form is just
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="">
<style>
</style>
<script src=""></script>
<body>
<form action="{{target}}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
</body>
</html>
I'm trying to create a login form that checks the existence of an account via a username and a password i've tried some stuff but don't seem to be able to get it to work
<form id="signupform" action="{{url_for ('login') }}" method="post">
<div class="input-group input-group-lg">
<input type="text" class="form-control" placeholder="Username" aria-describedby="sizing-addon1" name="username" >
</div>
<div class="input-group input-group-lg">
<input type="password" class="form-control" placeholder="Password (8 chars min)" aria-describedby="sizing-addon1" name="password" " >
</div>
<small id="emailHelp" class="form-text text-muted">
We'll never share your info with anyone else.<br>
No account? sign up here!
</small>
<button type="button" class="btn btn-primary">Login</button>
</form>
this is the the form ^^
import os
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))
#app.route("/")
def index():
return render_template("login.html")
#app.route("/home" , methods=["POST"])
def login():
# request username and password
username=request.form.get("username")
password=request.form.get("password")
#check if account exists
account= db.execute("SELECT * FROM users WHERE username = :username AND password = :password ",
{"username":username , "password":password }).fetchone()
if account is None:
return render_template("loginerror.html")
else:
return render_template("homepage.html")
and this is the flask app^^
the database is also linked correctly so idk
i'm so lost, any help is really appreciated
The problem is here <button type="button" class="btn btn-primary">Login</button>
. From MDN button element doc:
type
The default behavior of the button. Possible values are:
submit: The button submits the form data to the server. This is the default if the attribute is not specified for buttons
associated with a , or if the attribute is an empty or invalid
value.
reset: The button resets all the controls to their initial values, like . (This behavior tends to annoy
users.)
button: The button has no default behavior, and does nothing when pressed by default. It can have client-side scripts listen to the
element's events, which are triggered when the events occur.
You didn't specified the error you got (and you should have to), but I'm pretty sure that the form you made is correct (there's a trailling quotation mark at the end of your password field)
There is no methods=["POST","GET"]) in your #app.route("/home" , methods=["POST"]) snippet. Currently, you code is posting his result but cannot get the page back. When you are using post, you are sending information to the server, but it's NOT a "GET" request which mean it won't change your URL or search for a new one. You should implements a function that check if you are logged in and if so, do a "window.location.href = myserver.com/home" (in js).
Also, I suggest you two things. First, use a already made login engine like flask-login you will save time. Also, if you don't need to access the model by another procces simultaneously, use the flask-sqlalchemy library. It's a nice wrapper that reduce the code you have to write
So I have this simple polling application that continuously create polls that people can vote +1 or -1 on. However, since this website doesn't require user logins people can vote multiple of times on every poll.
<form name="poll" id='{{ item.id }}' method="post" action='/poll'>
<label class='lab-pos'>
<input type="radio" name="points" id='whine-pos' value=1>
<img class='img-pos'src="/static/item-pos.png">
</label>
<label class='lab-neg'>
<input type="radio" name="points" id='whine-neg' value=-1>
<img class='img-neg'src="/static/item-neg.png">
</label>
</form>
I am sending the submit with a javascript function to my sqlite3 database, there is no submit button but the script.
<script type="text/javascript">
$(document).ready(function() {
$('input[type=radio]').on('change', function() {
$(this).closest("form").submit();
});
});
</script>
Is it possible to save the the votes in cookies with flask so when a person visits the site again they will not be able to vote again but only change the vote? (if they want). I know they can just clear cookies and they can vote again but that doesn't really bothers me in this phase.
My database structure in SQLAlchemy looks like this at the moment, and my view in flask like below
class pollingresult(db.Model):
__tablename__ = "pollingresult"
id = db.Column('id', db.Integer, primary_key=True)
poll = db.Column('poll', db.Integer)
cookie = db.Column('cookie', db.String(255))
feed_id = db.Column('feed_id', db.Integer)
def __init__(self, poll):
self.poll = poll
and my view in flask like below
#app.route('/poll', methods=['GET', 'POST'])
def poll():
polltodb = pollingresult(request.form['points'])
session['points_session'] = request.form['points']
db.session.add(polltodb)
db.session.commit()
return ('',204)
I have played around with the session but it seems that on refresh the polls are still getting 'rested' so people can vote again.
edit 161202:
So, I am still struggling with this task, I can save the session['points_session'] to a session, but I need to save the session more like a dict, where the dict has id = item.id and points = points so I can prefill the forms with javascript 'if id = 1 and point = 1' prefill form with id = 1. I also need to prevent the form to be submitted again based on the session, so I guess i will have to create a somewhat dummy token for some kind of session key?
edit 161207:
So I would like to send the poll_id along with the form submit so I thought I could use an ajax post request, however, this throws the error "Failed to decode JSON object: No JSON object could be decoded".
<script type="text/javascript">
$(document).ready(function() {
$('input[type=radio]').on('change', function() {
$(this).closest("form").submit();
var poll_id = $(this).closest("div").attr("id");
var data = {poll_id};
console.log(JSON.stringify(data));
$.post('/poll', {
data: JSON.stringify(data),
}, function(data) {
console.log(data);
});
});
});
</script>
Along with the new poll route:
#app.route('/poll', methods=['GET', 'POST'])
def poll():
polltodb = pollingresult(request.form['points'])
session['points_session'] = request.form['points']
db.session.add(polltodb)
db.session.commit()
data = request.get_json(force=True)
print data
return ('',204)
This will later be inserted into the DB along with some kind of session key.
Instead of saving the polls the user has voted on in their session, simply attach a "poll_user_id" to the session so you can keep track of the user and their votes in the database.
from uuid import uuid1 as uuid
if "poll_user_id" not in session:
session["poll_user_id"] = uuid()
Here's some psuedo code as I'm not familar with Flask and their database engine.
old_vote = query(poll_user_id=session["poll_user_id"], poll=poll_id)
if not old_vote:
insert(poll_user_id=session["poll_user_id"], poll=poll_id, choice=form.choice)
else:
update(poll_user_id=session["poll_user_id"], poll=poll_id, choice=form.choice)
Now, when a user votes, either new or as an update, it checks if a vote already exists with the same "poll_user_id" value, if it does you'll do an update. If it doesn't do an insert.
i would suggest you don't use cookies at all. I use browser fingerprinting to identify users. The advantage is that you can id them even if they open the page in incognito again and again (which would clear all your cookies / sessions)
https://clientjs.org/#Fingerprints
You would be better off generating a (admittedly semi-unique) fingerprint and tracking duplicate user's this way.
I have been using this with good success and i have a link where the user can flag that he has not completed the vote/action and i have not
Django is creating two records in MySQL instead of one.
I call a function via a link
<button class="btn btn-primary">Thats Me!</button>
The function itself is very straight forward. I take the variable via a request.get, create a new object, and finally save it. However, when I check the DB there are two records, not just one.
def markpresent(request, id):
new_attendance = attendance(clientid_id = id, date = datetime.datetime.now(), camp = 3)
new_attendance.save()
return render(request, 'clienttracker/markpresent.html', {
'client': id,
})
Model
class attendance(models.Model):
clientid = models.ForeignKey(newclients, on_delete=models.CASCADE)
date = models.DateField()
camp = models.CharField(max_length = 3, default=0)
Any help and direction would be appreciated.
SOLUTION BASED ON ANSWERS
<form action="{% url 'markpresent' %}" method="post">
{% csrf_token %}
<button type="submit" name="client" value="{{ c.id }}" class="btn btn-primary">Thats Me!</button>
</form>
def markpresent(request):
id = request.POST.get('client')
new_attendance = attendance(clientid_id = id, date = datetime.datetime.now(), camp = 3)
new_attendance.save()
return render(request, 'clienttracker/markpresent.html', {
'client': id,
})
Thanks
You should avoid modifying your database on a GET request. Various things could cause a duplicate request - for instance, a request for an asset or favicon being caught by the same URL pattern and routed to the same view - so you should always require a POST before adding an entry in your database.
Are you using Google Chrome? If yes, then Google Chrome has something like lazy loading. So if you will type your URL in Google Chrome, it will try to load site behind the scenes and if you will tap enter, then you will get this URL again. The same is when you're trying to go over anchor with a link. It's an edge case, but it happens. Try with firefox or disable that function.
I'm writing a Django admin action to mass e-mail contacts. The action is defined as follows:
def email_selected(self,request,queryset):
rep_list = []
for each in queryset:
reps = CorporatePerson.objects.filter(company_id = Company.objects.get(name=each.name))
contact_reps = reps.filter(is_contact=True)
for rep in contact_reps:
rep_list.append(rep)
return email_form(request,queryset,rep_list)
email_form exists as a view and fills a template with this code:
def email_form(request,queryset,rep_list):
if request.method == 'POST':
form = EmailForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
send_mail(
cd['subject'],
cd['message'],
cd.get('email','noreply#localboast'),['redacted#email.com'],
)
return HttpResponseRedirect('thanks')
else:
form = EmailForm()
return render_to_response('corpware/admin/email-form.html',{'form':form,})
and the template exists as follows:
<body>
<form action="/process_mail/" method="post">
<table>
{{ form.as_table }}
</table>
<input type = "submit" value = "Submit">
</form>
</body>
/process_mail/ is hardlinked to another view in urls.py - which is a problem. I'd really like it so that I don't have to use <form action="/process_mail/" method="post"> but unfortunately I can't seem to POST the user inputs to the view handler without the admin interface for the model being reloaded in it's place (When I hit the submit button with , the administration interface appears, which I don't want.)
Is there a way that I could make the form POST to itself (<form action="" method="post">) so that I can handle inputs received in email_form? Trying to handle inputs with extraneous URLs and unneeded functions bothers me, as I'm hardcoding URLs to work with the code.
You can use django's inbuilt url tag to avoid hardcoding links. see...
http://docs.djangoproject.com/en/dev/ref/templates/builtins/#url
Chances are you'd be better off setting up a mass mailer to be triggered off by a cron job rather than on the post.
Check out the answer I posted here
Django scheduled jobs
Also if you insist on triggering the email_send function on a view update perhaps look at
http://docs.djangoproject.com/en/dev/topics/signals/