From 8d4e2ce498827eb8ccba934369e4301d063c1487 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 15 Sep 2019 22:34:22 +1000 Subject: [PATCH 1/6] Update CONTRIBUTING.md Include notes about migration files --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 027d3641cb..ebb9d4a4fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,10 @@ Contributions to InvenTree are welcomed - please follow the guidelines below. No pushing to master! New featues must be submitted in a separate branch (one branch per feature). +## Include Migration Files + +Any required migration files **must** be included in the commit, or the pull-request will be rejected. If you change the underlying database schema, make sure you run `make migrate` and commit the migration files before submitting the PR. + ## Testing Any new code should be covered by unit tests - a submitted PR may not be accepted if the code coverage is decreased. From 3e33326120781d78929b8a86808a705ff1e02e1a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 15 Sep 2019 22:46:24 +1000 Subject: [PATCH 2/6] Add the InvenTreeSetting model - Storage of singleton settings in key:value pairs --- .../migrations/0004_inventreesetting.py | 21 ++++++ InvenTree/common/models.py | 71 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 InvenTree/common/migrations/0004_inventreesetting.py diff --git a/InvenTree/common/migrations/0004_inventreesetting.py b/InvenTree/common/migrations/0004_inventreesetting.py new file mode 100644 index 0000000000..64fc9cd486 --- /dev/null +++ b/InvenTree/common/migrations/0004_inventreesetting.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.5 on 2019-09-15 12:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0003_auto_20190902_2310'), + ] + + operations = [ + migrations.CreateModel( + name='InvenTreeSetting', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(help_text='Settings key', max_length=50, unique=True)), + ('value', models.CharField(blank=True, help_text='Settings value', max_length=200)), + ], + ), + ] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 61bb9ec138..a5bc537325 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -9,6 +9,77 @@ from __future__ import unicode_literals from django.db import models from django.utils.translation import ugettext as _ from django.core.validators import MinValueValidator, MaxValueValidator +from django.core.exceptions import ValidationError + + +class InvenTreeSetting(models.Model): + """ + An InvenTreeSetting object is a key:value pair used for storing + single values (e.g. one-off settings values). + + The class provides a way of retrieving the value for a particular key, + even if that key does not exist. + """ + + @classmethod + def get_setting(cls, key, backup_value=None): + """ + Get the value of a particular setting. + If it does not exist, return the backup value (default = None) + """ + + try: + setting = InvenTreeSetting.objects.get(key__iexact=key) + return setting.value + except InvenTreeSetting.DoesNotExist: + return backup_value + + @classmethod + def set_setting(cls, key, value, user, create=True): + """ + Set the value of a particular setting. + If it does not exist, option to create it. + + Args: + key: settings key + value: New value + user: User object (must be staff member to update a core setting) + create: If True, create a new setting if the specified key does not exist + """ + + if not user.is_staff: + return + + try: + setting = InvenTreeSetting.objects.get(key__iexact=key) + except InvenTreeSetting.DoesNotExist: + + if create: + setting = InvenTreeSetting(key=key) + else: + return + + setting.value = value + setting.save() + + key = models.CharField(max_length=50, blank=False, unique=True, help_text=_('Settings key')) + + value = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings value')) + + def validate_unique(self, exclude=None): + """ Ensure that the key:value pair is unique. + In addition to the base validators, this ensures that the 'key' + is unique, using a case-insensitive comparison. + """ + + super().validate_unique(exclude) + + try: + setting = InvenTreeSetting.objects.exclude(id=self.id).filter(key__iexact=self.key) + if setting.exists(): + raise ValidationError({'key': _('Key string must be unique')}) + except InvenTreeSetting.DoesNotExist: + pass class Currency(models.Model): From 02e71bd2cec139d6f841e7ce7ae74a77c83c64b4 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 15 Sep 2019 22:50:47 +1000 Subject: [PATCH 3/6] Template for displaying other settings --- InvenTree/InvenTree/urls.py | 1 + InvenTree/templates/InvenTree/settings/other.html | 11 +++++++++++ InvenTree/templates/InvenTree/settings/tabs.html | 5 +++++ 3 files changed, 17 insertions(+) create mode 100644 InvenTree/templates/InvenTree/settings/other.html diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index d811c0165e..033261d9f8 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -61,6 +61,7 @@ settings_urls = [ url(r'^user/?', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings-user'), url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'), url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'), + url(r'^other/?', SettingsView.as_view(template_name='InvenTree/settings/other.html'), name='settings-other'), # Catch any other urls url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'), diff --git a/InvenTree/templates/InvenTree/settings/other.html b/InvenTree/templates/InvenTree/settings/other.html new file mode 100644 index 0000000000..bff3d4c2e6 --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/other.html @@ -0,0 +1,11 @@ +{% extends "InvenTree/settings/settings.html" %} + +{% block tabs %} +{% include "InvenTree/settings/tabs.html" with tab='other' %} +{% endblock %} + +{% block settings %} + +

InvenTree Settings

