DRF How to test file uploads? - python

I have a simple model, a serializer and a view. I want to upload a file over the view but no method I found worked.
Here's my code:
def test_api_post(self):
lesson = self.Create_lesson()
file = SimpleUploadedFile(
"file.txt",
"".join(random.choices(string.ascii_letters + string.digits, k=1024 * 5)).encode(),
"text/plain"
)
response = self.client.post(
"/api/submission/",
{
"lesson": lesson.id,
"file": file
},
format="multipart"
)
self.assertStatusOk(response.status_code) # Error
I tried it using with open() as file and I also tried using path.read_bytes(). Nothing worked.
How can I test binary file uploading with django-rest-framework's test client? doesn't work, https://gist.github.com/guillaumepiot/817a70706587da3bd862835c59ef584e doesn't work and how to unit test file upload in django also doesn't work.

I have fixed the problem with that:
import io
from django.test import TestCase
class test(TestCase):
def test_upload_file(self):
with open('/path/to/file.txt', 'rb') as fp :
fio = io.FileIO(fp.fileno())
fio.name = 'file.txt'
r = self.client.post('/url/', {'filename': fio, 'extraparameter': 5})
self.assertEqual(r.headers['Content-Type'], 'application/json')
url.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'/url/$', views.serverside_method, name="serverside_method")
]
Example on the server-side (view.py)
def serverside_method(request):
if 'filename' in request.FILES:
file_request = request.FILES['filename']
file_size = file_request.size
file_request_name = file_request.name
return JsonResponse({'Success': True})
else:
return JsonResponse({'Success': False})
source: https://gist.github.com/nghiaht/682c2d8d40272c52dbf7adf214f1c0f1

work for me
from rest_framework.test import APIRequestFactory
from apps.Some.SomeViewSet import SomeViewSet
c = APIRequestFactory()
url = 'someurl/'
view = SomeViewSet.as_view()
file = 'path/to/file'
q = {'filename': file}
request = c.post(url, q)
response = view(request)

Related

How to allow users to download an excel file using python/Flask?

I have a method that takes in an excel file, read the excel file, extracts data from it and save. The issue I am having now is how to write a method that returns the excel file and allow users to download it from the frontend.
This is the main file
class UploadFileForm(FlaskForm):
file = FileField('File', validators=[InputRequired()])
submit = SubmitField('Download File')
#app.route('/', methods=['GET', 'POST'])
def home_api():
form = UploadFileForm()
if form.validate_on_submit():
file = form.file.data
file.save(os.path.join(os.path.abspath(os.path.dirname(_file_)),app.config['UPLOAD_FOLDER'],secure_filename(file.filename)))
process_xl(os.path.join(os.path.abspath(os.path.dirname(_file_)),app.config['UPLOAD_FOLDER'],secure_filename(file.filename)))
download_file(os.path.join(os.path.abspath(os.path.dirname(_file_)),app.config['UPLOAD_FOLDER'],secure_filename(file.filename)))
return 'File has been uploaded'
return render_template('index.html', form=form)
This is the service that does the extraction and little manipulation.
from flask import send_file
import requests
from openpyxl import load_workbook
def weather(city):
url = "https://community-open-weather-map.p.rapidapi.com/weather"
querystring = {"q":f"{city}","lat":"0","lon":"0","callback":"test","id":"2172797","lang":"null","units":"imperial","mode":"HTML"}
headers = {
"X-RapidAPI-Key": "0a109dce92msh070cc32fb93c003p1e4e8djsnef8cf195fed1",
"X-RapidAPI-Host": "community-open-weather-map.p.rapidapi.com"
}
response = requests.request("GET", url, headers=headers, params=querystring)
return response.text
def process_xl(filename):
wb = load_workbook(filename=filename)
sheet_1 = wb['Sheet1']
sheet_2 = wb['Sheet2']
city_1 = sheet_1['A1'].value
city_2 = sheet_2['']
Based on the requirement, I need a function that will return the excel file which I can call in the main method. I have tried returning the the excel file it was not working
You only need to create a route that sends the file to the client
here is how
import os
from flask import send_file
#app.route('/downlaod')
def downloadExcel():
root_path = "path_to_your_file"
filename = "your_file_name.xlsx"
file_path = os.path.join(root_path, filename)
return send_file(file_path)
You function should save the file.
def process_xl(filename):
wb = load_workbook(filename=filename)
sheet_1 = wb['Sheet1']
sheet_2 = wb['Sheet2']
city_1 = sheet_1['A1'].value
city_2 = sheet_2['']
wb.save(filename)
This line solved my problem
return send_from_directory('directory-path', file.filename)
This is the full code here
from flask import send_from_directory
class UploadFileForm(FlaskForm):
file = FileField('File', validators=[InputRequired()])
submit = SubmitField('Download File')
#app.route('/', methods=['GET', 'POST'])
def home_api():
form = UploadFileForm()
if form.validate_on_submit():
file = form.file.data
file.save(os.path.join(os.path.abspath(os.path.dirname(_file_)),app.config['UPLOAD_FOLDER'],secure_filename(file.filename)))
process_xl(os.path.join(os.path.abspath(os.path.dirname(_file_)),app.config['UPLOAD_FOLDER'],secure_filename(file.filename)))
download_file(os.path.join(os.path.abspath(os.path.dirname(_file_)),app.config['UPLOAD_FOLDER'],secure_filename(file.filename)))
return send_from_directory('directory-path', file.filename)
return render_template('index.html', form=form)

