From 63d221854bddca881330e73299e47397723cf03a Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 7 Aug 2022 22:46:33 +1000 Subject: [PATCH] Url field fix (#3488) * Updates for automated metadata extraction * Update link field for StockItem model - Increase max_length to 200 characters - Custom migration - Updates for InvenTreeUrlField model * Adding unit tests * Bug fix for metadata.py --- InvenTree/InvenTree/fields.py | 27 ++++++------- InvenTree/InvenTree/metadata.py | 16 +++++--- .../migrations/0082_alter_stockitem_link.py | 19 +++++++++ InvenTree/stock/models.py | 2 +- InvenTree/stock/tests.py | 39 +++++++++++++++++++ 5 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 InvenTree/stock/migrations/0082_alter_stockitem_link.py diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py index 35f9fb5eb4..d8eff33093 100644 --- a/InvenTree/InvenTree/fields.py +++ b/InvenTree/InvenTree/fields.py @@ -6,7 +6,6 @@ from decimal import Decimal from django import forms from django.core import validators from django.db import models as models -from django.forms.fields import URLField as FormURLField from django.utils.translation import gettext_lazy as _ from djmoney.forms.fields import MoneyField @@ -23,26 +22,28 @@ class InvenTreeRestURLField(RestURLField): """Custom field for DRF with custom scheme vaildators.""" def __init__(self, **kwargs): """Update schemes.""" + + # Enforce 'max length' parameter in form validation + if 'max_length' not in kwargs: + kwargs['max_length'] = 200 + super().__init__(**kwargs) self.validators[-1].schemes = allowable_url_schemes() -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.""" - validators = [validators.URLValidator(schemes=allowable_url_schemes())] + default_validators = [validators.URLValidator(schemes=allowable_url_schemes())] - def formfield(self, **kwargs): - """Return a Field instance for this field.""" - return super().formfield(**{ - 'form_class': InvenTreeURLFormField - }) + def __init__(self, **kwargs): + """Initialization method for InvenTreeURLField""" + + # Max length for InvenTreeURLField defaults to 200 + if 'max_length' not in kwargs: + kwargs['max_length'] = 200 + + super().__init__(**kwargs) def money_kwargs(): diff --git a/InvenTree/InvenTree/metadata.py b/InvenTree/InvenTree/metadata.py index 8e4fa13452..3026719ecc 100644 --- a/InvenTree/InvenTree/metadata.py +++ b/InvenTree/InvenTree/metadata.py @@ -116,6 +116,12 @@ class InvenTreeMetadata(SimpleMetadata): model_class = None + # Attributes to copy extra attributes from the model to the field (if they don't exist) + extra_attributes = [ + 'help_text', + 'max_length', + ] + try: model_class = serializer.Meta.model @@ -148,10 +154,7 @@ class InvenTreeMetadata(SimpleMetadata): elif name in model_default_values: serializer_info[name]['default'] = model_default_values[name] - # Attributes to copy from the model to the field (if they don't exist) - attributes = ['help_text'] - - for attr in attributes: + for attr in extra_attributes: if attr not in serializer_info[name]: if hasattr(field, attr): @@ -172,8 +175,9 @@ class InvenTreeMetadata(SimpleMetadata): # This is used to automatically filter AJAX requests serializer_info[name]['filters'] = relation.model_field.get_limit_choices_to() - if 'help_text' not in serializer_info[name] and hasattr(relation.model_field, 'help_text'): - serializer_info[name]['help_text'] = relation.model_field.help_text + for attr in extra_attributes: + if attr not in serializer_info[name] and hasattr(relation.model_field, attr): + serializer_info[name][attr] = getattr(relation.model_field, attr) if name in model_default_values: serializer_info[name]['default'] = model_default_values[name] diff --git a/InvenTree/stock/migrations/0082_alter_stockitem_link.py b/InvenTree/stock/migrations/0082_alter_stockitem_link.py new file mode 100644 index 0000000000..a7d0821859 --- /dev/null +++ b/InvenTree/stock/migrations/0082_alter_stockitem_link.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.15 on 2022-08-07 02:38 + +import InvenTree.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0081_auto_20220801_0044'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', max_length=200, verbose_name='External Link'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 391f074f11..b23e72117d 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -647,7 +647,7 @@ class StockItem(MetadataMixin, MPTTModel): link = InvenTreeURLField( verbose_name=_('External Link'), - max_length=125, blank=True, + blank=True, max_length=200, help_text=_("Link to external URL") ) diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 1e49020503..172d94fe52 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -44,6 +44,45 @@ class StockTest(InvenTreeTestCase): Part.objects.rebuild() StockItem.objects.rebuild() + def test_link(self): + """Test the link URL field validation""" + + item = StockItem.objects.get(pk=1) + + # Check that invalid URLs fail + for bad_url in [ + 'test.com', + 'httpx://abc.xyz', + 'https:google.com', + ]: + with self.assertRaises(ValidationError): + item.link = bad_url + item.save() + item.full_clean() + + # Check that valid URLs pass + for good_url in [ + 'https://test.com', + 'https://digikey.com/datasheets?file=1010101010101.bin', + 'ftp://download.com:8080/file.aspx', + ]: + item.link = good_url + item.save() + item.full_clean() + + # A long URL should fail + long_url = 'https://website.co.uk?query=' + 'a' * 173 + + with self.assertRaises(ValidationError): + item.link = long_url + item.full_clean() + + # Shorten by a single character, will pass + long_url = long_url[:-1] + + item.link = long_url + item.save() + def test_expiry(self): """Test expiry date functionality for StockItem model.""" today = datetime.datetime.now().date()