Intermediate Django 2.0 admin action page not displaying - django

I have created an update custom admin action and want to ask for update confirmation by going to a new page before the update action is finalised.
Below is the code for various files:
admin.py`
class ProfileHolderAdmin(admin.ModelAdmin):
list_display = [field.attname for field in ProfileHolder._meta.fields]
actions = ['update_verified']
def update_verified(self, request, querySet):
users_verified=querySet.update(verified='y')
views.update_confirmation(request)
self.message_user(request,"No. of users verified = %s" %str(users_verified))
update_verified.short_description = "Mark selected users as verified"
admin.site.register(ProfileHolder, ProfileHolderAdmin)`
Code in views.py:
def update_confirmation(request):
return render(request,'Profile/confirm_update.html', context=None)
Code in confirm_update.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Confirm Update</title>
</head>
<body>
<form action="admin.py" method="post">
<p>
Are you sure you want to update the verification status of selected users?
</p>
<input type="hidden" name="action" value="update_status" />
<input type="submit" name="apply" value="Update status"/>
</form>
</body>
</html>
The path to the html file is:
website_name\Profile\templates\Profile\confirm_update.html
Profile is the name of my app and website_name is the directory name.
When I carry out the update function, the status is being correctly updated but the intermediate update page is not showing.
Please help.

Your admin action currently returns None which means the admin will take care of redirecting back to the normal change list page.
In order to render the page you need to return the response you get from update_confirmation. Of course you first want to render the page and then run the queryset update. For that you need to find a way to tell the two calls apart (e.g. by adding a POST parameter confirmed).
def update_verified(self, request, querySet):
if request.POST.get('confirmed') is None:
return views.update_confirmation(request)
users_verified=querySet.update(verified='y')
self.message_user(request,"No. of users verified = %s" %str(users_verified))

Related

Django models to HTML. Then, HTML to PDF

I am trying to export django models data (admin side data) into a PDF file.
For that first I created a HTML file to render the data from models.
The HTML file I created
It worked successfully and showed the data from the models correctly.
Successfully worked (I create a url for it to check whether it is working or not)
Then I tried to render the same html file to PDF. I ran the server and I generated a pdf file.
PDF file I expected it will show the data also. But It only showed the table border.
You can see my folders and names in the 1st photo.
I thing it is enough to add this code. If you need full code please tell me.
This is my views.py from app.
def render_to_pdf(template_src, context_dict={}):
template = get_template(template_src)
html = template.render(context_dict)
result = BytesIO()
pdf = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), result)
if not pdf.err:
return HttpResponse(result.getvalue(), content_type='application/pdf')
return None
class ViewPDF(View):
def get(self, request, *args, **kwargs):
pdf = render_to_pdf('app/pdf_template.html')
return HttpResponse(pdf, content_type='application/pdf')
Can't I use the same html file to get the data as pdf?
Can anyone tell me what wrong I did?
pdf_template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>This is my first pdf</title>
</head>
<body>
<center>
<h2>User Table</h2>
<table border="1">
<tr>
<th>Username</th>
<th>E-mail</th>
<th>Country</th>
<th>City</th>
</tr>
{% for result in user %}
<tr>
<td>
{{result.username}}
</td>
<td>
{{result.email}}
</td>
<td>
{{result.country}}
</td>
<td>
{{result.city}}
</td>
</tr>
{% endfor %}
</table>
</center>
</body>
</html>
I got the correct out put. I am posting this as an answer so that it will use for someone else too.
class ViewPDF(View):
def get(self, request, *args, **kwargs):
context={}
context['user'] =user.objects.all()
pdf = render_to_pdf('app/pdf_template.html',context_dict=context)
return HttpResponse(pdf, content_type='application/pdf')
In the views.py I modified the code like above.
i see you used 'user' as a list for your 'for' condition but you never add it to your context. i think it's working fine but there is no data to show
update:
in the 'render_to_pdf' you get 'context_dict' argument to render your template by that. but you never pass this argument when you call your function. that's why you can' see anything except borders. because there is no data
update 2: in this line :
pdf = render_to_pdf('app/pdf_template.html')
just add context dict. something like this:
pdf = render_to_pdf('app/pdf_template.html',context)

CSRF-attack using Django

