diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index e14efd5298..68417fc2c2 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -4,6 +4,7 @@ Provides helper functions used throughout the InvenTree project import io import json +import os.path from datetime import datetime from PIL import Image @@ -20,6 +21,19 @@ def TestIfImage(img): return False +def TestIfImageURL(url): + """ Test if an image URL (or filename) looks like a valid image format. + + Simply tests the extension against a set of allowed values + """ + return os.path.splitext(os.path.basename(url))[-1].lower() in [ + '.jpg', '.jpeg', + '.png', '.bmp', + '.tif', '.tiff', + '.webp', + ] + + def str2bool(text, test=True): """ Test if a string 'looks' like a boolean value. diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index fa6b077f8e..7b6ce44acf 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -106,37 +106,29 @@ var transfer = event.originalEvent.dataTransfer; + var formData = new FormData(); + if (isFileTransfer(transfer)) { - - var file = transfer.files[0]; - - inventreeFileUpload( - "{% url 'part-image-upload' part.id %}", - file, - {}, - { - success: function(data, status, xhr) { - location.reload(); - }, - error: function(xhr, status, error) { - showAlertDialog('Error uploading image', renderErrorMessage(xhr)); - } - } - ); + formData.append('image_file', transfer.files[0]); } else if (isOnlineTransfer(transfer)) { - - getImageUrlFromTransfer(transfer); - /* - for (var i = 0; i < 12; i++) { - transfer.items[i].getAsString(function(text) { - console.log('item ' + i + ' - ' + text); - }); - } - */ + formData.append('image_url', getImageUrlFromTransfer(transfer)); } else { console.log('Unknown transfer'); + return; } - + + inventreeFormDataUpload( + "{% url 'part-image-upload' part.id %}", + formData, + { + success: function(data, status, xhr) { + location.reload(); + }, + error: function(xhr, status, error) { + showAlertDialog('Error uploading image', renderErrorMessage(xhr)); + } + } + ); }); $("#show-qr-code").click(function() { diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 1bfef2962a..86d90569fa 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -7,6 +7,9 @@ from __future__ import unicode_literals from django.shortcuts import get_object_or_404 +from django.core.validators import URLValidator +from django.core.exceptions import ValidationError + from django.http import JsonResponse from django.urls import reverse_lazy from django.views.generic import DetailView, ListView @@ -30,7 +33,7 @@ from .forms import EditSupplierPartForm from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.views import QRCodeView -from InvenTree.helpers import DownloadFile, str2bool, TestIfImage +from InvenTree.helpers import DownloadFile, str2bool, TestIfImage, TestIfImageURL class PartIndex(ListView): @@ -270,7 +273,20 @@ class PartImage(AjaxUpdateView): class UploadPartImage(AjaxView): - """ View for uploading a Part image """ + """ View for uploading a Part image via AJAX request. + e.g. via a "drag and drop" event. + + There are two ways to upload a file: + + 1. Attach an image file as request.FILES['image_file'] + - Image is validated saved + - Part object is saved + 2. Attach an iamge URL as request.POST['image_url'] + NOT YET IMPLEMENTED + - Image URL is valiated + - Image is downloaded and validated + - Part object is saved + """ model = Part @@ -285,18 +301,63 @@ class UploadPartImage(AjaxView): response['error'] = 'Part not found' return JsonResponse(error_dict, status=404) - uploaded_file = request.FILES['file'] + # Direct image upload + if 'image_file' in request.FILES: + image = request.FILES['image_file'] - if TestIfImage(uploaded_file): - part.image = uploaded_file - part.clean() - part.save() + if TestIfImage(image): + part.image = image + part.clean() + part.save() - response['success'] = 'File was uploaded successfully' - else: - response['error'] = 'Not a valid image file' - status = 400 + response['success'] = 'File was uploaded successfully' + status = 200 + else: + response['error'] = 'Not a valid image file' + status = 400 + return JsonResponse(response, status=status) + + elif 'image_url' in request.POST: + image_url = request.POST['image_url'] + + validator = URLValidator() + + try: + validator(image_url) + except ValidationError: + response['error'] = 'Invalid image URL' + response['url'] = image_url + + return JsonResponse(response, status=400) + + # Test the the URL at least looks like an image + if not TestIfImageURL(image_url): + response['error'] = 'Invalid image URL' + return JsonResponse(response, status=400) + + response['error'] = 'Cannot download cross-site images (yet)' + response['url'] = image_url + response['apology'] = 'deepest' + + return JsonResponse(response, status=400) + + # TODO - Attempt to download the image file here + + """ + head = requests.head(url, stream=True, headers={'User-agent': 'Mozilla/5.0'}) + + if head.headers['Content-Length'] < SOME_MAX_LENGTH: + + image = requests.get(url, stream=True, headers={'User-agent': 'Mozilla/5.0'}) + + file = io.BytesIO(image.raw.read()) + + - Save the file? + + """ + + # Default response return JsonResponse(response, status=status) diff --git a/InvenTree/static/script/inventree/api.js b/InvenTree/static/script/inventree/api.js index 015186d83b..8c67f92979 100644 --- a/InvenTree/static/script/inventree/api.js +++ b/InvenTree/static/script/inventree/api.js @@ -42,8 +42,8 @@ function inventreeGet(url, filters={}, options={}) { }); } -function inventreeFileUpload(url, file, data={}, options={}) { - /* Upload a file via AJAX using the FormData approach. +function inventreeFormDataUpload(url, data, options={}) { + /* Upload via AJAX using the FormData approach. * * Note that the following AJAX parameters are required for FormData upload * @@ -53,10 +53,6 @@ function inventreeFileUpload(url, file, data={}, options={}) { // CSRF cookie token var csrftoken = getCookie('csrftoken'); - - var data = new FormData(); - - data.append('file', file); return $.ajax({ beforeSend: function(xhr, settings) { @@ -68,14 +64,13 @@ function inventreeFileUpload(url, file, data={}, options={}) { processData: false, contentType: false, success: function(data, status, xhr) { - console.log('Uploaded file - ' + file.name); - + console.log('Form data upload success'); if (options.success) { options.success(data, status, xhr); } }, error: function(xhr, status, error) { - console.log('Error uploading file: ' + status); + console.log('Form data upload failure: ' + status); if (options.error) { options.error(xhr, status, error); diff --git a/InvenTree/static/script/inventree/inventree.js b/InvenTree/static/script/inventree/inventree.js index b9454e96a7..551058f101 100644 --- a/InvenTree/static/script/inventree/inventree.js +++ b/InvenTree/static/script/inventree/inventree.js @@ -58,5 +58,9 @@ function getImageUrlFromTransfer(transfer) { /* Extract external image URL from a drag-and-dropped image */ - console.log(transfer.getData('text/html').match(/src\s*=\s*"(.+?)"/)[1]); + var url = transfer.getData('text/html').match(/src\s*=\s*"(.+?)"/)[1]; + + console.log('Image URL: ' + url); + + return url; } \ No newline at end of file