2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 20:16:44 +00:00

Merge pull request #1127 from SchrodingersGat/settings-view-unit-test

Extra unit testing for settings forms / views
This commit is contained in:
Oliver 2020-11-14 09:19:31 +11:00 committed by GitHub
commit 771efecaa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 197 additions and 19 deletions

View File

@ -82,7 +82,7 @@ settings_urls = [
url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'), url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'),
url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'), url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'),
url(r'^(?P<pk>\d+)/edit/?', SettingEdit.as_view(), name='setting-edit'), url(r'^(?P<pk>\d+)/edit/', SettingEdit.as_view(), name='setting-edit'),
# Catch any other urls # Catch any other urls
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'), url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'),

View File

@ -8,7 +8,8 @@ from __future__ import unicode_literals
import os import os
from django.db import models from django.db import models, transaction
from django.db.utils import IntegrityError, OperationalError
from django.conf import settings from django.conf import settings
import djmoney.settings import djmoney.settings
@ -16,7 +17,6 @@ from djmoney.models.fields import MoneyField
from djmoney.contrib.exchange.models import convert_money from djmoney.contrib.exchange.models import convert_money
from djmoney.contrib.exchange.exceptions import MissingRate from djmoney.contrib.exchange.exceptions import MissingRate
from django.db.utils import OperationalError
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -230,7 +230,7 @@ class InvenTreeSetting(models.Model):
return None return None
@classmethod @classmethod
def get_default_value(cls, key): def get_setting_default(cls, key):
""" """
Return the default value for a particular setting. Return the default value for a particular setting.
@ -281,20 +281,23 @@ class InvenTreeSetting(models.Model):
try: try:
setting = InvenTreeSetting.objects.filter(key__iexact=key).first() setting = InvenTreeSetting.objects.filter(key__iexact=key).first()
except OperationalError:
# Settings table has not been created yet!
return None
except (ValueError, InvenTreeSetting.DoesNotExist): except (ValueError, InvenTreeSetting.DoesNotExist):
setting = None
except (IntegrityError, OperationalError):
setting = None
# Setting does not exist! (Try to create it)
if not setting:
setting = InvenTreeSetting(key=key, value=InvenTreeSetting.get_setting_default(key))
try: try:
# Attempt Create the setting if it does not exist # Wrap this statement in "atomic", so it can be rolled back if it fails
setting = InvenTreeSetting.create( with transaction.atomic():
key=key, setting.save()
value=InvenTreeSetting.get_default_value(key) except (IntegrityError, OperationalError):
) # It might be the case that the database isn't created yet
except OperationalError: pass
# Settings table has not been created yet
setting = None
return setting return setting
@ -322,7 +325,7 @@ class InvenTreeSetting(models.Model):
# If no backup value is specified, atttempt to retrieve a "default" value # If no backup value is specified, atttempt to retrieve a "default" value
if backup_value is None: if backup_value is None:
backup_value = cls.get_default_value(key) backup_value = cls.get_setting_default(key)
setting = InvenTreeSetting.get_setting_object(key) setting = InvenTreeSetting.get_setting_object(key)
@ -380,7 +383,7 @@ class InvenTreeSetting(models.Model):
@property @property
def default_value(self): def default_value(self):
return InvenTreeSetting.get_default_value(self.key) return InvenTreeSetting.get_setting_default(self.key)
@property @property
def description(self): def description(self):
@ -403,6 +406,9 @@ class InvenTreeSetting(models.Model):
if validator is not None: if validator is not None:
self.run_validator(validator) self.run_validator(validator)
if self.is_bool():
self.value = InvenTree.helpers.str2bool(self.value)
def run_validator(self, validator): def run_validator(self, validator):
""" """
Run a validator against the 'value' field for this InvenTreeSetting object. Run a validator against the 'value' field for this InvenTreeSetting object.

View File

