Django Save creating two records instead of one - python

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.

Related

How to populate Django databases

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>

How do I count hits of each element in a list in Django?

So I have a page where multiple articles are listed. (To be precise, TITLES that are outlinked to the articles written on Notion template.) And I want to have a filed in my model that counts the number of clicks of each article. (I don't want to use django-hitcount library).
Let me first show you my code. models.py
class Article(models.Model):
number = models.IntegerField(primary_key=True)
title = models.CharField(max_length=20, default="")
url = models.URLField(max_length=100, default="")
hits = models.IntegerField(default=0)
template
...
<div class="col text-center">
{% for q in allArticles %}
<h2 id={{q.number}}>{{q.title}}</h2>
{% endfor %}
</div>
...
I was thinking of using onclick() event in JavaScript, but then passing data from JavaScript to Django seemed too challenging to me at the moment.
I'd very much appreciate your help. Thanks.
Well, when you dont take up new challenges you stop learning !
The onclick method looks like the best imo, lets see what others suggest.
honestly, using JS and AJAX to communicate with your django server might be dauting at first but it is quite easy really.
if you know how to create a function in your views.py and know a bit of JS, it's just like any other classic functionnality.
Set up your urls.py for the view function that will add a click to the counter:
path('ajax/add_click', views.add_click name="add_click"),
Then, create your view function (pseudo code):
def add_click(request):
# retrieve the article
article_id = request.GET.get("articleId", None)
# then retrieve the object in database, add 1 to the counter save and return a response
Now the "complicated" part, the ajax request:
function add_one_click(articleId) {
$.ajax({
type: "GET",
url: '/ajax/add_click', // you also can use {% url "app_name:add_click" %}
data: {
'articleId': articleId,
},
success: function() {
console.log("hit added to article");
}
});
}
You need to add JS and Ajax lib to your html template for it to works.
Also you need to pass in the onclick attribute the name of the function + the id of the article
onclick="add_one_click({{article.id}})"
One more thing, this type of view, if not protected can lead to get false results.
Instead of having q.url have a new URL(/article_count?id=q.id) which you will define on your Django project
def article_count(req):
_id = req.GET.get('id', '')
# Query Aritcle and get object
q = Article.objects.get(id=_id)
# update the fields for clicks
q.hits += 1
q.save()
# redirect the page
return redirect(q.url)
Edit:
Create a new url that would handle your article click, lets say-
path('article/clicked/<article_number>', views.click_handler, name='click_counter')
Now, in your template use this url for all the article
<div class="col text-center">
{% for q in allArticles %}
<h2 id={{q.number}}>{{q.title}}</h2>
{% endfor %}
</div>
and in your views.py create a new controller
def click_handler(request, article_number):
article = Article.objects.get(number=article_number)
article.hits += 1
article.save()
# now redirect user to the outer link
return redirect(article.url)

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

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