POSTing forms in Django's admin interface - python

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/

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 to redirect to another url after sending a POST request to admin:auth_user_password_change in Django?

I'm using the built in django admin site for the application's admin site in a sense that POST requests are built identically to its equivalent admin site, and sent to the same urls. So it's like the application admin site is just a wrapper for the admin site.
My problem is, I don't know how to redirect back to url where it was originally posted. Currently it redirects to the Change User admin page. I'd like it to redirect back to the page.
Here's my change-password-template.html:
<div>
<form action="{% url 'admin:auth_user_password_change' user.pk %}" method="post" id="change-password-form">
{% csrf_token %}
# ... here goes the password fields
<input type="submit" value="Change password">
</div>
<input type="hidden" name="next" value="{% url 'change_password' id=user.pk%}"> # my attempt at trying to redirect back to the original webpage
</div>
</form>
</div>
So, it does correctly post to admin/auth/user/{{user.pk}}/password/ but instead of redirecting back to the that page it instead redirects to: admin/auth/user/{{user.pk}}/change/. How can I make it do so?
The wrong way to do it...
You would need to overwrite the user_change_password method in your User admin, since that is where the redirect happens:
# contrib.auth.admin.py
def user_change_password(self, request, id, form_url=''):
...
if request.method == 'POST':
form = self.change_password_form(user, request.POST)
if form.is_valid():
...
return HttpResponseRedirect(
reverse(
'%s:%s_%s_change' % (
self.admin_site.name,
user._meta.app_label,
user._meta.model_name,
),
args=(user.pk,),
)
)
Since this ModelAdmin is registered in the auth app, you will need to unregister that, add your own custom UserAdmin. There are clear instructions on how to do this in the django docs. Once you have done that simply overwrite the method above and edit the redirect.
This would be much more effort than it's worth however, and more importantly it would also be a nasty hack. It's not what the admin interface is intended for. (It would also have the unintended side-effect that when you actually do use the admin app, it will now redirect to wherever you now have told it to - this would be very unusual behaviour).
A better solution...
If you want to add an end-point where a user can change their password it's not difficult to do it yourself, and that would be the best solution in this case.
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.urls import reverse
def change_password(request, user_id):
new_password = request.POST["password"]
user = User.objects.get(id=user_id)
user.set_password(new_password)
user.save()
return HttpResponseRedirect(reverse('your_url_here))
You'll need to add some additional checks (e.g. make sure the user has the appropriate permissions, make sure the request is a POST etc.).
Probably better still...
Django actually has a built in view for doing this, that you can subclass to customise.
# views.py
from django.contrib.auth.views import PasswordChangeView
class MyPasswordChangeView(PasswordChangeView):
success_url = reverse("url_to_redirect_to")
# urls.py
urlpatters = [
path('change_password', MyPasswordChangeViews.as_view())
]
You can read more about these built in views here.

validate_on_submit() fails when RadioButton choices are dynamically generated

I am creating quiz-like web application for learning languages using Flask, Jinja, WTForms, SqlAlchemy etc. Once an user completes such a language course by successfully going through all levels stored in JSON file I want the app offer him a practice mode, where the user will answer randomly selected levels.
When I run the app, I can see radio buttons generated with values from random level as I want, but when I choose any answer and submit it, form.validate_on_submit() returns False and form.errors returns {'practiceform': [u'Not a valid choice']}. When I hard-code value to currentLevel variable, it works properly.
views.py
#user_blueprint.route('/courses/<course>/quiz/practice',methods=['GET','POST'])
#login_required
def practice(course):
courseClass = class_for_name("project.models", course.capitalize())
courses = courseClass.query.filter_by(email=current_user.email).first()
maxLevel = courseClass.query.filter_by(email=current_user.email).first().get_maxLevel()
currentLevel = randint(0, maxLevel-1) # If this value is hard-coded or fetched from db, it works correctly
dic = generateQuestion(course, currentLevel)
display = dic["display"]
correct = dic["correct"]
options = dic["options"]
form = PracticeForm(request.form)
form.practiceform.choices = [(option, option) for option in options]
if form.validate_on_submit():
practiceForm = form.practiceform.data
if ((practiceForm == correct) and courses):
# Do something
flash("Nice job", 'success')
return redirect(url_for('user.practice', course=course))
else:
# Do something else
flash("Wrong answer", 'danger')
return redirect(url_for('user.practice', course=course))
return render_template('courses/practice.html', form=form, display=display)
forms.py
class PracticeForm(Form):
practiceform = RadioField('practice')
practice.html
{% extends "_base.html" %}
{% block content %}
<form action='' method='POST' role='form'>
<p>
<!-- Tried put form.csrf, form.csrf_token, form.hidden_tag() here -->
{{ form.practiceform() }}
</p>
<input type="submit" value="submit" />
</form>
{% endblock %}
So what am I missing there? What makes difference between lets say hardcoded level 25, which works properly or if the number 25 is randomly generated within randint?
My guess is that option is a int, bug WTForms get a str from request.form.
When data comes back from requests it is treated as a string by WTForms unless you specify a type explicitly with the coerce kwarg of the wtforms.fields.*Field constructor:
practiceform = RadioField('practice', coerce=int)
So I found that randint() caused the problem because the practice(course) method was called on both GET and POST actions which led to having two different integers -> two different forms most of the time. So I refactored the code. kept the practice(course) method for GET action and created a new method which handles POST action and this solved the problem.

I want something to be executed through django

I know this question was asked before, but none worked for me. I have this code that I want it to be executed when a button is clicked and a message is passed
import time
from sinchsms import SinchSMS
number = '+yourmobilenumber'
message = 'I love SMS!'
client = SinchSMS(your_app_key, your_app_secret)
print("Sending '%s' to %s" % (message, number))
response = client.send_message(number, message)
message_id = response['messageId']
response = client.check_status(message_id)
while response['status'] != 'Successful':
print(response['status'])
time.sleep(1)
response = client.check_status(message_id)
print(response['status'])
Basically, what I need is to add an input in a template "HTML File", this input get passed to the message variable in the code above, same with the number. I can easily do that with instances, but how can the below get executed when a button is clicked from the form in the template?
I'm kinda newbie in Django and still finding my way
Here is the tutorial that explains how to make the python file, but execute it from the shell, not a django application.
I hope I was clear describing my problem and any help would be appreciated!
All you need is a form with a message field. In a view, you want to show that form and when the user press submit, you want to execute your script.
Here is some pseudo-code:
urls.py
url('^my-page/' my_views.my_view, name='my-page'),
forms.py
SmsForm(forms.Form):
message = fields.CharField(...)
my_views.py
def my_view(request):
form = SmsForm(data=request.POST or None)
if request.method == 'POST':
if form.is_valid():
send_sms(form.cleaned_data['message']) # do this last
messages.success(request, "Success")
return HttpResponseRedirect(request.path)
else:
messages.warning(request, "Failure")
return render(request, 'my_template.html', {'form': form})
Check the Django documentation about urls, views, forms and messages and proceed step by step:
get the page to load
get the form to load
get the form submission to work and simply show "Success" or "Failure"
finally, write the send_sms function (you've almost done it)
Lets start from the dust cloud.
What you are asking is mostly about how the web pages work. You need to know how to pass parameters using HTML. There are lots of ways to do it. But with django there is a pattern.
You need a url, and a view to catch any requests. Then you need to create a template and a form inside it. With this form you could create some requests to send data to your view.
To create you need to edit urls.py inside your project add an url:
urls.py
from django.conf.urls import url
from my_app.views import my_view
urlpatterns = [
...
url(r'^my_url$', my_view, name='my_view')
...
]
For more about urls please look at URL dispatcher page at documentation.
Then create your view inside your app which is my_app in my example. Edit my_app/views.py
my_app/views.py
from django.http import HttpResponse
def my_view(request):
return HttpResponse('IT WORKS!')
This way you get a working view which could be accessed with path /my_url. If you run ./manage.py runserver you could access your view from http://localhost:8000/my_url.
To create a form you need to create a template. By default django searches app directories for templates. Create a templates directory in your app, in our case my_app/templates and create an HTML file inside. For example my_app/templates/my_form.html. But i advice to create one more directory inside templates directory. my_app/templates/my_app/my_form.html. This will prevent template conflicts. You can check Templates page at documentation for more.
my_app/templates/my_app/my_form.html
<html>
<body>
<form action="/my_url" method="POST">
{% csrf_token %}
<input type="text" name="number">
<input type="text" name="message">
<input type="submit" value="Run My Code">
</form>
</body>
</html>
This is the one of the ways of creating your form. But I do not recommend it. I will make it prettier. But first lets "Make it work", edit your views.py:
csrf_token is a django builtin template tag, to put CSRF token into your form. By default django requires CSRF tokens at every post
request.
my_app/views.py
from django.http import HttpResponse
from django.shortcuts import render
def my_view(request):
if request.method == 'GET':
return render('my_app/my_form.html')
elif request.method == 'POST':
# get post parameters or None as default value
number = request.POST.get('number', None)
message = request.POST.get('message', None)
# check if parameters are None or not
if number is None or message is None:
return HttpResponse('Number and Message should be passed')
# your code goes here
...
return HttpResponse('Your code result')
Till this point the purpose of this answer was "Making it work". Lets convert it nice and clean. First of all we would create Form. Forms are like models, which helps you create forms as objects. It also handles form validations. Forms are saved inside forms directory generally. Create my_app/forms.py and edit it:
my_app/forms.py
from django import forms
class MyForm(forms.Form):
number = forms.CharField(max_length=15, required=True)
message = forms.CharField(max_length=160, required=True)
Put your form inside your template:
my_app/templates/my_app/my_form.html
<html>
<body>
<form action="{% url 'my_view' %}" method="POST">
{% csrf_token %}
{{ form }}
</form>
</body>
</html>
Besides the form, the action of the HTML form tag is also changed.
url template tag is used to get url form url name specified in urls.py.
Instead of url tag, {{ request.path }} could have been used.
Create a form instance and pass it to the template rendering:
my_app/views.py
from django.http import HttpResponse
from django.shortcuts import render
from .forms import MyForm
def my_view(request):
if request.method == 'GET':
form = MyForm()
return render('my_app/my_form.html', {'form': form})
elif request.method == 'POST':
form = MyForm(request.POST)
# check if for is not valid
if not form.is_valid():
# return same template with the form
# form will show errors on it.
return render('my_app/my_form.html', {'form': form})
# your code goes here
...
return HttpResponse('Your code result')
You can use class based vies to write your view, but it's not necessary. I hope it helps.
You can create a view that takes up query parameters from the url and use it for further implementation. Then you can create a link/button in the html template which can redirect you to that url. For example:
in urls.py:
url(r'^run_a/(?P<msg>\w{0,25})/(?P<num>\w{0,25})/$', yourcode, name='get_msg'),
in template:
submit
in views.py:
def get_msg(request,msg,num):
message=msg
number=num
#rest of the code
Hope this helps :)

Do we need CSRF verification for every POST request?

I am just building a simple HTML form with POST method and unfortunately I am finding CSRF verification error.
This is just a simple html form using POST method on localhost. There are no cross sites involved. I could definitely fix it by using csrf_token but I still don't understand why django is asking me for that..
There are no re-directions/ iframes involved here...
So, why this is happening?? is this normal to all ??
# Also tried using RequestContext(request) but there isn't any change in the error
#settings.py
'django.middleware.csrf.CsrfViewMiddleware' in MIDDLEWARE_CLASSES
#views.py
# url for home page is "" i.e, http://127.0.0.1:8000/
def HomePage (request):
if request.method == "POST":
form = myForm(request.POST)
if form.is_valid():
data = form.cleaned_data
context = { "myForm" : myForm(choices),
"values" : data,
}
return render_to_response("home.html", context)
else:
form = myForm(choices)
context = {"myForm" : form}
return render_to_response("home.html", context)
# home.html
<div id="pingmeeForm">
<form action="" method="post">
<table>
{{myForm.as_table}}
</table>
<input name="enter" type="submit" value="enter"/>
</form>
{{values}}
</div>
# forms.py
class myForm (forms.Form):
def __init__(self, my_choices,*args, **kwargs):
super(myForm, self).__init__(*args, **kwargs)
self.fields['Friends'] = forms.ChoiceField(choices=my_choices)
message = forms.CharField()
If you do a post request, you typically change the state of the server. If you change the state of the server, you don't want to allow other sites to do so. To protect against other sites issueing post-requests on your server, you add csrf protection. Therefore the solution should (imho) never be to remove the Csrf protection. Depending on the situation, either of the following two is the case:
Your post request does not change the state. In that case, make it a get request.
Your post request changes the state. You need CSRF.
The error message you got but didn't show explains exactly what you are doing wrong: you should ensure that
the view function uses RequestContext
for the template, instead of Context.

Categories