Django request XML file - python

Hi I'm trying to send a XML file from one view to another.
First view (In this view I send the XML):
def view1(request):
xml_file = open("/path/to/respuesta_error.xml", "rb").read()
r = urllib2.Request("http://localhost:8000/ingram/response/", data=xml_file, headers={'Content-Type': 'text/xml'})
u = urllib2.urlopen(r)
response = u.read()
return HttpResponse(response)
Second view (In this view I should read the XML):
#csrf_exempt
# https://docs.python.org/2/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element
def view2(request):
xml_file = open("/path/to/respuesta_error.xml", "rb") # <-- How can I read the request XML?
text = xml_file.read()
text = strip_ns(text)
file_new = open("ultima_respuesta.xml", "w")
file_new.write(texto)
file_new.close()
return HttpResponse("test")
With that, in view2 I read the file /path/to/respuesta_error.xml, I don't know what I should do to read the XML file sent by POST from view1.
I tried with request and request.body but doesn't work.
Thanks

I found that request.body contains the XML as a string, so the view2 should be like this:
#csrf_exempt
# https://docs.python.org/2/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element
def view2(request):
text = strip_ns(request.body)
file_new = open("ultima_respuesta.xml", "w")
file_new.write(texto)
file_new.close()
return HttpResponse("test")

Related

How to test uploadfile in django

I have an uploadform and I want to test it. But there is a problem.
def test_if_can_upload_file(self):
with open('app_blog/tests/test.txt') as file:
self.client.post(reverse('csv_blog'), {'attachment': file})
test_file = file.read()
self.assertEqual(test_file, 'test file')
When I test it, there is an error:
self.assertEqual(test_file, 'test file')
AssertionError: '' != 'test file'
+ test file
Why is my file shown like it is empty? Actually it is not empty.Or maybe I test my form in a wrong way?
form
class UploadBlogForm(forms.ModelForm):
file = forms.FileField()
class Meta:
model = Blog
fields = 'file',
view
def upload_blog(request):
if request.method == "POST":
upload_file_form = UploadBlogForm(request.POST, request.FILES)
if upload_file_form.is_valid():
blog_file = upload_file_form.cleaned_data['file'].read()
blog_str = blog_file.decode('utf-8').split('\n')
csv_reader = reader(blog_str, delimiter=":::", quotechar='"')
Your self.client.post(…) will already exhaust the file handler and read the entire content of the file, this thus means that when you call file.read(), the cursor already moved to the end of the file, and thus returns an empty string.
You should reopen the file and read the file from the beginning, so:
def test_if_can_upload_file(self):
with open('app_blog/tests/test.txt') as file:
self.client.post(reverse('csv_blog'), {'attachment': file})
with open('app_blog/tests/test.txt') as file:
test_file = file.read()
self.assertEqual(test_file, 'test file')

Better way to file response with DRF?

I have this action:
#action(methods=['get'], detail=True)
def download_csv(self, request, pk, *args, **kwargs):
project = self.get_object()
data = show_stages_tasks(request, pk)
file_name = f"{project.name}.csv"
export_to_csv(data, file_name)
file_handle = open(file_name, "r")
response = FileResponse(file_handle.read(), content_type='application/csv')
response['Content-Disposition'] = f'attachment; filename="{file_handle.name}"'
file_handle.close()
os.remove(file_name)
return response
and export_to_csv is:
def export_to_csv(data, filename="project"):
content = JSONRenderer().render(data)
stream = io.BytesIO(content)
content_parsed = JSONParser().parse(stream)
tasks = content_parsed[0]["related_tasks"]
keys = tasks[0].keys()
with open(filename, 'w') as output_file:
dict_writer = csv.DictWriter(output_file, fieldnames=keys)
dict_writer.writeheader()
for task in tasks:
task['children'] = []
task['task_folders'] = []
dict_writer.writerow(task)
And show_stages_tasks returns a serialized data with DRF serializer, with 3 nested serializers (too big and I think unnecessary to post it here).
As you see here - I parse serializer data, create a CSV file, save it, next open it, pass in the Response and delete file. The question is can I somehow pass the content of the file, without actually creating CSV file and next deleting it?
From the Django's official doc, you could find a similar example.
In that example, they are using django.http.HttpResponse class, it can be also used in your case
import csv
from django.http import HttpResponse
def some_view(request):
# Create the HttpResponse object with the appropriate CSV header.
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
writer = csv.writer(response)
writer.writerow(['First row', 'Foo', 'Bar', 'Baz'])
writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"])
return response

How to write a dictionary list to a file using flask and python?

