diff --git a/.gitignore b/.gitignore index 7b9522c3b7..6b36e2d1d2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,8 @@ InvenTree/media InvenTree/static media static +inventree_media +inventree_static # Local config file config.yaml diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py new file mode 100644 index 0000000000..7a2fa4b89a --- /dev/null +++ b/InvenTree/InvenTree/fields.py @@ -0,0 +1,27 @@ +""" Custom fields used in InvenTree """ + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from .validators import allowable_url_schemes + +from django.forms.fields import URLField as FormURLField +from django.db import models as models +from django.core import validators + + +class InvenTreeURLFormField(FormURLField): + """ Custom URL form field with custom scheme validators """ + + default_validators = [validators.URLValidator(schemes=allowable_url_schemes())] + + +class InvenTreeURLField(models.URLField): + """ Custom URL field which has custom scheme validators """ + + default_validators = [validators.URLValidator(schemes=allowable_url_schemes())] + + def formfield(self, **kwargs): + return super().formfield(**{ + 'form_class': InvenTreeURLFormField + }) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index f73a5034ca..c3d646c660 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -213,6 +213,14 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +# Extra (optional) URL validators +# See https://docs.djangoproject.com/en/2.2/ref/validators/#django.core.validators.URLValidator + +EXTRA_URL_SCHEMES = CONFIG.get('extra_url_schemes', []) + +if not type(EXTRA_URL_SCHEMES) in [list]: + eprint("Warning: extra_url_schemes not correctly formatted") + EXTRA_URL_SCHEMES = [] # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ diff --git a/InvenTree/InvenTree/validators.py b/InvenTree/InvenTree/validators.py index 4fbcf44b1c..98c0532d14 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -2,10 +2,29 @@ Custom field validators for InvenTree """ +from django.conf import settings from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +def allowable_url_schemes(): + """ Return the list of allowable URL schemes. + In addition to the default schemes allowed by Django, + the install configuration file (config.yaml) can specify + extra schemas """ + + # Default schemes + schemes = ['http', 'https', 'ftp', 'ftps'] + + extra = settings.EXTRA_URL_SCHEMES + + for e in extra: + if e.lower() not in schemes: + schemes.append(e.lower()) + + return schemes + + def validate_part_name(value): """ Prevent some illegal characters in part names. """ diff --git a/InvenTree/build/migrations/0006_auto_20190913_1407.py b/InvenTree/build/migrations/0006_auto_20190913_1407.py new file mode 100644 index 0000000000..917d3a4e42 --- /dev/null +++ b/InvenTree/build/migrations/0006_auto_20190913_1407.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.5 on 2019-09-13 14:07 + +import InvenTree.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('build', '0005_auto_20190604_2217'), + ] + + operations = [ + migrations.AlterField( + model_name='build', + name='URL', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL'), + ), + ] diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 2ae3f0c5a7..57792340e8 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -17,6 +17,7 @@ from django.db.models import Sum from django.core.validators import MinValueValidator from InvenTree.status_codes import BuildStatus +from InvenTree.fields import InvenTreeURLField from stock.models import StockItem from part.models import Part, BomItem @@ -89,7 +90,7 @@ class Build(models.Model): related_name='builds_completed' ) - URL = models.URLField(blank=True, help_text='Link to external URL') + URL = InvenTreeURLField(blank=True, help_text='Link to external URL') notes = models.TextField(blank=True, help_text='Extra build notes') """ Notes attached to each build output """ diff --git a/InvenTree/company/migrations/0008_auto_20190913_1407.py b/InvenTree/company/migrations/0008_auto_20190913_1407.py new file mode 100644 index 0000000000..d2e23da35f --- /dev/null +++ b/InvenTree/company/migrations/0008_auto_20190913_1407.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.5 on 2019-09-13 14:07 + +import InvenTree.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0007_remove_supplierpart_lead_time'), + ] + + operations = [ + migrations.AlterField( + model_name='company', + name='URL', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external company information'), + ), + migrations.AlterField( + model_name='supplierpart', + name='URL', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='URL for external supplier part link'), + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 9b974ce793..d1c340921a 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -19,6 +19,7 @@ from django.urls import reverse from django.conf import settings from django.contrib.staticfiles.templatetags.staticfiles import static +from InvenTree.fields import InvenTreeURLField from InvenTree.status_codes import OrderStatus from common.models import Currency @@ -85,7 +86,7 @@ class Company(models.Model): contact = models.CharField(max_length=100, blank=True, help_text='Point of contact') - URL = models.URLField(blank=True, help_text='Link to external company information') + URL = InvenTreeURLField(blank=True, help_text='Link to external company information') image = models.ImageField(upload_to=rename_company_image, max_length=255, null=True, blank=True) @@ -238,7 +239,7 @@ class SupplierPart(models.Model): MPN = models.CharField(max_length=100, blank=True, help_text='Manufacturer part number') - URL = models.URLField(blank=True, help_text='URL for external supplier part link') + URL = InvenTreeURLField(blank=True, help_text='URL for external supplier part link') description = models.CharField(max_length=250, blank=True, help_text='Supplier part description') diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 1799b49e1a..f362048e35 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -47,7 +47,16 @@ media_root: '../inventree_media' # By default it is stored in a directory named 'static' local to the InvenTree directory static_root: '../inventree_static' +# Optional URL schemes to allow in URL fields +# By default, only the following schemes are allowed: ['http', 'https', 'ftp', 'ftps'] +# Uncomment the lines below to allow extra schemes +#extra_url_schemes: +# - mailto +# - git +# - ssh + # Logging options +# If debug mode is enabled, set log_queries to True to show aggregate database queries in the debug console log_queries: False # Backup options diff --git a/InvenTree/part/migrations/0023_auto_20190913_1401.py b/InvenTree/part/migrations/0023_auto_20190913_1401.py new file mode 100644 index 0000000000..8d5c5992d6 --- /dev/null +++ b/InvenTree/part/migrations/0023_auto_20190913_1401.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.5 on 2019-09-13 14:01 + +import InvenTree.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0022_auto_20190908_0918'), + ] + + operations = [ + migrations.AlterField( + model_name='part', + name='URL', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to extenal URL'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index f57da3cfdb..76d9f606d0 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -34,6 +34,7 @@ import hashlib from InvenTree import helpers from InvenTree import validators from InvenTree.models import InvenTreeTree +from InvenTree.fields import InvenTreeURLField from InvenTree.status_codes import BuildStatus, StockStatus, OrderStatus @@ -353,7 +354,7 @@ class Part(models.Model): revision = models.CharField(max_length=100, blank=True, help_text='Part revision or version number') - URL = models.URLField(blank=True, help_text='Link to extenal URL') + URL = InvenTreeURLField(blank=True, help_text='Link to extenal URL') image = models.ImageField(upload_to=rename_part_image, max_length=255, null=True, blank=True) diff --git a/InvenTree/stock/migrations/0015_auto_20190913_1407.py b/InvenTree/stock/migrations/0015_auto_20190913_1407.py new file mode 100644 index 0000000000..de1b49a2a2 --- /dev/null +++ b/InvenTree/stock/migrations/0015_auto_20190913_1407.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.5 on 2019-09-13 14:07 + +import InvenTree.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0014_auto_20190908_0918'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='URL', + field=InvenTree.fields.InvenTreeURLField(blank=True, max_length=125), + ), + migrations.AlterField( + model_name='stockitemtracking', + name='URL', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external page for further information'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index c659801a0d..272d212cef 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -23,6 +23,7 @@ from InvenTree import helpers from InvenTree.status_codes import StockStatus from InvenTree.models import InvenTreeTree +from InvenTree.fields import InvenTreeURLField from part.models import Part @@ -308,7 +309,7 @@ class StockItem(models.Model): serial = models.PositiveIntegerField(blank=True, null=True, help_text='Serial number for this item') - URL = models.URLField(max_length=125, blank=True) + URL = InvenTreeURLField(max_length=125, blank=True) batch = models.CharField(max_length=100, blank=True, null=True, help_text='Batch code for this stock item') @@ -715,7 +716,7 @@ class StockItemTracking(models.Model): notes = models.CharField(blank=True, max_length=512, help_text='Entry notes') - URL = models.URLField(blank=True, help_text='Link to external page for further information') + URL = InvenTreeURLField(blank=True, help_text='Link to external page for further information') user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True) diff --git a/Makefile b/Makefile index fc3e4d64e1..ebbd9cafef 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ clean: rm -rf .tox rm -f .coverage -update: backup install migrate +update: backup install migrate static # Perform database migrations (after schema changes are made) migrate: @@ -15,6 +15,9 @@ migrate: 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 @@ -64,4 +67,4 @@ backup: cd InvenTree && python3 manage.py dbbackup cd InvenTree && python3 manage.py mediabackup -.PHONY: clean migrate superuser install mysql postgresql style test coverage docreqs docs backup update \ No newline at end of file +.PHONY: clean migrate superuser install mysql postgresql static style test coverage docreqs docs backup update \ No newline at end of file