How to populate Django databases - python

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>

Related

Thwarting form double-submission through server side tokens (Django)

I am trying to implement a server-side check to prevent users from double-submitting my forms (Django web app).
One technique I'm trying is:
1) When the form is created, save a unique ID in the session, plus pass the unique ID value into the template as well.
2) When the form is submitted, pop the unique ID from the session, and compare it to the same unique ID retrieved from the form.
3) If the values are the same, allow processing, otherwise not.
These SO answers contributed in me formulating this.
Here's a quick look at my generalized code:
def my_view(request):
if request.method == 'POST':
secret_key_from_form = request.POST.get('sk','0')
secret_key_from_session = request.session.pop('secret_key','1')
if secret_key_from_form != secret_key_from_session:
return render(request,"404.html",{})
else:
# process the form normally
form = MyForm(request.POST,request.FILES)
if form.is_valid():
# do something
else:
# do something else
else:
f = MyForm()
secret_key = uuid.uuid4()
request.session["secret_key"] = secret_key
request.session.modified = True
return render(request,"my_form.html",{'form':f,'sk':secret_key})
And here's a sample template:
<form action="{% url 'my_view' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<input type="hidden" name="sk" value="{{ sk }}">
{{ form.my_data }}
<button type="submit">OK</button>
</form>
This set up has failed to stop double-submission.
I.e., one can go on a click frenzy and still end up submitting tons of copies. Moreover, if I print secret_key_from_form and secret_key_from_session, I see them being printed multiple times, even though secret_key_from_session should have popped after the first attempt.
What doesn't this work? And how do I fix it?
UPDATE: when I use redis cache to save the value of the special key, this arrangement works perfectly. Therefore, it seems the culprit is me being unable to update request.session values (even with trying request.session.modified=True). I'm open to suggestions vis-a-vis what could be going wrong.
Note that this question specifically deals with a server-side solution to double-submission - my JS measures are separate and exclusive to this question.
You might just need request.session.modified = True. If you want to make sure that the session is deleting you can use del too.
secret_key_from_session = request.session.get('secret_key','1')
del request.session['secret_key']
request.session.modified = True
I couldn't figure out what caused the problem, but via substituting Redis cache for every request.session call, I was able to get my desired results. I'm open to suggestions.

flask save polls in cookie

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 Sessions not Working