I would like to add/write information to text files (on the disk) using flask (with just python the program works but when I implement it with the rest nothing happens).
view.py :
#app.route('/add', methods=['GET','POST'])
def add_entry():
if request.method == 'POST':
filename = request.form['file_name']
read_file(filename) #returns a dictionnary list
write_file(filename)
return redirect(url_for('index'))
else :
return render_template("file_add.html",title='Add a file', items=items)
and function.py:
def write_file(filename):
with open("app/static/DATA/{}".format(filename), "w") as f:
global items
items2 = [{ 'fixed_address': request.form['mac_address'],
'hardware': request.form['ip_address'],
'host': request.form['host_name'],
'comment': request.form['comment']}]
items.append(items2)
f.write(items)
Nothing happens when I submit, the file stays the same. What am I doing wrong?
I saw that f.write() might not work with other stuff beside strings but even the other solutions don't work and f.write('random_string') does nothing as well.
1) Pass request obj to your function to use it in it's body to retrieve POST params. In your code (when you do not pass request obj to your write function) you cannot get desired params because request is not a flask request object in write func body, just an undefined var. To use flask request obj in your function body - you need to pass it as a second argument:
view.py:
#app.route('/add', methods=['GET','POST'])
def add_entry():
if request.method == 'POST':
filename = request.form['file_name']
read_file(filename) #returns a dictionnary list
write_file(filename, request)
return redirect(url_for('index'))
else :
return render_template("file_add.html",title='Add a file', items=items)
function.py:
def write_file(filename, request):
with open("app/static/DATA/{}".format(filename), "w") as f:
global items
items2 = [{ 'fixed_address': request.form['mac_address'],
'hardware': request.form['ip_address'],
'host': request.form['host_name'],
'comment': request.form['comment']}]
items.append(items2)
f.write(items)
2) Always use full path here with open("app/static/DATA/{}".format(filename), "w") as f:
3) Use json.dump() to write your data to a file:
import json
obj = [{"123": "123"}]
with open("<full_path>", "w") as f:
json.dump(obj, f, indent=4)
4) Do not use filename = request.form['file_name'] - use filename = request.form.get('file_name') instead.

Python Requests PUT to update image in Prestashop

