From f4debeac471f0f1de56a3cfa0fdb942dc1c0e285 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 20:39:43 +1000 Subject: [PATCH 01/11] Moving to "invoke" framework - Add "migrate" test --- tasks.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tasks.py diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000000..c02fc45587 --- /dev/null +++ b/tasks.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +from invoke import task + +import os + +def localDir(): + """ + Returns the directory of *THIS* file. + Used to ensure that the various scripts always run + in the correct directory. + """ + return os.path.dirname(os.path.abspath(__file__)) + +def managePyDir(): + """ + Returns the directory of the manage.py file + """ + + return os.path.join(localDir(), 'InvenTree') + +def managePyPath(): + """ + Return the path of the manage.py file + """ + + return os.path.join(managePyDir, 'manage.py') + +def manage(c, cmd): + """ + Runs a given command against django's "manage.py" script. + + Args: + c - Command line context + cmd - django command to run + """ + + c.run('cd {path} && python3 manage.py {cmd}'.format( + path=managePyDir(), + cmd=cmd + )) + +@task +def migrate(c): + """ + Performs database migrations. + This is a critical step if the database schema have been altered! + """ + + print("Running InvenTree database migrations...") + print("========================================") + + manage(c, "makemigrations") + manage(c, "migrate") + manage(c, "migrate --run-syncdb") + manage(c, "check") + + print("========================================") + print("InvenTree database migrations completed!") + +@task +def test(c): + print("hello!") \ No newline at end of file From 05fae4be8714d1f8d6501f6dc4d809b6ef5a5d73 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:08:04 +1000 Subject: [PATCH 02/11] Are more methods to tasks.py: - static - update - install - key - coverage The functionality of setup.py is now included here! --- InvenTree/setup.py | 68 ---------------------------- Makefile | 25 ----------- requirements.txt | 2 +- tasks.py | 110 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 108 insertions(+), 97 deletions(-) delete mode 100644 InvenTree/setup.py diff --git a/InvenTree/setup.py b/InvenTree/setup.py deleted file mode 100644 index b74cfe7358..0000000000 --- a/InvenTree/setup.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Performs initial setup functions. - -- Generates a Django SECRET_KEY file to be used by manage.py -- Copies config template file (if a config file does not already exist) -""" - -import random -import string -import os -import sys -import argparse -from shutil import copyfile - -OUTPUT_DIR = os.path.dirname(os.path.realpath(__file__)) - -KEY_FN = 'secret_key.txt' -CONFIG_FN = 'config.yaml' -CONFIG_TEMPLATE_FN = 'config_template.yaml' - - -def generate_key(length=50): - """ Generate a random string - - Args: - length: Number of characters in returned string (default = 50) - - Returns: - Randomized secret key string - """ - - options = string.digits + string.ascii_letters + string.punctuation - key = ''.join([random.choice(options) for i in range(length)]) - return key - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser(description='Generate Django SECRET_KEY file') - parser.add_argument('--force', '-f', help='Override existing files', action='store_true') - parser.add_argument('--dummy', '-d', help='Dummy run (do not create any files)', action='store_true') - - args = parser.parse_args() - - # Places to store files - key_filename = os.path.join(OUTPUT_DIR, KEY_FN) - conf_template = os.path.join(OUTPUT_DIR, CONFIG_TEMPLATE_FN) - conf_filename = os.path.join(OUTPUT_DIR, CONFIG_FN) - - # Generate secret key data - key_data = generate_key() - - if args.dummy: - print('SECRET_KEY: {k}'.format(k=key_data)) - sys.exit(0) - - if not args.force and os.path.exists(key_filename): - print("Key file already exists - '{f}'".format(f=key_filename)) - else: - with open(key_filename, 'w') as key_file: - print("Generating SECRET_KEY file - '{f}'".format(f=key_filename)) - key_file.write(key_data) - - if not args.force and os.path.exists(conf_filename): - print("Config file already exists (skipping)") - else: - print("Copying config template to 'config.yaml'") - copyfile(conf_template, conf_filename) diff --git a/Makefile b/Makefile index cc3a3043a6..50add70030 100644 --- a/Makefile +++ b/Makefile @@ -7,24 +7,6 @@ clean: rm -rf .tox rm -f .coverage -update: install migrate static - -# Perform database migrations (after schema changes are made) -migrate: - cd InvenTree && python3 manage.py makemigrations - cd InvenTree && python3 manage.py migrate - cd InvenTree && python3 manage.py migrate --run-syncdb - cd InvenTree && python3 manage.py check - -# Collect static files into the correct locations -static: - cd InvenTree && python3 manage.py collectstatic - -# Install all required packages -install: - pip3 install -U -r requirements.txt - cd InvenTree && python3 setup.py - # Create a superuser account superuser: cd InvenTree && python3 manage.py createsuperuser @@ -53,11 +35,6 @@ test: cd InvenTree && python3 manage.py check cd InvenTree && python3 manage.py test barcode build common company label order part report stock InvenTree -# Run code coverage -coverage: - cd InvenTree && python3 manage.py check - coverage run InvenTree/manage.py test barcode build common company label order part report stock InvenTree - coverage html # Install packages required to generate code docs docreqs: @@ -71,5 +48,3 @@ docs: backup: cd InvenTree && python3 manage.py dbbackup cd InvenTree && python3 manage.py mediabackup - -.PHONY: clean migrate superuser install mysql postgresql translate static style test coverage docreqs docs backup update \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 354c7ea316..3687341166 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ django-import-export==2.0.0 # Data import / export for admin interface django-cleanup==4.0.0 # Manage deletion of old / unused uploaded files django-qr-code==1.2.0 # Generate QR codes flake8==3.8.3 # PEP checking -coverage==4.0.3 # Unit test coverage +coverage==5.2.1 # Unit test coverage python-coveralls==2.9.1 # Coveralls linking (for Travis) rapidfuzz==0.7.6 # Fuzzy string matching django-stdimage==5.1.1 # Advanced ImageField management diff --git a/tasks.py b/tasks.py index c02fc45587..5a8d2e33dc 100644 --- a/tasks.py +++ b/tasks.py @@ -1,9 +1,30 @@ # -*- coding: utf-8 -*- from invoke import task +from shutil import copyfile +import random +import string import os +def apps(): + """ + Returns a list of installed apps + """ + + return [ + 'barcode', + 'build', + 'common', + 'company', + 'label', + 'order', + 'part', + 'report', + 'stock', + 'InvenTree' + ] + def localDir(): """ Returns the directory of *THIS* file. @@ -24,7 +45,7 @@ def managePyPath(): Return the path of the manage.py file """ - return os.path.join(managePyDir, 'manage.py') + return os.path.join(managePyDir(), 'manage.py') def manage(c, cmd): """ @@ -40,6 +61,45 @@ def manage(c, cmd): cmd=cmd )) +@task(help={'length': 'Length of secret key (default=50)'}) +def key(c, length=50, force=False): + """ + Generates a SECRET_KEY file which InvenTree uses for generating security hashes + """ + + SECRET_KEY_FILE = os.path.join(localDir(), 'InvenTree', 'secret_key.txt') + + # If a SECRET_KEY file does not exist, generate a new one! + if force or not os.path.exists(SECRET_KEY_FILE): + print("Generating SECRET_KEY file - " + SECRET_KEY_FILE) + with open(SECRET_KEY_FILE, 'w') as key_file: + options = string.digits + string.ascii_letters + string.punctuation + + key = ''.join([random.choice(options) for i in range(length)]) + + key_file.write(key) + + else: + print("SECRET_KEY file already exists - skipping") + + +@task(post=[key]) +def install(c): + """ + Installs required python packages, and runs initial setup functions. + """ + + # Install required Python packages with PIP + #c.run('pip3 install -U -r requirements.txt') + + # If a config.yaml file does not exist, copy from the template! + CONFIG_FILE = os.path.join(localDir(), 'InvenTree', 'config.yaml') + CONFIG_TEMPLATE_FILE = os.path.join(localDir(), 'InvenTree', 'config_template.yaml') + + if not os.path.exists(CONFIG_FILE): + print("Config file 'config.yaml' does not exist - copying from template.") + copyfile(CONFIG_TEMPLATE_FILE, CONFIG_FILE) + @task def migrate(c): """ @@ -58,6 +118,50 @@ def migrate(c): print("========================================") print("InvenTree database migrations completed!") + @task -def test(c): - print("hello!") \ No newline at end of file +def static(c): + """ + Copies required static files to the STATIC_ROOT directory, + as per Django requirements. + """ + + manage(c, "collectstatic") + + +@task(pre=[install, migrate, static]) +def update(c): + """ + Update InvenTree installation. + + This command should be invoked after source code has been updated, + e.g. downloading new code from GitHub. + + The following tasks are performed, in order: + + - install + - migrate + - static + """ + pass + +@task +def coverage(c): + """ + Run code-coverage of the InvenTree codebase, + using the 'coverage' code-analysis tools. + + Generates a code coverage report (available in the htmlcov directory) + """ + + # Run sanity check on the django install + manage(c, 'check') + + # Run coverage tests + c.run('coverage run {manage} test {apps}'.format( + manage=managePyPath(), + apps=' '.join(apps()) + )) + + # Generate coverage report + c.run('coverage html') \ No newline at end of file From ab75f85555fa5aa448618b4462d0fac3ae702cac Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:10:14 +1000 Subject: [PATCH 03/11] Migrate "test" command to invoke --- Makefile | 6 ------ tasks.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 50add70030..add60ce892 100644 --- a/Makefile +++ b/Makefile @@ -30,12 +30,6 @@ translate: style: flake8 InvenTree -# Run unit tests -test: - cd InvenTree && python3 manage.py check - cd InvenTree && python3 manage.py test barcode build common company label order part report stock InvenTree - - # Install packages required to generate code docs docreqs: pip3 install -U -r docs/requirements.txt diff --git a/tasks.py b/tasks.py index 5a8d2e33dc..c8738e346e 100644 --- a/tasks.py +++ b/tasks.py @@ -145,6 +145,20 @@ def update(c): """ pass +@task +def test(c): + """ + Run unit-tests for InvenTree codebase. + """ + + # Run sanity check on the django install + manage(c, 'check') + + # Run coverage tests + manage(c, 'test {apps}'.format( + apps=' '.join(apps()) + )) + @task def coverage(c): """ From ec1e646de0bc486e9943f8e07d6ba9cd22642378 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:12:05 +1000 Subject: [PATCH 04/11] Migrate "style" task --- Makefile | 3 --- tasks.py | 9 +++++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index add60ce892..085c55ebf7 100644 --- a/Makefile +++ b/Makefile @@ -26,9 +26,6 @@ translate: cd InvenTree && python3 manage.py makemessages cd InvenTree && python3 manage.py compilemessages -# Run PEP style checks against source code -style: - flake8 InvenTree # Install packages required to generate code docs docreqs: diff --git a/tasks.py b/tasks.py index c8738e346e..0e369a9254 100644 --- a/tasks.py +++ b/tasks.py @@ -145,6 +145,15 @@ def update(c): """ pass +@task +def style(c): + """ + Run PEP style checks against InvenTree sourcecode + """ + + print("Running PEP style checks...") + c.run('flake8 InvenTree') + @task def test(c): """ From af50e29e2ceeaaf1bbe977560c64e22520a9476f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:13:28 +1000 Subject: [PATCH 05/11] Migrate "translate" command --- Makefile | 6 ------ tasks.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 085c55ebf7..1927dcbf03 100644 --- a/Makefile +++ b/Makefile @@ -21,12 +21,6 @@ postgresql: sudo apt-get install postgresql postgresql-contrib libpq-dev pip3 install psycopg2 -# Update translation files -translate: - cd InvenTree && python3 manage.py makemessages - cd InvenTree && python3 manage.py compilemessages - - # Install packages required to generate code docs docreqs: pip3 install -U -r docs/requirements.txt diff --git a/tasks.py b/tasks.py index 0e369a9254..9c9bc25c6e 100644 --- a/tasks.py +++ b/tasks.py @@ -145,6 +145,18 @@ def update(c): """ pass +@task +def translate(c): + """ + Regenerate translation files. + + Run this command after added new translatable strings, + or after adding translations for existing strings. + """ + + manage(c, "makemigrations") + manage(c, "compilemessages") + @task def style(c): """ From f5fd0fc5be68c4fd4f6b59d6a2cf95e82080f691 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:17:38 +1000 Subject: [PATCH 06/11] Migrate "mysql" and "postgresql" targets --- Makefile | 12 +----------- tasks.py | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 1927dcbf03..98df26bef6 100644 --- a/Makefile +++ b/Makefile @@ -10,17 +10,7 @@ clean: # Create a superuser account superuser: cd InvenTree && python3 manage.py createsuperuser - -# Install pre-requisites for mysql setup -mysql: - sudo apt-get install mysql-server libmysqlclient-dev - pip3 install mysqlclient - -# Install pre-requisites for postgresql setup -postgresql: - sudo apt-get install postgresql postgresql-contrib libpq-dev - pip3 install psycopg2 - + # Install packages required to generate code docs docreqs: pip3 install -U -r docs/requirements.txt diff --git a/tasks.py b/tasks.py index 9c9bc25c6e..7db6b51d74 100644 --- a/tasks.py +++ b/tasks.py @@ -199,4 +199,26 @@ def coverage(c): )) # Generate coverage report - c.run('coverage html') \ No newline at end of file + c.run('coverage html') + +@task +def mysql(c): + """ + Install packages required for using InvenTree with a MySQL database. + """ + + print('Installing packages required for MySQL') + + c.run('sudo apt-get install mysql-server libmysqlclient-dev') + c.run('pip3 install mysqlclient') + +@task +def postgresql(c): + """ + Install packages required for using InvenTree with a PostgreSQL database + """ + + print("Installing packages required for PostgreSQL") + + c.run('sudo apt-get install postgresql postgresql-contrib libpq-dev') + c.run('pip3 install psycopg2') From 592e87941d149d2c5dd2e6ea10a77c866f22536a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:24:02 +1000 Subject: [PATCH 07/11] Add more targets - superuser - backup --- Makefile | 25 ------------------------- tasks.py | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 25 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 98df26bef6..0000000000 --- a/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -clean: - find . -path '*/__pycache__/*' -delete - find . -type d -name '__pycache__' -empty -delete - find . -name *.pyc -o -name *.pyo -delete - rm -rf *.egg-info - rm -rf .cache - rm -rf .tox - rm -f .coverage - -# Create a superuser account -superuser: - cd InvenTree && python3 manage.py createsuperuser - -# Install packages required to generate code docs -docreqs: - pip3 install -U -r docs/requirements.txt - -# Build code docs -docs: - cd docs && make html - -# Make database backup -backup: - cd InvenTree && python3 manage.py dbbackup - cd InvenTree && python3 manage.py mediabackup diff --git a/tasks.py b/tasks.py index 7db6b51d74..4725e32ef1 100644 --- a/tasks.py +++ b/tasks.py @@ -100,6 +100,14 @@ def install(c): print("Config file 'config.yaml' does not exist - copying from template.") copyfile(CONFIG_TEMPLATE_FILE, CONFIG_FILE) +@task +def superuser(c): + """ + Create a superuser (admin) account for the database. + """ + + manage(c, 'createsuperuser') + @task def migrate(c): """ @@ -222,3 +230,15 @@ def postgresql(c): c.run('sudo apt-get install postgresql postgresql-contrib libpq-dev') c.run('pip3 install psycopg2') + +@task +def backup(c): + """ + Create a backup of database models and uploaded media files. + + Backup files will be written to the 'backup_dir' file specified in 'config.yaml' + + """ + + manage(c, 'dbbackup') + manage(c, 'mediabackup') From 513142d487aa8e54b417bce75f895bd3093dad81 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:27:39 +1000 Subject: [PATCH 08/11] Update travis file --- .travis.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index bdc0aad06c..d3ac736a7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,16 +12,17 @@ addons: before_install: - sudo apt-get update - sudo apt-get install gettext - - make install - - make migrate + - pip3 install invoke + - invoke install + - invoke migrate - cd InvenTree && python3 manage.py createsuperuser --username InvenTreeAdmin --email admin@inventree.com --noinput && cd .. script: - cd InvenTree && python3 manage.py makemigrations && cd .. - python3 ci/check_migration_files.py - - make coverage - - make translate - - make style + - invoke coverage + - invoke translate + - invoke style after_success: - coveralls \ No newline at end of file From ef1e5983418eb118ec1876ce1e090e40aa681138 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:32:08 +1000 Subject: [PATCH 09/11] Install PIP requirements as a part of "invoke install" --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 4725e32ef1..498b7fc758 100644 --- a/tasks.py +++ b/tasks.py @@ -90,7 +90,7 @@ def install(c): """ # Install required Python packages with PIP - #c.run('pip3 install -U -r requirements.txt') + c.run('pip3 install -U -r requirements.txt') # If a config.yaml file does not exist, copy from the template! CONFIG_FILE = os.path.join(localDir(), 'InvenTree', 'config.yaml') From d8f9f95cb4f7a559a09da215ca9dcae0d133db96 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:49:03 +1000 Subject: [PATCH 10/11] Add command to run the development server --- tasks.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tasks.py b/tasks.py index 498b7fc758..bc3286db88 100644 --- a/tasks.py +++ b/tasks.py @@ -242,3 +242,13 @@ def backup(c): manage(c, 'dbbackup') manage(c, 'mediabackup') + +@task(help={'address': 'Server address:port'}) +def server(c, address="127.0.0.1:8000"): + """ + Launch a (deveopment) server using Django's in-built webserver. + + Note: This is *not* sufficient for a production installation. + """ + + manage(c, "runserver {address}".format(address=address)) From 017c1ece8961e471bb07a87a543900d729de9fdf Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 21 Aug 2020 21:51:22 +1000 Subject: [PATCH 11/11] Update docstring --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index bc3286db88..3d92cec5c4 100644 --- a/tasks.py +++ b/tasks.py @@ -243,7 +243,7 @@ def backup(c): manage(c, 'dbbackup') manage(c, 'mediabackup') -@task(help={'address': 'Server address:port'}) +@task(help={'address': 'Server address:port (default=127.0.0.1:8000)'}) def server(c, address="127.0.0.1:8000"): """ Launch a (deveopment) server using Django's in-built webserver.