I'm working on a Django project and I can make a CSRF-attack from an external url or file. How I can block it?
The attack consist:
I create a file with this content:
<html>
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://XXXXXX.com/YYYYY/AAAAAA/LLLLLL">
<input type="submit" value="Submit request" />
</form>
</body>
</html>
I login on my page
I open the file in the same browser
Submit the button
The request is accepted and the action is executed.
Thanks for everything :)
Solved
django.middleware.csrf.CsrfViewMiddleware does not provide csrf protection if the request is GET
# Assume that anything not defined as 'safe' by RFC7231 needs protection
if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if getattr(request, '_dont_enforce_csrf_checks', False):
# Mechanism to turn off CSRF checks for test suite.
# It comes after the creation of CSRF cookies, so that
# everything else continues to work exactly the same
# (e.g. cookies are sent, etc.), but before any
# branches that call reject().
return self._accept(request)
Change your method to post and add csrf token
<html>
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://XXXXXX.com/YYYYY/AAAAAA/LLLLLL" method="post">
{% csrf_token %}
<input type="submit" value="Submit request" />
</form>
</body>
</html>
and handle your view inside :
if request.method == 'POST':
# your logic here
Make sure that 'django.middleware.csrf.CsrfViewMiddleware' should come before any view middleware that assume that CSRF attacks have been dealt with.

'ManagementForm data is missing or has been tampered with' when submitting a form via XHR

I want a user to be able to select from an existing list of options. If the option is not within the ones already in the database, though, they need to be able to add a new item, while remaining on the main form, because after having added the new item they need to be able to save the main form
I was using the JQuery library select2, which allows a tags:True option, thanks to which users can add a new item to a list if not present. Nevertheless, Django validates that field and if it finds an item which is not in the database is raises an error. My initial plan was that of capturing the new value in the view and then (saving first the form with commit=False), if it was not in the database, save it. But this is not doable without forcing Django not to validate the field, which I haven't managed to do.
Another option, which I'm currently investigating, is that of adding a modal pop-up containing the sub-form. Of course I'd like to avoid opening the sub-form in another page, which would work but would be quite non-user-friendly.
models.py:
class Venue(models.Model):
venue_name = models.CharField(max_length=30)
class performanceOfCompositionNoDb(models.Model):
venue = models.ForeignKey(Venue, on_delete=models.SET_NULL, null=True, blank=True)
forms.py:
class VenueForm(forms.ModelForm):
class Meta:
model = Venue
fields = ['venue_name']
views.py:
def composition_edit_view(request, id=id):
form_composition = CompositionForm(request.POST or None, instance=obj)
form_venue = VenueForm(request.POST or None)
if request.method == "POST" and form_composition.is_valid():
form_composition.save()
context = {
'form_composition': form_composition,
'form_venue': form_venue
[...]
def venue_add_view(request):
form_venue = VenueForm(request.POST or None)
if form_venue.is_valid():
form_venue.save()
context = {
'form_venue': form_venue,
}
return render(request, "venue-add.html", context)
my template.html:
{% include '../venue-add.html'%}
<form id="compositionForm" action='.' enctype="multipart/form-data" method='POST'>
{{form_composition}}
<p>Add new venue</p>
<input class="button" type='submit' id='save' value='Save' />
</form>
venue-add.html:
<div class="reveal" id="addvenueModal" data-reveal>
<form action='.' enctype="multipart/form-data" method='POST'>
{% csrf_token %}
<div class="grid-container">
<div class="grid-x grid-padding-x">
{{ form_venue }}
</div>
<input class="button" type='submit' value='Save' />
</div>
</form>
</div>
I'm expecting to open the venue-add form when I click on the 'Add new venue' button, which happens. With the modal open and the new text input, I then click the 'submit' button of the modal. At that point I get a 'Validation error - ['ManagementForm data is missing or has been tampered with']'. I have other formsets in the main template, and it all works correctly if I don't add a new venue.
How can I solve this? Also, if there's a way of using the select2 library and add a new venue in a more dynamic way, do let me know! Thanks.
Testing with XHR
Using XHR gives the same ['ManagementForm data is missing or has been tampered with'] error in the response:
<div class="reveal" id="addVenue" data-reveal>
<form id="addVenueForm" action='.' onsubmit="addVenue(this); return false;" enctype="multipart/form-data" method='POST'>
{% csrf_token %}
<div class="grid-container">
<div class="grid-x grid-padding-x">
{{ form_venue }}
</div>
<input class="button" type='submit' value='Save' />
</div>
</form>
<script type="text/javascript"> "use strict";
function addVenue (oFormElement) {
var oReq = new XMLHttpRequest();
var data = new FormData(oFormElement)
oReq.onload = {}
oReq.onreadystatechange = function() {
if (oReq.readyState == XMLHttpRequest.DONE) {
var result = oReq.responseText;}
}
oReq.open("post", oFormElement.action, true);
oReq.send(data);
} </script>
As I said, I do have formsets (working correctly) in the main form from which I'm launching this modal. This modal doesn't contain any formset though, it's a simple one-field form, with its own csrf token.
Edit 2
OK, so upon further investigating I've found that the error springs from
return render(request, "compositions/composition_edit.html", context)
in the view.py. In other words, when I hit 'submit' in the modal, for some reason the 'submit' of the main form kicks in also, thus generating issues. How can I isolate the 'submit' of the modal and get the 'submit' of the main form not to kick in unless explicitly clicked?
I had to change the action of the modal form to the address I mapped in my urls.py (action='/venue-add/') for that form. That solved the issue.
Now, the newly-added items are not displayed in the main form unless I refresh the page, no matter ho many times I destroy/empty/repopulate the select2() dropdown list. I think this has to do with the fact that the data to the venue dropdown list is sent by the view at the loading of the main form, and that the context remains the same no matter what updates to the database have been made after the page loading.
For the above reason I'm investigating using an API on my own application and GET and POST data via an AJAX call, which still gives me issue. I'm opening another question for that though.

Flask: Work around a timeout error while running long data collection process

I have a Flask app that will run on OpenShift, that takes a while to generate data and can lead to a timeout error.
From looking at examples, I thought that I could render a "please wait" template which returns immediately, while also calling my long running, run_analysis() function. When the data was finished being collected, run_analysis() would render a new page.
Either this isn't possible or I'm doing it wrong. Thanks for your help.
from flask import Flask
import jinja2
app = Flask(__name__)
please_wait_template = jinja2.Template('''
<!DOCTYPE html>
<html lang="en">
<head>
<title>please wait for data</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Collecting data, this could take a while.</h1>
</body>
</html>''')
input_template = jinja2.Template('''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Input Keywords</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Doing stuff</h1>
<form action="/please_wait/" method="POST">
Enter keywords<br>
<input type="text" name="kw" placeholder="data science"><br>
<input type="submit" value="Submit" name="submit">
</form>
</body>
</html>''')
output_template = jinja2.Template("""
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>results</title>
<meta charset="UTF-8">
</head>
<body>
<h1>RESULTS</h1>
{{ results }}
</body>
</html>
""")
#app.route('/')
def render_input_page():
return input_template.render()
#app.route('/please_wait/')
def please_wait():
return please_wait_template.render()
#app.route('/please_wait/', methods=['post'])
def run_analysis():
kws = request.form['kw']
zips = request.form['zipcodes']
template = run_long_analysis(kws, zips)
return template
def run_long_analysis(kws, zips):
import time
time.sleep(2400)
return output_template(results="testing")
Yes, it is possible. You will want to break out of the single file however. i.e. creating a templates directory and storing your templates there. http://flask.pocoo.org/docs/0.10/quickstart/#rendering-templates
Quoting the docs basic example:
from flask import render_template
#app.route('/hello/')
#app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
You can see render_template is used instead of the .render() or output_template. This is convenient and it makes reading the Flask logic easier.
Specifically to your code:
#app.route('/please_wait/', methods=['post'])
def run_analysis():
kws = request.form['kw']
zips = request.form['zipcodes']
template = run_long_analysis(kws, zips)
The line zips = request.form['zipcodes'] would be troublesome, because I don't see a form under the please_wait route. Perhaps you realize that though.
In general, a tool that will help in situations like this, in my experience is the Flask session object. Which is basically a global dictionary that persists across a user session. See more here in the docs http://flask.pocoo.org/docs/0.10/quickstart/#sessions.
Quoting that example:
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
#app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form action="" method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
#app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
# set the secret key. keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
You can see in the above example that the session object behaves like a dict. It also allows you to dynamically change content displayed, via Jinja2. In other words, it can be passed into the template. These methods in conjunction, should give you the functionality you are looking for.

how to send url parameter in POST request without form

I have to pass a parameter in url. I can't send it in as usual GET request the variable and value is shown in the address bar with the request. So, the end user can change the value for this variable value and send request which will be processed.
href="url=/admin/usermanagement/?flag=2
I want to send this hiding flag=2
now this goes as a GET request and it is seen in the address bar. Please give your suggestion if you have any idea on changing this to POST to hide and send the value.
You can still use a html form but "hide" it from the user:
<!DOCTYPE html>
<html>
<body>
<form id="myform" method="post" action="/">
{% csrf_token %}
<input type="hidden" name="flag" value="2" />
Let's go!
</form>
</body>
</html>
And the view:
def index(request):
if request.method == 'POST':
try:
flag = request.POST['flag']
# TODO use flag
except KeyError:
print 'Where is my flag?'
return render_to_response('index.html', {},
context_instance=RequestContext(request))
You can use AJAX to get rid of forms entirely.
Just add this to your JavaScript:
function postTo(url, query) {
var request = (XMLHttpRequest?new XMLHttpRequest():new ActiveXObject());
request.open('POST', url, true);
request.send(query);
}
Then call with something like this:
postTo('/admin/usermanagement/','flag=2');
Note that this will NOT reload the page. If you want to reload the page, use Borges' answer.