From 4a8170079e2b715d40e94f5d407d110a635f8a5d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 12:31:03 +1100 Subject: [PATCH 1/8] Remove code which automatically created settings objects on server launch --- InvenTree/common/apps.py | 85 +--------------------------------------- 1 file changed, 1 insertion(+), 84 deletions(-) diff --git a/InvenTree/common/apps.py b/InvenTree/common/apps.py index 06b825c574..0acc72c377 100644 --- a/InvenTree/common/apps.py +++ b/InvenTree/common/apps.py @@ -6,87 +6,4 @@ class CommonConfig(AppConfig): name = 'common' def ready(self): - - """ Will be called when the Common app is first loaded """ - self.add_instance_name() - self.add_default_settings() - - def add_instance_name(self): - """ - Check if an InstanceName has been defined for this database. - If not, create a random one! - """ - - # See note above - from .models import InvenTreeSetting - - """ - Note: The "old" instance name was stored under the key 'InstanceName', - but has now been renamed to 'INVENTREE_INSTANCE'. - """ - - try: - - # Quick exit if a value already exists for 'inventree_instance' - if InvenTreeSetting.objects.filter(key='INVENTREE_INSTANCE').exists(): - return - - # Default instance name - instance_name = InvenTreeSetting.get_default_value('INVENTREE_INSTANCE') - - # Use the old name if it exists - if InvenTreeSetting.objects.filter(key='InstanceName').exists(): - instance = InvenTreeSetting.objects.get(key='InstanceName') - instance_name = instance.value - - # Delete the legacy key - instance.delete() - - # Create new value - InvenTreeSetting.objects.create( - key='INVENTREE_INSTANCE', - value=instance_name - ) - - except (OperationalError, ProgrammingError, IntegrityError): - # Migrations have not yet been applied - table does not exist - pass - - def add_default_settings(self): - """ - Create all required settings, if they do not exist. - """ - - from .models import InvenTreeSetting - - for key in InvenTreeSetting.GLOBAL_SETTINGS.keys(): - try: - settings = InvenTreeSetting.objects.filter(key__iexact=key) - - if settings.count() == 0: - value = InvenTreeSetting.get_default_value(key) - - print(f"Creating default setting for {key} -> '{value}'") - - InvenTreeSetting.objects.create( - key=key, - value=value - ) - - return - - elif settings.count() > 1: - # Prevent multiple shadow copies of the same setting! - for setting in settings[1:]: - setting.delete() - - # Ensure that the key has the correct case - setting = settings[0] - - if not setting.key == key: - setting.key = key - setting.save() - - except (OperationalError, ProgrammingError, IntegrityError): - # Table might not yet exist - pass + pass From ec8d8e5a644c5f44e78a9ace2e77273cf88b81d3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 13:31:27 +1100 Subject: [PATCH 2/8] Add more invoke commands: - export-records: Exports all database records to external file - import-records: Imports database records from external file - import-fixtures: Fills the database with dummy records --- .travis.yml | 12 ++++ InvenTree/common/apps.py | 3 +- InvenTree/common/fixtures/currency.yaml | 16 ----- tasks.py | 87 +++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 17 deletions(-) delete mode 100644 InvenTree/common/fixtures/currency.yaml diff --git a/.travis.yml b/.travis.yml index 67d8c0502a..4f772f6d5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,11 +30,23 @@ before_install: script: - cd InvenTree && python3 manage.py makemigrations && cd .. - python3 ci/check_migration_files.py + # Run unit testing / code coverage tests - invoke coverage + # Run unit test for SQL database backend - cd InvenTree && python3 manage.py test --settings=InvenTree.ci_mysql && cd .. + # Run unit test for PostgreSQL database backend - cd InvenTree && python3 manage.py test --settings=InvenTree.ci_postgresql && cd .. - invoke translate - invoke style + # Create an empty database and fill it with test data + - rm inventree_db.sqlite3 + - invoke migrate + - invoke import-fixtures + # Export database records + - invoke export-records -f data.json + # Create a new empty database and import the saved data + - rm inventree_db.sqlite3 + - invoke import-records -f data.json after_success: - coveralls \ No newline at end of file diff --git a/InvenTree/common/apps.py b/InvenTree/common/apps.py index 0acc72c377..34b43fc68b 100644 --- a/InvenTree/common/apps.py +++ b/InvenTree/common/apps.py @@ -1,5 +1,6 @@ +# -*- coding: utf-8 -*- + from django.apps import AppConfig -from django.db.utils import OperationalError, ProgrammingError, IntegrityError class CommonConfig(AppConfig): diff --git a/InvenTree/common/fixtures/currency.yaml b/InvenTree/common/fixtures/currency.yaml deleted file mode 100644 index 639b0751df..0000000000 --- a/InvenTree/common/fixtures/currency.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Test fixtures for Currency objects - -- model: common.currency - fields: - symbol: '$' - suffix: 'AUD' - description: 'Australian Dollars' - base: True - -- model: common.currency - fields: - symbol: '$' - suffix: 'USD' - description: 'US Dollars' - base: False - value: 1.4 \ No newline at end of file diff --git a/tasks.py b/tasks.py index 49f3f9445b..0c33fcb4c7 100644 --- a/tasks.py +++ b/tasks.py @@ -238,6 +238,93 @@ def postgresql(c): c.run('sudo apt-get install postgresql postgresql-contrib libpq-dev') c.run('pip3 install psycopg2') +@task(help={'filename': "Output filename (default = 'data.json')"}) +def export_records(c, filename='data.json'): + """ + Export all database records to a file + """ + + # Get an absolute path to the file + if not os.path.isabs(filename): + filename = os.path.join(localDir(), filename) + filename = os.path.abspath(filename) + + print(f"Exporting database records to file '{filename}'") + + if os.path.exists(filename): + response = input("Warning: file already exists. Do you want to overwrite? [y/N]: ") + response = str(response).strip().lower() + + if response not in ['y', 'yes']: + print("Cancelled export operation") + return 0 + + cmd = f'dumpdata --exclude contenttypes --exclude auth.permission --indent 2 --output {filename}' + + manage(c, cmd, pty=True) + +@task(help={'filename': 'Input filename'}) +def import_records(c, filename='data.json'): + """ + Import database records from a file + """ + + # Get an absolute path to the supplied filename + if not os.path.isabs(filename): + filename = os.path.join(localDir(), filename) + + if not os.path.exists(filename): + print(f"Error: File '{filename}' does not exist") + return -1 + + print(f"Importing database records from '{filename}'") + + cmd = f'loaddata {filename}' + + manage(c, cmd, pty=True) + +@task +def import_fixtures(c): + """ + Import fixture data into the database. + + This command imports all existing test fixture data into the database. + + Warning: + - Intended for testing / development only! + - Running this command may overwrite existing database data!! + - Don't say you were not warned... + """ + + fixtures = [ + # Build model + 'build', + + # Company model + 'company', + 'price_breaks', + 'supplier_part', + + # Order model + 'order', + + # Part model + 'bom', + 'category', + 'params', + 'part', + 'test_templates', + + # Stock model + 'location', + 'stock_tests', + 'stock', + ] + + command = 'loaddata ' + ' '.join(fixtures) + + manage(c, command, pty=True) + @task def backup(c): """ From 1738df90427b99762149a574f6779f7293e152c2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 14:48:57 +1100 Subject: [PATCH 3/8] Update unit tests --- InvenTree/common/tests.py | 33 +++++++++++++++++++-------------- tasks.py | 3 +++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 323049f164..365b9d0980 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -4,20 +4,7 @@ from __future__ import unicode_literals from django.test import TestCase from django.contrib.auth import get_user_model -from .models import Currency, InvenTreeSetting - - -class CurrencyTest(TestCase): - """ Tests for Currency model """ - - fixtures = [ - 'currency', - ] - - def test_currency(self): - # Simple test for now (improve this later!) - - self.assertEqual(Currency.objects.count(), 2) +from .models import InvenTreeSetting class SettingsTest(TestCase): @@ -25,6 +12,10 @@ class SettingsTest(TestCase): Tests for the 'settings' model """ + fixtures = [ + 'settings', + ] + def setUp(self): User = get_user_model() @@ -35,6 +26,20 @@ class SettingsTest(TestCase): self.client.login(username='username', password='password') + def test_settings_objects(self): + + # There should be two settings objects in the database + settings = InvenTreeSetting.objects.all() + + self.assertEqual(settings.count(), 2) + + instance_name = InvenTreeSetting.objects.get(pk=1) + self.assertEqual(instance_name.key, 'INVENTREE_INSTANCE') + self.assertEqual(instance_name.value, 'My very first InvenTree Instance') + + self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 21) + + def test_required_values(self): """ - Ensure that every global setting has a name. diff --git a/tasks.py b/tasks.py index 0c33fcb4c7..f4ddf5e0b6 100644 --- a/tasks.py +++ b/tasks.py @@ -300,6 +300,9 @@ def import_fixtures(c): # Build model 'build', + # Common models + 'settings', + # Company model 'company', 'price_breaks', From 21315096d464583e81fc0eb5ce957b8e6789dff1 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 14:53:49 +1100 Subject: [PATCH 4/8] Further unit testing fixes --- InvenTree/common/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 365b9d0980..649ded617f 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -37,7 +37,8 @@ class SettingsTest(TestCase): self.assertEqual(instance_name.key, 'INVENTREE_INSTANCE') self.assertEqual(instance_name.value, 'My very first InvenTree Instance') - self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 21) + # Check object lookup (case insensitive) + self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 1) def test_required_values(self): From fe9749ba4f9bfd375cc9ace3a692448a2aace569 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 14:54:03 +1100 Subject: [PATCH 5/8] Add missing fixture for settings --- InvenTree/common/fixtures/settings.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 InvenTree/common/fixtures/settings.yaml diff --git a/InvenTree/common/fixtures/settings.yaml b/InvenTree/common/fixtures/settings.yaml new file mode 100644 index 0000000000..70ce23f312 --- /dev/null +++ b/InvenTree/common/fixtures/settings.yaml @@ -0,0 +1,13 @@ +# Sample settings objects + +- model: common.InvenTreeSetting + pk: 1 + fields: + key: INVENTREE_INSTANCE + value: "My very first InvenTree Instance" + +- model: common.InvenTreeSetting + pk: 2 + fields: + key: INVENTREE_COMPANY_NAME + value: "ACME Pty Ltd" \ No newline at end of file From 96ef5e1bde25df1e9991b8d92ad6f28ba34a3562 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 15:37:21 +1100 Subject: [PATCH 6/8] Travis fixes --- .travis.yml | 4 ++-- InvenTree/common/tests.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f772f6d5d..150afdd763 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,13 +39,13 @@ script: - invoke translate - invoke style # Create an empty database and fill it with test data - - rm inventree_db.sqlite3 + - rm inventree_default_db.sqlite3 - invoke migrate - invoke import-fixtures # Export database records - invoke export-records -f data.json # Create a new empty database and import the saved data - - rm inventree_db.sqlite3 + - rm inventree_default_db.sqlite3 - invoke import-records -f data.json after_success: diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 649ded617f..26a3f36aa2 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -40,7 +40,6 @@ class SettingsTest(TestCase): # Check object lookup (case insensitive) self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 1) - def test_required_values(self): """ - Ensure that every global setting has a name. From 563bfe9bf572b50fb0a1a9880d5bf1690e9d0f89 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 16:10:00 +1100 Subject: [PATCH 7/8] Further fixes to tasks.py --- tasks.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tasks.py b/tasks.py index f4ddf5e0b6..30641c7c81 100644 --- a/tasks.py +++ b/tasks.py @@ -6,6 +6,7 @@ from shutil import copyfile import random import string import os +import sys def apps(): """ @@ -257,11 +258,11 @@ def export_records(c, filename='data.json'): if response not in ['y', 'yes']: print("Cancelled export operation") - return 0 + sys.exit(1) - cmd = f'dumpdata --exclude contenttypes --exclude auth.permission --indent 2 --output {filename}' + cmd = f'dumpdata --exclude contenttypes --exclude auth.permission --indent 2 --output {filename}' - manage(c, cmd, pty=True) + manage(c, cmd, pty=True) @task(help={'filename': 'Input filename'}) def import_records(c, filename='data.json'): @@ -275,7 +276,7 @@ def import_records(c, filename='data.json'): if not os.path.exists(filename): print(f"Error: File '{filename}' does not exist") - return -1 + sys.exit(1) print(f"Importing database records from '{filename}'") From 4765065eb098f530f6e023e858ce48cfe75ddba7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 12 Nov 2020 16:41:43 +1100 Subject: [PATCH 8/8] Make sure to run database migrations first! (DUH) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 150afdd763..52d0ef1c5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,7 @@ script: - invoke export-records -f data.json # Create a new empty database and import the saved data - rm inventree_default_db.sqlite3 + - invoke migrate - invoke import-records -f data.json after_success: