Skip to content

Commit 771efec

Browse files
Merge pull request #1127 from SchrodingersGat/settings-view-unit-test
Extra unit testing for settings forms / views
2 parents 0bb8c0a + 2e84250 commit 771efec

File tree

5 files changed

+197
-19
lines changed

5 files changed

+197
-19
lines changed

InvenTree/InvenTree/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'),
8383
url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'),
8484

85-
url(r'^(?P<pk>\d+)/edit/?', SettingEdit.as_view(), name='setting-edit'),
85+
url(r'^(?P<pk>\d+)/edit/', SettingEdit.as_view(), name='setting-edit'),
8686

8787
# Catch any other urls
8888
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'),

InvenTree/common/models.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99
import os
1010

11-
from django.db import models
11+
from django.db import models, transaction
12+
from django.db.utils import IntegrityError, OperationalError
1213
from django.conf import settings
1314

1415
import djmoney.settings
1516
from djmoney.models.fields import MoneyField
1617
from djmoney.contrib.exchange.models import convert_money
1718
from djmoney.contrib.exchange.exceptions import MissingRate
1819

19-
from django.db.utils import OperationalError
2020
from django.utils.translation import ugettext as _
2121
from django.core.validators import MinValueValidator
2222
from django.core.exceptions import ValidationError
@@ -230,7 +230,7 @@ def get_setting_validator(cls, key):
230230
return None
231231

232232
@classmethod
233-
def get_default_value(cls, key):
233+
def get_setting_default(cls, key):
234234
"""
235235
Return the default value for a particular setting.
236236
@@ -281,20 +281,23 @@ def get_setting_object(cls, key):
281281

282282
try:
283283
setting = InvenTreeSetting.objects.filter(key__iexact=key).first()
284-
except OperationalError:
285-
# Settings table has not been created yet!
286-
return None
287284
except (ValueError, InvenTreeSetting.DoesNotExist):
288-
285+
setting = None
286+
except (IntegrityError, OperationalError):
287+
setting = None
288+
289+
# Setting does not exist! (Try to create it)
290+
if not setting:
291+
292+
setting = InvenTreeSetting(key=key, value=InvenTreeSetting.get_setting_default(key))
293+
289294
try:
290-
# Attempt Create the setting if it does not exist
291-
setting = InvenTreeSetting.create(
292-
key=key,
293-
value=InvenTreeSetting.get_default_value(key)
294-
)
295-
except OperationalError:
296-
# Settings table has not been created yet
297-
setting = None
295+
# Wrap this statement in "atomic", so it can be rolled back if it fails
296+
with transaction.atomic():
297+
setting.save()
298+
except (IntegrityError, OperationalError):
299+
# It might be the case that the database isn't created yet
300+
pass
298301

299302
return setting
300303

@@ -322,7 +325,7 @@ def get_setting(cls, key, backup_value=None):
322325

323326
# If no backup value is specified, atttempt to retrieve a "default" value
324327
if backup_value is None:
325-
backup_value = cls.get_default_value(key)
328+
backup_value = cls.get_setting_default(key)
326329

327330
setting = InvenTreeSetting.get_setting_object(key)
328331

@@ -380,7 +383,7 @@ def name(self):
380383

381384
@property
382385
def default_value(self):
383-
return InvenTreeSetting.get_default_value(self.key)
386+
return InvenTreeSetting.get_setting_default(self.key)
384387

385388
@property
386389
def description(self):
@@ -403,6 +406,9 @@ def clean(self):
403406
if validator is not None:
404407
self.run_validator(validator)
405408

409+
if self.is_bool():
410+
self.value = InvenTree.helpers.str2bool(self.value)
411+
406412
def run_validator(self, validator):
407413
"""
408414
Run a validator against the 'value' field for this InvenTreeSetting object.