@ -0,0 +1,143 @@
"""
Unit tests for the views associated with the 'common' app
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from common.models import InvenTreeSetting
class SettingsViewTest(TestCase):
"""
Tests for the settings management views
"""
fixtures = [
'settings',
]
def setUp(self):
super().setUp()
# Create a user (required to access the views / forms)
self.user = get_user_model().objects.create_user(
username='username',
email='me@email.com',
password='password',
)
self.client.login(username='username', password='password')
def get_url(self, pk):
return reverse('setting-edit', args=(pk,))
def get_setting(self, title):
return InvenTreeSetting.get_setting_object(title)
def get(self, url, status=200):
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, status)
data = json.loads(response.content)
return response, data
def post(self, url, data, valid=None):
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
json_data = json.loads(response.content)
# If a particular status code is required
if valid is not None:
if valid:
self.assertEqual(json_data['form_valid'], True)
else:
self.assertEqual(json_data['form_valid'], False)
form_errors = json.loads(json_data['form_errors'])
return json_data, form_errors
def test_instance_name(self):
"""
Test that we can get the settings view for particular setting objects.
"""
# Start with something basic - load the settings view for INVENTREE_INSTANCE
setting = self.get_setting('INVENTREE_INSTANCE')
self.assertIsNotNone(setting)
self.assertEqual(setting.value, 'My very first InvenTree Instance')
url = self.get_url(setting.pk)
self.get(url)
new_name = 'A new instance name!'
# Change the instance name via the form
data, errors = self.post(url, {'value': new_name}, valid=True)
name = InvenTreeSetting.get_setting('INVENTREE_INSTANCE')
self.assertEqual(name, new_name)
def test_choices(self):
"""
Tests for a setting which has choices
"""
setting = InvenTreeSetting.get_setting_object('INVENTREE_DEFAULT_CURRENCY')
# Default value!
self.assertEqual(setting.value, 'USD')
url = self.get_url(setting.pk)
# Try posting an invalid currency option
data, errors = self.post(url, {'value': 'XPQaaa'}, valid=False)
self.assertIsNotNone(errors.get('value'), None)
# Try posting a valid currency option
data, errors = self.post(url, {'value': 'AUD'}, valid=True)
def test_binary_values(self):
"""
Test for binary value
"""
setting = InvenTreeSetting.get_setting_object('PART_COMPONENT')
self.assertTrue(setting.as_bool())
url = self.get_url(setting.pk)
setting.value = True
setting.save()
# Try posting some invalid values
# The value should be "cleaned" and stay the same
for value in ['', 'abc', 'cat', 'TRUETRUETRUE']:
self.post(url, {'value': value}, valid=True)
# Try posting some valid (True) values
for value in [True, 'True', '1', 'yes']:
self.post(url, {'value': value}, valid=True)
self.assertTrue(InvenTreeSetting.get_setting('PART_COMPONENT'))
# Try posting some valid (False) values
for value in [False, 'False']:
self.post(url, {'value': value}, valid=True)
self.assertFalse(InvenTreeSetting.get_setting('PART_COMPONENT'))

View File

@ -70,7 +70,7 @@ class SettingsTest(TestCase):
for key in InvenTreeSetting.GLOBAL_SETTINGS.keys(): for key in InvenTreeSetting.GLOBAL_SETTINGS.keys():
value = InvenTreeSetting.get_default_value(key) value = InvenTreeSetting.get_setting_default(key)
InvenTreeSetting.set_setting(key, value, self.user) InvenTreeSetting.set_setting(key, value, self.user)

View File

@ -72,3 +72,32 @@ class SettingEdit(AjaxUpdateView):
form.fields['value'].help_text = description form.fields['value'].help_text = description
return form return form
def validate(self, setting, form):
"""
Perform custom validation checks on the form data.
"""
data = form.cleaned_data
value = data.get('value', None)
if setting.choices():
"""
If a set of choices are provided for a given setting,
the provided value must be one of those choices.
"""
choices = [choice[0] for choice in setting.choices()]
if value not in choices:
form.add_error('value', _('Supplied value is not allowed'))
if setting.is_bool():
"""
If a setting is defined as a boolean setting,
the provided value must look somewhat like a boolean value!
"""
if not str2bool(value, test=True) and not str2bool(value, test=False):
form.add_error('value', _('Supplied value must be a boolean'))