From 5ba7aeaa277b14a2c4ce7ef1f3d97adad9307bb6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 23 Jun 2021 10:28:21 +1000 Subject: [PATCH] Fixes: - Use DRF ImageField, not FileField - Ensure that permissions get updated correctly in 'test' mode - Allow file upload in the APITester class --- .gitignore | 3 ++ InvenTree/InvenTree/api_tester.py | 4 +- InvenTree/InvenTree/ready.py | 6 ++- InvenTree/InvenTree/serializers.py | 2 +- InvenTree/InvenTree/version.py | 2 - InvenTree/part/test_api.py | 79 ++++++++++++++++++++++++++++-- InvenTree/users/apps.py | 2 +- InvenTree/users/models.py | 2 +- 8 files changed, 88 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 5dd3580ef6..c53a837e24 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,9 @@ local_settings.py *.backup *.old +# Files used for testing +dummy_image.* + # Sphinx files docs/_build diff --git a/InvenTree/InvenTree/api_tester.py b/InvenTree/InvenTree/api_tester.py index f0f33ad1a5..a803e6797f 100644 --- a/InvenTree/InvenTree/api_tester.py +++ b/InvenTree/InvenTree/api_tester.py @@ -109,12 +109,12 @@ class InvenTreeAPITestCase(APITestCase): return response - def patch(self, url, data, expected_code=None): + def patch(self, url, data, files=None, expected_code=None): """ Issue a PATCH request """ - response = self.client.patch(url, data=data, format='json') + response = self.client.patch(url, data=data, files=files, format='json') if expected_code is not None: self.assertEqual(response.status_code, expected_code) diff --git a/InvenTree/InvenTree/ready.py b/InvenTree/InvenTree/ready.py index 4acbcae9af..7d63861f4b 100644 --- a/InvenTree/InvenTree/ready.py +++ b/InvenTree/InvenTree/ready.py @@ -12,7 +12,7 @@ def isInTestMode(): return False -def canAppAccessDatabase(): +def canAppAccessDatabase(allow_test=False): """ Returns True if the apps.py file can access database records. @@ -39,6 +39,10 @@ def canAppAccessDatabase(): 'compilemessages', ] + if not allow_test: + # Override for testing mode? + excluded_commands.append('test') + for cmd in excluded_commands: if cmd in sys.argv: return False diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 032e10bf81..f093255ff0 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -103,7 +103,7 @@ class InvenTreeAttachmentSerializerField(serializers.FileField): return os.path.join(str(settings.MEDIA_URL), str(value)) -class InvenTreeImageSerializerField(serializers.FileField): +class InvenTreeImageSerializerField(serializers.ImageField): """ Custom image serializer. On upload, validate that the file is a valid image file diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 3681be0f0b..08fa5e0ae4 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -31,8 +31,6 @@ v3 -> 2021-05-22: """ - - def inventreeInstanceName(): """ Returns the InstanceName settings for the current database """ return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "") diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 176149c880..d606b12637 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- -from rest_framework import status +import PIL from django.urls import reverse +from rest_framework import status +from rest_framework.test import APIClient + +from InvenTree.api_tester import InvenTreeAPITestCase +from InvenTree.status_codes import StockStatus + from part.models import Part, PartCategory from stock.models import StockItem from company.models import Company -from InvenTree.api_tester import InvenTreeAPITestCase -from InvenTree.status_codes import StockStatus - class PartAPITest(InvenTreeAPITestCase): """ @@ -473,6 +476,74 @@ class PartDetailTests(InvenTreeAPITestCase): self.assertEqual(response.status_code, 200) + def test_image_upload(self): + """ + Test that we can upload an image to the part API + """ + + self.assignRole('part.add') + + # Create a new part + response = self.client.post( + reverse('api-part-list'), + { + 'name': 'imagine', + 'description': 'All the people', + 'category': 1, + }, + expected_code=201 + ) + + pk = response.data['pk'] + + url = reverse('api-part-detail', kwargs={'pk': pk}) + + p = Part.objects.get(pk=pk) + + # Part should not have an image! + with self.assertRaises(ValueError): + print(p.image.file) + + # Create a custom APIClient for file uploads + # Ref: https://stackoverflow.com/questions/40453947/how-to-generate-a-file-upload-test-request-with-django-rest-frameworks-apireq + upload_client = APIClient() + upload_client.force_authenticate(user=self.user) + + # Try to upload a non-image file + with open('dummy_image.txt', 'w') as dummy_image: + dummy_image.write('hello world') + + with open('dummy_image.txt', 'rb') as dummy_image: + response = upload_client.patch( + url, + { + 'image': dummy_image, + }, + format='multipart', + ) + + self.assertEqual(response.status_code, 400) + + # Now try to upload a valid image file + img = PIL.Image.new('RGB', (128, 128), color='red') + img.save('dummy_image.jpg') + + with open('dummy_image.jpg', 'rb') as dummy_image: + response = upload_client.patch( + url, + { + 'image': dummy_image, + }, + format='multipart', + ) + + self.assertEqual(response.status_code, 200) + + # And now check that the image has been set + p = Part.objects.get(pk=pk) + + print("Image:", p.image.file) + class PartAPIAggregationTest(InvenTreeAPITestCase): """ diff --git a/InvenTree/users/apps.py b/InvenTree/users/apps.py index a9f671895d..f6666e3a94 100644 --- a/InvenTree/users/apps.py +++ b/InvenTree/users/apps.py @@ -13,7 +13,7 @@ class UsersConfig(AppConfig): def ready(self): - if canAppAccessDatabase(): + if canAppAccessDatabase(allow_test=True): try: self.assign_permissions() diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 09d70c3501..2763ba0e10 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -276,7 +276,7 @@ def update_group_roles(group, debug=False): """ - if not canAppAccessDatabase(): + if not canAppAccessDatabase(allow_test=True): return # List of permissions already associated with this group