InvenTree/common/test_views.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"""
2+
Unit tests for the views associated with the 'common' app
3+
"""
4+
5+
# -*- coding: utf-8 -*-
6+
from __future__ import unicode_literals
7+
8+
import json
9+
10+
from django.test import TestCase
11+
from django.urls import reverse
12+
from django.contrib.auth import get_user_model
13+
14+
from common.models import InvenTreeSetting
15+
16+
17+
class SettingsViewTest(TestCase):
18+
"""
19+
Tests for the settings management views
20+
"""
21+
22+
fixtures = [
23+
'settings',
24+
]
25+
26+
def setUp(self):
27+
super().setUp()
28+
29+
# Create a user (required to access the views / forms)
30+
self.user = get_user_model().objects.create_user(
31+
username='username',
32+
email='me@email.com',
33+
password='password',
34+
)
35+
36+
self.client.login(username='username', password='password')
37+
38+
def get_url(self, pk):
39+
return reverse('setting-edit', args=(pk,))
40+
41+
def get_setting(self, title):
42+
43+
return InvenTreeSetting.get_setting_object(title)
44+
45+
def get(self, url, status=200):
46+
47+
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
48+
49+
self.assertEqual(response.status_code, status)
50+
51+
data = json.loads(response.content)
52+
53+
return response, data
54+
55+
def post(self, url, data, valid=None):
56+
57+
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
58+
59+
json_data = json.loads(response.content)
60+
61+
# If a particular status code is required
62+
if valid is not None:
63+
if valid:
64+
self.assertEqual(json_data['form_valid'], True)
65+
else:
66+
self.assertEqual(json_data['form_valid'], False)
67+
68+
form_errors = json.loads(json_data['form_errors'])
69+
70+
return json_data, form_errors
71+
72+
def test_instance_name(self):
73+
"""
74+
Test that we can get the settings view for particular setting objects.
75+
"""
76+
77+
# Start with something basic - load the settings view for INVENTREE_INSTANCE
78+
setting = self.get_setting('INVENTREE_INSTANCE')
79+
80+
self.assertIsNotNone(setting)
81+
self.assertEqual(setting.value, 'My very first InvenTree Instance')
82+
83+
url = self.get_url(setting.pk)
84+
85+
self.get(url)
86+
87+
new_name = 'A new instance name!'
88+
89+
# Change the instance name via the form
90+
data, errors = self.post(url, {'value': new_name}, valid=True)
91+
92+
name = InvenTreeSetting.get_setting('INVENTREE_INSTANCE')
93+
94+
self.assertEqual(name, new_name)
95+
96+
def test_choices(self):
97+
"""
98+
Tests for a setting which has choices
99+
"""
100+
101+
setting = InvenTreeSetting.get_setting_object('INVENTREE_DEFAULT_CURRENCY')
102+
103+
# Default value!
104+
self.assertEqual(setting.value, 'USD')
105+
106+
url = self.get_url(setting.pk)
107+
108+
# Try posting an invalid currency option
109+
data, errors = self.post(url, {'value': 'XPQaaa'}, valid=False)
110+
111+
self.assertIsNotNone(errors.get('value'), None)
112+
113+
# Try posting a valid currency option
114+
data, errors = self.post(url, {'value': 'AUD'}, valid=True)
115+
116+
def test_binary_values(self):
117+
"""
118+
Test for binary value
119+
"""
120+
121+
setting = InvenTreeSetting.get_setting_object('PART_COMPONENT')
122+
123+
self.assertTrue(setting.as_bool())
124+
125+
url = self.get_url(setting.pk)
126+
127+
setting.value = True
128+
setting.save()
129+
130+
# Try posting some invalid values
131+
# The value should be "cleaned" and stay the same
132+
for value in ['', 'abc', 'cat', 'TRUETRUETRUE']:
133+
self.post(url, {'value': value}, valid=True)
134+
135+
# Try posting some valid (True) values
136+
for value in [True, 'True', '1', 'yes']:
137+
self.post(url, {'value': value}, valid=True)
138+
self.assertTrue(InvenTreeSetting.get_setting('PART_COMPONENT'))
139+
140+
# Try posting some valid (False) values
141+
for value in [False, 'False']:
142+
self.post(url, {'value': value}, valid=True)
143+
self.assertFalse(InvenTreeSetting.get_setting('PART_COMPONENT'))

InvenTree/common/tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_defaults(self):
7070

7171
for key in InvenTreeSetting.GLOBAL_SETTINGS.keys():
7272

73-
value = InvenTreeSetting.get_default_value(key)
73+
value = InvenTreeSetting.get_setting_default(key)
7474

7575
InvenTreeSetting.set_setting(key, value, self.user)
7676

InvenTree/common/views.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,32 @@ def get_form(self):
7272
form.fields['value'].help_text = description
7373

7474
return form
75+
76+
def validate(self, setting, form):
77+
"""
78+
Perform custom validation checks on the form data.
79+
"""
80+
81+
data = form.cleaned_data
82+
83+
value = data.get('value', None)
84+
85+
if setting.choices():
86+
"""
87+
If a set of choices are provided for a given setting,
88+
the provided value must be one of those choices.
89+
"""
90+
91+
choices = [choice[0] for choice in setting.choices()]
92+
93+
if value not in choices:
94+
form.add_error('value', _('Supplied value is not allowed'))
95+
96+
if setting.is_bool():
97+
"""
98+
If a setting is defined as a boolean setting,
99+
the provided value must look somewhat like a boolean value!
100+
"""
101+
102+
if not str2bool(value, test=True) and not str2bool(value, test=False):
103+
form.add_error('value', _('Supplied value must be a boolean'))

0 commit comments

Comments
 (0)