diff --git a/InvenTree/InvenTree/api_tester.py b/InvenTree/InvenTree/api_tester.py index a803e6797f..1cbc62ec0a 100644 --- a/InvenTree/InvenTree/api_tester.py +++ b/InvenTree/InvenTree/api_tester.py @@ -18,6 +18,7 @@ class InvenTreeAPITestCase(APITestCase): email = 'test@testing.com' superuser = False + is_staff = True auto_login = True # Set list of roles automatically associated with the user @@ -40,8 +41,12 @@ class InvenTreeAPITestCase(APITestCase): if self.superuser: self.user.is_superuser = True - self.user.save() + if self.is_staff: + self.user.is_staff = True + + self.user.save() + for role in self.roles: self.assignRole(role) diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index f093255ff0..50a37d8cba 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -2,17 +2,19 @@ Serializers used in various InvenTree apps """ - # -*- coding: utf-8 -*- from __future__ import unicode_literals import os +from collections import OrderedDict + from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError as DjangoValidationError from rest_framework import serializers +from rest_framework.utils import model_meta from rest_framework.fields import empty from rest_framework.exceptions import ValidationError @@ -42,6 +44,72 @@ class InvenTreeModelSerializer(serializers.ModelSerializer): but also ensures that the underlying model class data are checked on validation. """ + def __init__(self, instance=None, data=empty, **kwargs): + + self.instance = instance + + # If instance is None, we are creating a new instance + if instance is None: + + if data is empty: + data = OrderedDict() + else: + # Required to side-step immutability of a QueryDict + data = data.copy() + + # Add missing fields which have default values + ModelClass = self.Meta.model + + fields = model_meta.get_field_info(ModelClass) + + for field_name, field in fields.fields.items(): + + if field.has_default() and field_name not in data: + + value = field.default + + # Account for callable functions + if callable(value): + try: + value = value() + except: + continue + + data[field_name] = value + + super().__init__(instance, data, **kwargs) + + def get_initial(self): + """ + Construct initial data for the serializer. + Use the 'default' values specified by the django model definition + """ + + initials = super().get_initial() + + # Are we creating a new instance? + if self.instance is None: + ModelClass = self.Meta.model + + fields = model_meta.get_field_info(ModelClass) + + for field_name, field in fields.fields.items(): + + if field.has_default() and field_name not in initials: + + value = field.default + + # Account for callable functions + if callable(value): + try: + value = value() + except: + continue + + initials[field_name] = value + + return initials + def run_validation(self, data=empty): """ Perform serializer validation. In addition to running validators on the serializer fields, diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index d606b12637..4922ed4e04 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -13,6 +13,7 @@ from InvenTree.status_codes import StockStatus from part.models import Part, PartCategory from stock.models import StockItem from company.models import Company +from common.models import InvenTreeSetting class PartAPITest(InvenTreeAPITestCase): @@ -311,6 +312,59 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(len(data['results']), n) + def test_default_values(self): + """ + Tests for 'default' values: + + Ensure that unspecified fields revert to "default" values + (as specified in the model field definition) + """ + + url = reverse('api-part-list') + + response = self.client.post(url, { + 'name': 'all defaults', + 'description': 'my test part', + 'category': 1, + }) + + data = response.data + + # Check that the un-specified fields have used correct default values + self.assertTrue(data['active']) + self.assertFalse(data['virtual']) + + # By default, parts are not purchaseable + self.assertFalse(data['purchaseable']) + + # Set the default 'purchaseable' status to True + InvenTreeSetting.set_setting( + 'PART_PURCHASEABLE', + True, + self.user + ) + + response = self.client.post(url, { + 'name': 'all defaults', + 'description': 'my test part 2', + 'category': 1, + }) + + # Part should now be purchaseable by default + self.assertTrue(response.data['purchaseable']) + + # "default" values should not be used if the value is specified + response = self.client.post(url, { + 'name': 'all defaults', + 'description': 'my test part 2', + 'category': 1, + 'active': False, + 'purchaseable': False, + }) + + self.assertFalse(response.data['active']) + self.assertFalse(response.data['purchaseable']) + class PartDetailTests(InvenTreeAPITestCase): """ @@ -391,7 +445,14 @@ class PartDetailTests(InvenTreeAPITestCase): # Try to remove the part response = self.client.delete(url) - + + # As the part is 'active' we cannot delete it + self.assertEqual(response.status_code, 405) + + # So, let's make it not active + response = self.patch(url, {'active': False}, expected_code=200) + + response = self.client.delete(url) self.assertEqual(response.status_code, 204) # Part count should have reduced @@ -542,8 +603,6 @@ class PartDetailTests(InvenTreeAPITestCase): # 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/stock/test_api.py b/InvenTree/stock/test_api.py index e1fb616335..729bf25a9b 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -354,16 +354,18 @@ class StockItemTest(StockAPITestCase): self.assertContains(response, 'does not exist', status_code=status.HTTP_400_BAD_REQUEST) # POST without quantity - response = self.client.post( + response = self.post( self.list_url, - data={ + { 'part': 1, 'location': 1, - } + }, + expected_code=201, ) - self.assertContains(response, 'This field is required', status_code=status.HTTP_400_BAD_REQUEST) - + # Item should have been created with default quantity + self.assertEqual(response.data['quantity'], 1) + # POST with quantity and part and location response = self.client.post( self.list_url,