I have built an application that shows users their storage usage and quotas on a system. Since there are many users, sifting through their storage allocations can be tedious so I want to give admins the option of acting as that user. The application decides which user is accessing the application based on an employee ID received via a secure badg, the variable (EMPLOYEE_ID) is stored in the request.META dictionary.
Ideally, I want the admins to be able to override this employee ID with another user's ID by posting it in a form. The form works and then serves the storage_home.html page as the employee the admin wishes to act as via a POST request, but when I or another admin clicks and does a GET for the quotas, the request.session dictionary is empty!
EMPLOYEE_ID is the original employee id of the admin
SIM_EMPLOYEE_ID is the employee the admin wishes to act as
I wonder if it's the way I'm linking to the quotas view in the storage_home.html template? Not sure.
Here is my code, I believe you should only need views, and the template that calls the quotas view function to see what the issue is since the request.sessions dictionary does have the SIM_EMPLOYEE_ID variable after the post that serves storage_home.html. I've omitted some variables from the views that are used in the template, but they work just fine, didn't want to clutter the code too much.
The sim_user function is called when the form is submitted. This then just recalls the storage function and right now successfully displays what I want it to, it's the GET request subsequently that fail to keep the session. I also have the following set in my settings:
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_DOMAIN = '.mydomain.com'
SESSION_SAVE_EVERY_REQUEST = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
views.py
def home(request):
"""Redirect requests at root url to /storage"""
return HttpResponseRedirect('/storage/')
def storage(request):
"""Return the home template."""
context = {}
context.update(csrf(request))
empid = request.session.get('SIM_EMPLOYEE_ID')
if not empid:
empid = request.META.get('EMPLOYEE_ID')
if functions.is_admin(empid):
form = UserForm()
context['form'] = form
template = loader.get_template('storage_admin.html')
else:
template = loader.get_template('storage_home.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
def sim_user(request):
context = {}
context.update(csrf(request))
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
empid = form.cleaned_data['empid']
request.session['SIM_EMPLOYEE_ID'] = empid
request.session.modified = True
return storage(request)
template = loader.get_template('deny.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
def quotas(request, sitename):
"""Return quota page depending on the
id of the employee. If employee is an
administrator, show all the quota information
for all users/projects. If employee is a user
of the sitename, show them user specific quota information.
Otherwise, deny access and display a custom template."""
context = {}
site = sitename.capitalize()
# EMPLOYEE_ID is in the Http Request's META information
empid = request.session.get('SIM_EMPLOYEE_ID')
if not empid:
empid = request.META.get('EMPLOYEE_ID')
if not empid:
template = loader.get_template('deny.html')
return HttpResponse(template.render(RequestContext(request, context)))
if functions.is_admin(empid):
template = loader.get_template('all_quotas.html')
else:
template = loader.get_template('personal_quotas.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
storage_home.html
{% extends 'base.html' %}
{% block title %}Storage Utilization{% endblock %}
{% block content %}
<h1 id="header"><b>Storage Utilization</b></h1>
<p></p>
<table id="storage_table" cellspacing="15">
<tbody>
{% for site in sites %}
{% url "su.views.quotas" as quota %}
<tr>
<td><img src="/static/images/{{ site }}.png"></td>
</tr>
{% endfor %}
</tbody>
</table>
<br></br>
{% endblock %}
Thanks for any help, please let me know if you need more explanation, code, or simplification.
Turns out removing SESSION_COOKIE_SECURE = True fixed the issue. This is my fault for not forgetting that my dev environment uses http and prod https. I actually have separate settings files, but failed to use them properly when I went back to test this new feature. I believe setting the SESSION_COOKIE_SECURE to True when using https should work once I test the production server.
Django provided session stopped working for me for some reason. I made my own it's really easy:
models.py
class CustomSession(models.Model):
uid = models.CharField(max_length=256)
def __str__(self):
return self.uid
How to work with CustomSession
from oauth.models import CustomSession
session = CustomSession.objects # get a list of session objects
new_user = CustomSession(uid=<UID>) # save a user to the session (by uid)
session.get(id=<ID>).uid # get user id
session.get(id=<ID>).delete() # delete user from session (logout)
session.all().delete() # delete all user data in session

What is the cause of the Bad Request Error when submitting form in Flask application?

After reading many similar sounding problems and the relevant Flask docs, I cannot seem to figure out what is generating the following error upon submitting a form:
400 Bad Request
The browser (or proxy) sent a request that this server could not understand.
While the form always displays properly, the bad request happens when I submit an HTML form that ties to either of these functions:
#app.route('/app/business', methods=['GET', 'POST'])
def apply_business():
if request.method == 'POST':
new_account = Business(name=request.form['name_field'], email=request.form['email_field'], account_type="business",
q1=request.form['q1_field'], q2=request.form['q2_field'], q3=request.form['q3_field'], q4=request.form['q4_field'],
q5=request.form['q5_field'], q6=request.form['q6_field'], q7=request.form['q7_field'],
account_status="pending", time=datetime.datetime.utcnow())
db.session.add(new_account)
db.session.commit()
session['name'] = request.form['name_field']
return redirect(url_for('success'))
return render_template('application.html', accounttype="business")
#app.route('/app/student', methods=['GET', 'POST'])
def apply_student():
if request.method == 'POST':
new_account = Student(name=request.form['name_field'], email=request.form['email_field'], account_type="student",
q1=request.form['q1_field'], q2=request.form['q2_field'], q3=request.form['q3_field'], q4=request.form['q4_field'],
q5=request.form['q5_field'], q6=request.form['q6_field'], q7=request.form['q7_field'], q8=request.form['q8_field'],
q9=request.form['q9_field'], q10=request.form['q10_field'],
account_status="pending", time=datetime.datetime.utcnow())
db.session.add(new_account)
db.session.commit()
session['name'] = request.form['name_field']
return redirect(url_for('success'))
return render_template('application.html', accounttype="student")
The relevant part of HTML is
<html>
<head>
<title>apply</title>
</head>
<body>
{% if accounttype=="business" %}
<form action="{{ url_for('apply_business') }}" method=post class="application_form">
{% elif accounttype=="student" %}
<form action="{{ url_for('apply_student') }}" method=post class="application_form">
{% endif %}
<p>Full Name:</p>
<input name="name_field" placeholder="First and Last">
<p>Email Address:</p>
<input name="email_field" placeholder="your#email.com">
...
The problem for most people was not calling GET or POST, but I am doing just that in both functions, and I double checked to make sure I imported everything necessary, such as from flask import request. I also queried the database and confirmed that the additions from the form weren't added.
In the Flask app, I was requesting form fields that were labeled slightly different in the HTML form. Keeping the names consistent is a must. More can be read at this question Form sending error, Flask
The solution was simple and uncovered in the comments. As addressed in this question, Form sending error, Flask, and pointed out by Sean Vieira,
...the issue is that Flask raises an HTTP error when it fails to find a
key in the args and form dictionaries. What Flask assumes by default
is that if you are asking for a particular key and it's not there then
something got left out of the request and the entire request is
invalid.
In other words, if only one form element that you request in Python cannot be found in HTML, then the POST request is not valid and the error appears, in my case without any irregularities in the traceback. For me, it was a lack of consistency with spelling: in the HTML, I labeled various form inputs
<input name="question1_field" placeholder="question one">
while in Python, when there was a POST called, I grab a nonexistent form with
request.form['question1']
whereas, to be consistent with my HTML form names, it needed to be
request.form['question1_field']

POSTing forms in Django's admin interface

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/

Categories