Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2020-08-21 22:11:02 +10:00
commit 98017cb8bd
7 changed files with 274 additions and 152 deletions

View File

@ -12,16 +12,17 @@ addons:
before_install: before_install:
- sudo apt-get update - sudo apt-get update
- sudo apt-get install gettext - sudo apt-get install gettext
- make install - pip3 install invoke
- make migrate - invoke install
- invoke migrate
- cd InvenTree && python3 manage.py createsuperuser --username InvenTreeAdmin --email admin@inventree.com --noinput && cd .. - cd InvenTree && python3 manage.py createsuperuser --username InvenTreeAdmin --email admin@inventree.com --noinput && cd ..
script: script:
- cd InvenTree && python3 manage.py makemigrations && cd .. - cd InvenTree && python3 manage.py makemigrations && cd ..
- python3 ci/check_migration_files.py - python3 ci/check_migration_files.py
- make coverage - invoke coverage
- make translate - invoke translate
- make style - invoke style
after_success: after_success:
- coveralls - coveralls

View File

@ -51,7 +51,8 @@ class PartResource(ModelResource):
report_skipped = False report_skipped = False
clean_model_instances = True clean_model_instances = True
exclude = [ exclude = [
'bom_checksum', 'bom_checked_by', 'bom_checked_date' 'bom_checksum', 'bom_checked_by', 'bom_checked_date',
'lft', 'rght', 'tree_id', 'level',
] ]
def get_queryset(self): def get_queryset(self):

View File

@ -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)

View File

@ -13,7 +13,7 @@ from .models import StockItemTracking
from .models import StockItemTestResult from .models import StockItemTestResult
from build.models import Build from build.models import Build
from company.models import SupplierPart from company.models import Company, SupplierPart
from order.models import PurchaseOrder, SalesOrder from order.models import PurchaseOrder, SalesOrder
from part.models import Part from part.models import Part
@ -59,12 +59,14 @@ class StockItemResource(ModelResource):
# Custom manaegrs for ForeignKey fields # Custom manaegrs for ForeignKey fields
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part)) part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
part_name = Field(attribute='part__full_ame', readonly=True) part_name = Field(attribute='part__full_name', readonly=True)
supplier_part = Field(attribute='supplier_part', widget=widgets.ForeignKeyWidget(SupplierPart)) supplier_part = Field(attribute='supplier_part', widget=widgets.ForeignKeyWidget(SupplierPart))
supplier = Field(attribute='supplier_part__supplier__id', readonly=True) supplier = Field(attribute='supplier_part__supplier__id', readonly=True)
customer = Field(attribute='customer', widget=widgets.ForeignKeyWidget(Company))
supplier_name = Field(attribute='supplier_part__supplier__name', readonly=True) supplier_name = Field(attribute='supplier_part__supplier__name', readonly=True)
status_label = Field(attribute='status_label', readonly=True) status_label = Field(attribute='status_label', readonly=True)
@ -77,6 +79,8 @@ class StockItemResource(ModelResource):
build = Field(attribute='build', widget=widgets.ForeignKeyWidget(Build)) build = Field(attribute='build', widget=widgets.ForeignKeyWidget(Build))
parent = Field(attribute='parent', widget=widgets.ForeignKeyWidget(StockItem))
sales_order = Field(attribute='sales_order', widget=widgets.ForeignKeyWidget(SalesOrder)) sales_order = Field(attribute='sales_order', widget=widgets.ForeignKeyWidget(SalesOrder))
build_order = Field(attribute='build_order', widget=widgets.ForeignKeyWidget(Build)) build_order = Field(attribute='build_order', widget=widgets.ForeignKeyWidget(Build))
@ -101,6 +105,11 @@ class StockItemResource(ModelResource):
report_skipped = False report_skipped = False
clean_model_instance = True clean_model_instance = True
exclude = [
# Exclude MPTT internal model fields
'lft', 'rght', 'tree_id', 'level',
]
class StockItemAdmin(ImportExportModelAdmin): class StockItemAdmin(ImportExportModelAdmin):

View File

@ -1,75 +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
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
# 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
# Update translation files
translate:
cd InvenTree && python3 manage.py makemessages
cd InvenTree && python3 manage.py compilemessages
# Run PEP style checks against source code
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
# 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:
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
.PHONY: clean migrate superuser install mysql postgresql translate static style test coverage docreqs docs backup update

View File

@ -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-cleanup==4.0.0 # Manage deletion of old / unused uploaded files
django-qr-code==1.2.0 # Generate QR codes django-qr-code==1.2.0 # Generate QR codes
flake8==3.8.3 # PEP checking 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) python-coveralls==2.9.1 # Coveralls linking (for Travis)
rapidfuzz==0.7.6 # Fuzzy string matching rapidfuzz==0.7.6 # Fuzzy string matching
django-stdimage==5.1.1 # Advanced ImageField management django-stdimage==5.1.1 # Advanced ImageField management

254
tasks.py Normal file
View File

@ -0,0 +1,254 @@
# -*- 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.
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(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 superuser(c):
"""
Create a superuser (admin) account for the database.
"""
manage(c, 'createsuperuser')
@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 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 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):
"""
Run PEP style checks against InvenTree sourcecode
"""
print("Running PEP style checks...")
c.run('flake8 InvenTree')
@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):
"""
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')
@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')
@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')
@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.
Note: This is *not* sufficient for a production installation.
"""
manage(c, "runserver {address}".format(address=address))