+ +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/tabs.html b/InvenTree/templates/InvenTree/settings/tabs.html index 78d15dbcfe..c33f823be2 100644 --- a/InvenTree/templates/InvenTree/settings/tabs.html +++ b/InvenTree/templates/InvenTree/settings/tabs.html @@ -8,4 +8,9 @@ Part + {% if user.is_staff %} + + Other + + {% endif %} \ No newline at end of file From 098cd0ec445bb9e5a4c7f53b34e3c470cbb0b106 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 15 Sep 2019 23:07:45 +1000 Subject: [PATCH 4/6] Add description field --- .../migrations/0005_auto_20190915_1256.py | 23 +++++++++++++++++++ InvenTree/common/models.py | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 InvenTree/common/migrations/0005_auto_20190915_1256.py diff --git a/InvenTree/common/migrations/0005_auto_20190915_1256.py b/InvenTree/common/migrations/0005_auto_20190915_1256.py new file mode 100644 index 0000000000..5b3205128d --- /dev/null +++ b/InvenTree/common/migrations/0005_auto_20190915_1256.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.5 on 2019-09-15 12:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0004_inventreesetting'), + ] + + operations = [ + migrations.AddField( + model_name='inventreesetting', + name='description', + field=models.CharField(blank=True, help_text='Settings description', max_length=200), + ), + migrations.AlterField( + model_name='inventreesetting', + name='key', + field=models.CharField(help_text='Settings key (must be unique - case insensitive', max_length=50, unique=True), + ), + ] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index a5bc537325..a5338126d5 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -62,10 +62,12 @@ class InvenTreeSetting(models.Model): setting.value = value setting.save() - key = models.CharField(max_length=50, blank=False, unique=True, help_text=_('Settings key')) + key = models.CharField(max_length=50, blank=False, unique=True, help_text=_('Settings key (must be unique - case insensitive')) value = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings value')) + description = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings description')) + def validate_unique(self, exclude=None): """ Ensure that the key:value pair is unique. In addition to the base validators, this ensures that the 'key' From 2c1a744c2de87ea89bf0812a2674a413a652d8f0 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 15 Sep 2019 23:09:58 +1000 Subject: [PATCH 5/6] Display singleton settings in the settings tab - Only visible to 'staff' user --- InvenTree/InvenTree/views.py | 9 +++++++++ InvenTree/common/admin.py | 8 +++++++- .../templates/InvenTree/settings/other.html | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 610f3c0c00..ad0d70bfe8 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -16,6 +16,7 @@ from django.views.generic import UpdateView, CreateView from django.views.generic.base import TemplateView from part.models import Part +from common.models import InvenTreeSetting from .forms import DeleteForm, EditUserForm, SetPasswordForm from .helpers import str2bool @@ -511,3 +512,11 @@ class SettingsView(TemplateView): """ template_name = "InvenTree/settings.html" + + def get_context_data(self, **kwargs): + + ctx = super().get_context_data(**kwargs).copy() + + ctx['settings'] = InvenTreeSetting.objects.all().order_by('key') + + return ctx diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py index e0db0a7136..cb643deca4 100644 --- a/InvenTree/common/admin.py +++ b/InvenTree/common/admin.py @@ -5,11 +5,17 @@ from django.contrib import admin from import_export.admin import ImportExportModelAdmin -from .models import Currency +from .models import Currency, InvenTreeSetting class CurrencyAdmin(ImportExportModelAdmin): list_display = ('symbol', 'suffix', 'description', 'value', 'base') +class SettingsAdmin(ImportExportModelAdmin): + + list_display = ('key', 'value', 'description') + + admin.site.register(Currency, CurrencyAdmin) +admin.site.register(InvenTreeSetting, SettingsAdmin) diff --git a/InvenTree/templates/InvenTree/settings/other.html b/InvenTree/templates/InvenTree/settings/other.html index bff3d4c2e6..d793788bb8 100644 --- a/InvenTree/templates/InvenTree/settings/other.html +++ b/InvenTree/templates/InvenTree/settings/other.html @@ -8,4 +8,23 @@

InvenTree Settings

+ + + + + + + + + + {% for setting in settings %} + + + + + + {% endfor %} + +
SettingValueDescription
{{ setting.key }}{{ setting.value }}{{ setting.description }}
+ {% endblock %} \ No newline at end of file From 4746a3ccff96b253f51bac7a64084028320f6737 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 15 Sep 2019 23:11:06 +1000 Subject: [PATCH 6/6] Bootstrapify the table --- InvenTree/common/models.py | 2 +- InvenTree/templates/InvenTree/settings/other.html | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index a5338126d5..0b8795484b 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -44,7 +44,7 @@ class InvenTreeSetting(models.Model): key: settings key value: New value user: User object (must be staff member to update a core setting) - create: If True, create a new setting if the specified key does not exist + create: If True, create a new setting if the specified key does not exist. """ if not user.is_staff: diff --git a/InvenTree/templates/InvenTree/settings/other.html b/InvenTree/templates/InvenTree/settings/other.html index d793788bb8..2f6207b170 100644 --- a/InvenTree/templates/InvenTree/settings/other.html +++ b/InvenTree/templates/InvenTree/settings/other.html @@ -27,4 +27,11 @@ +{% endblock %} + +{% block js_ready %} +{{ block.super }} + + $("#other-table").bootstrapTable(); + {% endblock %} \ No newline at end of file