I'm trying to update an existing image from a product in prestashop. I'm using Python and Requests and the following code:
import requests
import io
import mimetypes
from PIL import Image
from StringIO import StringIO
api_key = 'test'
url = "https://.../api/images/products/249/445"
file_name = 't3_6kxvzv.jpg'
fd = io.open(file_name, "rb")
content = fd.read()
fd.close()
def encode_multipart_formdata():
"""Encode files to an http multipart/form-data.
:param files: a sequence of (type, filename, value)
elements for data to be uploaded as files.
:return: headers and body.
"""
BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
CRLF = '\r\n'
L = []
L.append('--' + BOUNDARY)
L.append(
'Content-Disposition: form-data; \
name="%s"; filename="%s"' % ("image", file_name))
L.append('Content-Type: %s' % get_content_type(file_name))
L.append('')
L.append(content)
L.append('--' + BOUNDARY + '--')
L.append('')
body = CRLF.join(L)
headers = {
'Content-Type': 'multipart/form-data; boundary=%s' % BOUNDARY
}
return headers, body
def get_content_type(file_name):
"""Retrieve filename mimetype.
:param filename: file name.
:return: mimetype.
"""
return mimetypes.guess_type(file_name)[0] or 'application/octet- stream'
header, body = encode_multipart_formdata()
r = requests.put(url, data=body, auth=(api_key,""), headers= header)
# also tried data = content
r = requests.get(url, auth=(api_key,""))
i = Image.open(StringIO(r.content))
i.show()
I tried various PUT and POST requests with
data = content
but getting only a 400 status code.
I then tried to GET the existing image, which works fine.
The api_key has all the necessary setting to allow PUT and POST.
I then tried to read into how prestapyt is solving this problem, however after importing prestapyt I couldn't follow their documentation to add an image to a product using:
prestashop.add("https://...api/images/products/249/445", files[('image',file_name,content)])
produces:
KeyError: ('image', 't3_6kxvzv.jpg', '\xff\xd8\xff\xe0\x00\x10JFI...
I tried then to modify the encode_multipart_formdata and get_content_type functions to produce a similar solution, but cannot get past the 400 status code.
I would very much prefer to use Requests and try to understand how to update a picture to using prestapyt and a turn-key solution.
Thank you for your time!
Documentation I used:
Prestashop http://doc.prestashop.com/display/PS16/Chapter+9+-+Image+management
prestapyt https://github.com/prestapyt/prestapyt
UPDATE:
I was able to use Requests and POST to add an image to a product via:
url_2 = "https:/.../api/images/products/249"
r = requests.post(url_2, data=body, auth=(api_key,""), headers=header)
Still not able to use PUT to change or update an image.
This answer comes a little bit late, but here it is. You're reinventing the wheel, altough it's being interesting seeing how: now I understand how to build a multipart form data from scratch. I tried your code and it fails since youre joining str and bytes in your encode_multipart_formdata function:
L.append(content)
That line will raise a TypeError exception.
Requests can post multipart form data in a very simple way:
files = {'image': ('../imagepath/imagename.jpg', open('../imagepath/imagename.jpg', 'rb'), 'image/jpg')}
body, content_type = requests.models.RequestEncodingMixin._encode_files(files, {})
headers = {
"Content-Type": content_type
}
r=requests.post( url + "images/products/" + str(product_id),data=body, headers=headers)
print(r.text)
This has been tested with Python 3.7 and PrestaShop 1.7.8.2.
Couldn't get PUT to work, so instead used DELETE and POST
import requests
import io
import mimetypes
import xml.etree.ElementTree as ET
import sys
api = ''
urls =["https://.../api/images/products/22",
"https://.../api/images/products/31",
"https://.../api/images/products/37",
"https://.../api/images/products/46",
"https://.../api/images/products/212"]
def encode_multipart_formdata(file_name,content):
"""Encode files to an http multipart/form-data.
:param files: a sequence of (type, filename, value)
elements for data to be uploaded as files.
:return: headers and body.
"""
BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
CRLF = '\r\n'
L = []
L.append('--' + BOUNDARY)
L.append(
'Content-Disposition: form-data; \
name="%s"; filename="%s"' % ("image", file_name))
L.append('Content-Type: %s' % get_content_type(file_name))
L.append('')
L.append(content)
L.append('--' + BOUNDARY + '--')
L.append('')
body = CRLF.join(L)
headers = {
'Content-Type': 'multipart/form-data; boundary=%s' % BOUNDARY
}
return headers, body
def get_content_type(file_name):
"""Retrieve filename mimetype.
:param filename: file name.
:return: mimetype.
"""
return mimetypes.guess_type(file_name)[0] or 'application/octet-stream'
def get_image_url(url):
"""get from a given url the image url"""
r = requests.get(url, auth=(api,""))
tree = ET.fromstring(r.content)
return tree.find("image").find("declination").get("{http://www.w3.org/1999/xlink}href")
def delete_image(url):
"""deletes the image on prestashop given by url"""
url2 = get_image_url(url)
requests.delete(url2, auth=(api,""))
def load_image(file_name):
"""loads image to upload"""
fd = io.open(file_name, "rb")
content = fd.read()
fd.close()
return content, file_name
def upload_image(url, file_name):
"""uploads new image to a given url"""
content, file_name = load_image(file_name)
header, body = encode_multipart_formdata(file_name, content)
requests.post(url, data=body, auth=(api,""), headers=header)
if __name__ == "__main__":
file_name = sys.argv[1]
content, file_name = load_image(file_name)
for i in urls:
delete_image(i)
upload_image(i,file_name)
Workaround works fine, still don't understand why there has to be such a complicated way and why PUT doesn't work.

Streaming a CSV file in Django

I am attempting to stream a csv file as an attachment download. The CSV files are getting to be 4MB in size or more, and I need a way for the user to actively download the files without waiting for all of the data to be created and committed to memory first.
I first used my own file wrapper based on Django's FileWrapper class. That failed. Then I saw a method here for using a generator to stream the response:
How to stream an HttpResponse with Django
When I raise an error within the generator, I can see that I am creating the proper data with the get_row_data() function, but when I try to return the response it comes back empty. I've also disabled the Django GZipMiddleware. Does anyone know what I'm doing wrong?
Edit: The issue I was having was with the ConditionalGetMiddleware. I had to replace it, the code is in an answer below.
Here is the view:
from django.views.decorators.http import condition
#condition(etag_func=None)
def csv_view(request, app_label, model_name):
""" Based on the filters in the query, return a csv file for the given model """
#Get the model
model = models.get_model(app_label, model_name)
#if there are filters in the query
if request.method == 'GET':
#if the query is not empty
if request.META['QUERY_STRING'] != None:
keyword_arg_dict = {}
for key, value in request.GET.items():
#get the query filters
keyword_arg_dict[str(key)] = str(value)
#generate a list of row objects, based on the filters
objects_list = model.objects.filter(**keyword_arg_dict)
else:
#get all the model's objects
objects_list = model.objects.all()
else:
#get all the model's objects
objects_list = model.objects.all()
#create the reponse object with a csv mimetype
response = HttpResponse(
stream_response_generator(model, objects_list),
mimetype='text/plain',
)
response['Content-Disposition'] = "attachment; filename=foo.csv"
return response
Here is the generator I use to stream the response:
def stream_response_generator(model, objects_list):
"""Streaming function to return data iteratively """
for row_item in objects_list:
yield get_row_data(model, row_item)
time.sleep(1)
And here is how I create the csv row data:
def get_row_data(model, row):
"""Get a row of csv data from an object"""
#Create a temporary csv handle
csv_handle = cStringIO.StringIO()
#create the csv output object
csv_output = csv.writer(csv_handle)
value_list = []
for field in model._meta.fields:
#if the field is a related field (ForeignKey, ManyToMany, OneToOne)
if isinstance(field, RelatedField):
#get the related model from the field object
related_model = field.rel.to
for key in row.__dict__.keys():
#find the field in the row that matches the related field
if key.startswith(field.name):
#Get the unicode version of the row in the related model, based on the id
try:
entry = related_model.objects.get(
id__exact=int(row.__dict__[key]),
)
except:
pass
else:
value = entry.__unicode__().encode("utf-8")
break
#if it isn't a related field
else:
#get the value of the field
if isinstance(row.__dict__[field.name], basestring):
value = row.__dict__[field.name].encode("utf-8")
else:
value = row.__dict__[field.name]
value_list.append(value)
#add the row of csv values to the csv file
csv_output.writerow(value_list)
#Return the string value of the csv output
return csv_handle.getvalue()
Here's some simple code that'll stream a CSV; you can probably go from this to whatever you need to do:
import cStringIO as StringIO
import csv
def csv(request):
def data():
for i in xrange(10):
csvfile = StringIO.StringIO()
csvwriter = csv.writer(csvfile)
csvwriter.writerow([i,"a","b","c"])
yield csvfile.getvalue()
response = HttpResponse(data(), mimetype="text/csv")
response["Content-Disposition"] = "attachment; filename=test.csv"
return response
This simply writes each row to an in-memory file, reads the row and yields it.
This version is more efficient for generating bulk data, but be sure to understand the above before using it:
import cStringIO as StringIO
import csv
def csv(request):
csvfile = StringIO.StringIO()
csvwriter = csv.writer(csvfile)
def read_and_flush():
csvfile.seek(0)
data = csvfile.read()
csvfile.seek(0)
csvfile.truncate()
return data
def data():
for i in xrange(10):
csvwriter.writerow([i,"a","b","c"])
data = read_and_flush()
yield data
response = HttpResponse(data(), mimetype="text/csv")
response["Content-Disposition"] = "attachment; filename=test.csv"
return response
The middleware issue has been solved as of Django 1.5 and a StreamingHttpResponse has been introduced. The following should do:
import cStringIO as StringIO
import csv
def csv_view(request):
...
# Assume `rows` is an iterator or lists
def stream():
buffer_ = StringIO.StringIO()
writer = csv.writer(buffer_)
for row in rows:
writer.writerow(row)
buffer_.seek(0)
data = buffer_.read()
buffer_.seek(0)
buffer_.truncate()
yield data
response = StreamingHttpResponse(
stream(), content_type='text/csv'
)
disposition = "attachment; filename=file.csv"
response['Content-Disposition'] = disposition
return response
There's some documentation on how to output csv from Django but it doesn't take advantage of the StreamingHttpResponse so I went ahead and opened a ticket in order to track it.
The problem I was having was with the ConditionalGetMiddleware. I saw django-piston come up with a replacement middleware for the ConditionalGetMiddleware that allows streaming:
from django.middleware.http import ConditionalGetMiddleware
def compat_middleware_factory(klass):
"""
Class wrapper that only executes `process_response`
if `streaming` is not set on the `HttpResponse` object.
Django has a bad habbit of looking at the content,
which will prematurely exhaust the data source if we're
using generators or buffers.
"""
class compatwrapper(klass):
def process_response(self, req, resp):
if not hasattr(resp, 'streaming'):
return klass.process_response(self, req, resp)
return resp
return compatwrapper
ConditionalMiddlewareCompatProxy = compat_middleware_factory(ConditionalGetMiddleware)
So then you will replace ConditionalGetMiddleware with your ConditionalMiddlewareCompatProxy middleware, and in your view (borrowed code from a clever answer to this question):
def csv_view(request):
def data():
for i in xrange(10):
csvfile = StringIO.StringIO()
csvwriter = csv.writer(csvfile)
csvwriter.writerow([i,"a","b","c"])
yield csvfile.getvalue()
#create the reponse object with a csv mimetype
response = HttpResponse(
data(),
mimetype='text/csv',
)
#Set the response as an attachment with a filename
response['Content-Disposition'] = "attachment; filename=test.csv"
response.streaming = True
return response

Categories