How does one write Python code that merges 2 files created using wkhtmltopdf into 1 pdf file using pypdf2

I have an application with the back-end written in Python that converts html files to pdf files. To do this it implements wkhtmltopdf (https://wkhtmltopdf.org/). It currently works perfectly for creating a single PDF file from an html file and outputs that to the user.
However, I need to be able to create multiple separate PDF files and then merge the files together into a single PDF.
I have been trying to do this using Pypdf2 with the PdfFileMerger() function (https://pythonhosted.org/PyPDF2/PdfFileMerger.html) and haven't been able to do it. I keep getting 'bytes' object has no attribute 'seek'
Here is my current code:
def multi_test_sheet(request, equipment_id):
if not request.user.is_authenticated:
return render(request, "jobs/login.html", {"message": None})
from io import BytesIO
from PyPDF2 import PdfFileReader, PdfFileMerger
if not request.user.is_authenticated:
return render(request, "jobs/login.html", {"message": None})
equipment = Equipment.objects.filter(pk=equipment_id).first()
if not job:
raise Http404("test sheet error. Error code: get job failed")
pdf_write = PdfFileWriter()
user_properties=UserProperties.objects.get(user=request.user)
context = {
"equipment": equipment,
"job": equipment.equipments,
"test_sheet": equipment.sheet_eq,
"user_properties": user_properties,
"now": datetime.now().strftime("%b-%d-%Y %H:%M"),
"now_date": datetime.now().date()
}
html_sheet = render_to_string('jobs/test_sheet_gear1.html', context)
html_sheet2 = render_to_string('jobs/test_sheet_gear2.html', context)
pdf_content1 = pdfkit.from_string(html_sheet, None)
pdf_content2 = pdfkit.from_string(html_sheet2, None)
pdfadder = PdfFileMerger(strict=False)
pdfadder.append(pdf_content1)
pdfadder.append(pdf_content2)
pdf_adder.write("combined_sheets.pdf")
response = HttpResponse(pdf_adder, content_type="application/pdf")
response["Content-Disposition"] = f"filename={equipment.site_id}.pdf"
return response
I resolved this by hiring someone. The problem was that the objects being passed into the PyPDF2 function called PdfFileMerger() were not being recognized as pdf objects.
To resolve that, save the files (I place them in a folder called interim) using the second argument from the pdfkit.from_string() function, then assign the newly created files to independent variables using open() function, and finally proceed with the merging function by merging those variables.
def multi_test_sheet(request, equipment_id):
if not request.user.is_authenticated:
return render(request, "jobs/login.html", {"message": None})
from io import BytesIO
from PyPDF2 import PdfFileReader, PdfFileMerger
if not request.user.is_authenticated:
return render(request, "jobs/login.html", {"message": None})
equipment = Equipment.objects.filter(pk=equipment_id).first()
if not job:
raise Http404("test sheet error. Error code: get job failed")
page_quantity = 2 #temporary value for a property that will be added to either equipment or test sheet model
pdf_file_object = BytesIO()
stream = BytesIO()
pdf_write = PdfFileWriter()
user_properties=UserProperties.objects.get(user=request.user)
today = datetime.now()
now=today.strftime("%b-%d-%Y %H:%M")
now_date = today.date()
context = {
"equipment": equipment,
"job": equipment.equipments,
"test_sheet": equipment.sheet_eq,
"user_properties": user_properties,
"now": now,
"now_date": now_date
}
html_sheet = render_to_string('jobs/test_sheet_gear1.html', context)
html_sheet2 = render_to_string('jobs/test_sheet_gear2.html', context)
pdf_content1 = pdfkit.from_string(html_sheet, 'interm/test_sheet_gear1.pdf')
pdf_content2 = pdfkit.from_string(html_sheet2, 'interm/test_sheet_gear2.pdf')
pdfadder = PdfFileMerger(strict=False)
pdf1_v=PdfFileReader(open('interm/test_sheet_gear1.pdf', 'rb'))
pdf2_v=PdfFileReader(open('interm/test_sheet_gear2.pdf', 'rb'))
pdfadder.append(pdf1_v, import_bookmarks=False)
pdfadder.append(pdf2_v, import_bookmarks=False)
pdfadder.write('interm/'+str(user_properties.pk)+'combined_sheets.pdf')
output_file = open('interm/combined_sheets.pdf', 'rb')
response = HttpResponse(output_file, content_type="application/pdf")
response["Content-Disposition"] = f"filename={equipment.site_id}.pdf"
return response

How to force an instant image refresh from an Angular Safeurl without reloading the page

I've created an angular component (product) on which a user is able to change an image (product's image).
html:
<img [attr.id]="_product.id" [attr.src]="_product.thumbnail_url"/>
When the image is chosen by the user from a custom file picker component on a modal form. The following method is called to reload the image in the parent product component.
The imageBinary here is a base64 blob binary image returned from a python FLASK REST API endpoint which is connected to a mysql database.
reloadProductImage(new_image_id):void{
let newUrl:SafeUrl;
let urlStr:string;
this.productservice.getImage(new_image_id).subscribe((filedata) =>{
let imageBinary = filedata.thumbnail;
urlStr = 'data:image/jpeg;base64,' + imageBinary + '?'+Date.now();
newUrl = this.domSanitizer.bypassSecurityTrustUrl(urlStr);
this.form.patchValue({image_id: new_image_id});
this._product.thumbnail_url = newUrl;
this._product.image_id = new_image_id;
I've found many examples how to force the browser to a reload on a regular src url by appending a timestamp to the url. However, these examples do not seem to apply to a blob stored in a saferurl.
inspect image element
<img _ngcontent-snv-c187="" id="146914e3-6e0a-409f-8046-ff04580f6242" src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgA+++very+long+blob+++vBzu3ZFwwAAAABJRU5ErkJggg==?1619156455998">
Console Error message
Failed to load resource: net::ERR_INVALID_URL
Failed attempts
I've tried:
newUrl = 'data:image/jpeg;base64,' + imageBinary + '?'+ Date.now();
and
newUrl = 'data:image/jpeg;base64,' + imageBinary + '?datetime='+ Date.now();
Background info
For illustration purposes, I'll post my Angular and Python code here (methods in a Flask SQLAlchemy DB Model) which post an image file to a Flask REST API, generate the thumbnail from the base64 image and return it to Angular. Note: the get function is called in a GET request. The Create function is called in a POST request.
Angular FileUpload component:
import { HttpClient } from '#angular/common/http';
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
import {NotificationService} from '../..//notification/notification.service';
#Component({
selector: 'fileupload-form',
template: `
<p>
Productfoto toevoegen
</p>
<input type="file" (change)="onFileSelected($event)">
<button type="button" (click)="onUpload()">Upload</button>
`,
styles: [
]
})
export class FileuploadComponent implements OnInit {
selectedFile: File = null;
#Output()
fileSubmitted = new EventEmitter();
constructor(private http: HttpClient,
private notificationService: NotificationService,
) { }
onFileSelected(event){
this.selectedFile = <File>event.target.files[0];
}
onUpload(){
const fd = new FormData();
fd.append('inputFile', this.selectedFile , this.selectedFile.name);
this.http.post(['/offerte/api/files','upload'].join('/'),fd)
.subscribe(res => {
console.log(res);
this.notificationService.success(`Bestand ${this.selectedFile.name} is opgeslagen.`);
this.fileSubmitted.emit(res);
})
}
ngOnInit(): void {
}
}
Python Flask Endpoints:
Imports
import logging
from io import BytesIO
from flask import request, send_file
from flask_restx import Resource
from api.serializers import fileobject, page_of_files
from api.parsers import pagination_arguments
from api.restplus import api
from database.models import File
from flask_jwt_extended import jwt_required, get_jwt_identity
logger = logging.getLogger()
ns_files = api.namespace('files', description='Operations related to file management')
POST
#ns_files.route('/upload')
class Upload(Resource):
#api.doc(security='jwt')
#jwt_required
def post(self):
'''Upload a new file, returns id of new file'''
inputFile = request.files['inputFile']
logger.info('create new file %s', request.json)
current_user = get_jwt_identity()
return File.create(inputFile, current_user)
GET
#ns_files.route('/<string:file_id>')
#api.response(404, 'file not found')
class Details(Resource):
#api.marshal_with(fileobject)
def get(self, file_id):
'''Download a new file from the db (binary storage)'''
logger.info('loading file %s')
current_user = get_jwt_identity()
returnedFile = File.get(file_id)
return returnedFile
Python FLASK REST API functions
from PIL import Image
import magic
from base64 import b64encode
import io
accepted_mime_types = {'jpe':'image/jpeg','jpeg':'image/pjpeg','png':'image/png'}
def get(id):
return Product.query.filter(Product.id == id).one()
def create(inputFile, current_user):
filename = inputFile.filename
file_obj = File(
filename = filename,
extension = '.jpg',
folderpath = 'folderpath',
databinary = inputFile.read(),
category = 'default',
description = 'beschrijving...',
original_filename = filename,
created_by = current_user,
modified_by= current_user)
MAX_SIZE = (150, 150)
thumbnailFile, mimetype = File.getThumbnail(inputFile, MAX_SIZE)
if not thumbnailFile is None:
output = io.BytesIO()
thumbnailFile.save(output, format='PNG')
hex_data = output.getvalue()
file_obj.thumbnail = hex_data
else:
raise NameError(mimetype)
db.session.add(file_obj)
db.session.commit()
return str(file_obj.id)
def mimetype(file_obj):
return magic.from_buffer(file_obj.read(2048), mime=True)
def getThumbnail(file_obj, size):
file_obj.seek(0)
real_mime_type = magic.from_buffer(file_obj.read(2048), mime=True)
if (real_mime_type in accepted_mime_types.values()):
thumbnailImage = Image.open(file_obj)
thumbnailImage.thumbnail(size)
return thumbnailImage, real_mime_type
else:
raise NameError(real_mime_type)

MultiValueDictKeyError when upload an Image using Django Test

Hi I am trying to make a test case to test my upload image API. But I think I am not returning something when I pass the files for request.FILES
#models.py
class Image(models.Model):
name = models.CharField(max_length=200)
imagefile = models.ImageField(
null=True,
blank=True,
max_length=500,
upload_to='temp/images/')
def __str__(self):
return self.name
#views.py
class ImagesView(APIView):
def post(self, request):
print("DATA!!!", request.data)
print("FILE!!!", request.FILES)
params = Image(
imagefile=request.FILES['image'])
params.save()
print(params)
return Response({"status": "ok"})
#test.py
class CanalImagesApiTests(TestCase):
fixtures = []
def test_post_image(self):
c = Client()
response = c.post('/admin/login/', {'username': 'admin', 'password': 'passwrd'})
filename = 'data/sample_image.jpg'
name = 'sample_image.jpg'
data = {"data": "passthis"}
print(to_upload)
with open(filename, 'rb') as f:
c.post('/images/', data=data, files={"name": name, "image": f}, format='multipart')
response = c.get('/images/')
results = response.json()
My request.FILES is empty: <MultiValueDict: {}>
and my test gets an error: django.utils.datastructures.MultiValueDictKeyError: 'image'
You can pass a file object inside the data dictionary.
files parameter is not needed. format parameter means output format, not input format.
You can use APITestCase instead of TestCase if you are testing Django REST Framework API. This allows you can test some method like PUT.
from rest_framework import status
from rest_framework.test import APITestCase
class CanalImagesApiTests(APITestCase):
def test_post_image(self):
with open('data/sample_image.png', 'rb') as f:
data = {
"data": "passthis",
"image": f,
}
response = self.client.post('/images/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), {"status": "ok"})
Test result (which is passing):
DATA!!! <QueryDict: {'data': ['passthis'], 'image': [<InMemoryUploadedFile: sample_image.png (image/png)>]}>
FILE!!! <MultiValueDict: {'image': [<InMemoryUploadedFile: sample_image.png (image/png)>]}>

ExtractionError: Can't extract file(s) to egg cache

I'm developing my app on Windows with GAE, but the below error message was shown.
C:\Python27\Lib\site-packages
Perhaps your account does not have write access to this directory? You can
change the cache directory by setting the PYTHON_EGG_CACHE environment
variable to point to an accessible directory.
So I checked some post in this site and I found some articles but I could not understand it. My understanding is that I need to create cache directory with write access in home directory, but I did not understand where "home directory" is.
Also in other post, I found the answer saying This approach solved my issue. I did uninstall pyyaml using pip and then installed it with easy_install -z pyyaml
Which approach is correct?
import webapp2
import os
import jinja2
import cloudstorage
import mimetypes
from PIL import Image
from google.appengine.ext import ndb
from google.appengine.ext import blobstore
from google.appengine.api import users
from google.appengine.api import app_identity
from google.appengine.api import images
from models import Note
from models import CheckListItem
from models import NoteFile
jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
images_formats = {
'0':'image/png',
'1':'image/jpeg',
'2':'image/webp',
'-1':'image/bmp',
'-2':'image/gif',
'-3':'image/ico',
'-4':'image/tiff',
}
class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if user is not None:
logout_url = users.create_logout_url(self.request.uri)
template_context = {
'user': user.nickname(),
'logout_url' : logout_url,
}
template = jinja_env.get_template('main.html')
self.response.out.write(template.render(template_context))
else:
login_url = users.create_login_url(self.request.uri)
self.redirect(login_url)
def post(self):
user = users.get_current_user()
if user is None:
self.error(401)
bucket_name = app_identity.get_default_gcs_bucket_name()
uploaded_file = self.request.POST.get('uploaded_file')
file_name = getattr(uploaded_file,'filename',None)
file_content = getattr(uploaded_file,'file',None)
real_path = ''
if file_name and file_content:
content_t = mimetypes.guess_type(file_name)[0]
real_path = '/' + bucket_name + '/' + user.user_id() + "/" + file_name
with cloudstorage.open(real_path,'w',content_type=content_t,options={'x-goog-acl':'public-read'}) as f:
f.write(file_content.read())
self._create_note(user, file_name, real_path)
logout_url = users.create_logout_url(self.request.uri)
template_context = {
'user':user.nickname(),
'logout_url': logout_url,
}
self.response.out.write(self._render_template('main.html',template_context))
def _render_template(self,template_name,context=None):
if context is None:
context = {}
user = users.get_current_user()
ancestor_key = ndb.Key("User",user.nickname())
qry = Note.owner_query(ancestor_key)
context['notes'] = qry.fetch()
template = jinja_env.get_template(template_name)
return template.render(context)
#ndb.transactional
def _create_note(self,user,file_name,file_path):
note = Note(parent=ndb.Key("User", user.nickname()), title=self.request.get('title'), content=self.request.get('content'))
note.put()
item_titles = self.request.get('checklist_items').split(',')
for item_title in item_titles:
item = CheckListItem(parent=note.key, title=item_title)
item.put()
note.checklist_items.append(item.key)
if file_name and file_path:
url, thumbnail_url = self._get_urls_for(file_name)
f = NoteFile(parent=note.key, name=file_name, url=url,thumbnail_url=thumbnail_url,full_path=file_path)
f.put()
note.files.append(f.key)
note.put()
def _get_urls_for(self,file_name):
user = users.get_current_user()
if user is None:
return
bucket_name = app_identity.get_default_gcs_bucket_name()
path = '/' + bucket_name + '/' + user.user_id() + '/' + file_name
real_path = '/gs' + path
key = blobstore.create_gs_key(real_path)
try:
url = images.get_serving_url(key, size=0)
thumbnail_url = images.get_serving_url(key,size=150,crop=True)
except images.TransformationError,images.NotImageError:
url = "http://storage.googleapis.com{}".format(path)
thumbnail_url = None
return url,thumbnail_url
class MediaHandler(webapp2.RequestHandler):
def get(self,file_name):
user = users.get_current_user()
bucket_name = app_identity.get_default_gcs_bucket_name()
content_t = mimetypes.guess_type(file_name)[0]
real_path = '/' + bucket_name + '/' + user.user_id() + '/' + file_name
try:
with cloudstorage.open(real_path,'r')as f:
self.response.headers.add_header('Content-Type',content_t)
self.response.out.write(f.read())
except cloudstorage.errors.NotFoundError:
self.abort(404)
class ShrinkHandler(webapp2.RequestHandler):
def _shrink_note(self,note):
for file_key in note.files:
file = file_key.get() # this is the same as "file.get().url" in html file. we add the comment.
try:
with cloudstorage.open(file.full_path) as f:
image = images.Image(f.read())
image.resize(640)
new_image_data = image.execute_transforms()
content_t = images_format.get(str(image.format))
with cloudstorage.open(file.full_path,'w',content_type=content_t) as f:
f.write(new_image_data)
except images.NotImageError:
pass
def get(self):
user = users.get_current_user()
if user is None:
login_url = users.create_login_url(self.request.url)
return self.redirect(login_url)
ancestor_key = ndb.Key("User",user.nickname())
notes = Note.owner_query(ancestor_key).fetch()
for note in notes:
self._shrink_note(note)
self.response.write('Done.')
app = webapp2.WSGIApplication([
(r'/', MainHandler),
(r'/media/(?P<file_name>[\w.]{0,256})',MediaHandler),
(r'/shrink',ShrinkHandler)
], debug=True)
It looks like you're developing a standard env GAE app but you're attempting to install third party libraries in (and use them from) your local system's site packages. This won't work.
You need to install the third party libraries in your app's library directory (i.e. using -t <your_lib_dir> option for pip install, for which you shouldn't need special permissions. See also:
Using third-party libraries.
No module named warnings when starting GAE inside virtualenv locally

Categories

Resources