mirror of
https://github.com/inventree/InvenTree.git
synced 2026-10-14 22:20:59 +00:00
feat(backend): mark setting when changed via API (#12982)
* mark setting when changed via API might be helpful for https://github.com/inventree/InvenTree/issues/12767 * add migration * fix migration * use atomic instead * move atomic * move the transaction safety to be migration specific * reduce diff * make write safe * remove migration_safe_query again * use a simpler approach in the squashed migration * try using a manager * add update / updated_by to api * update generic settings api to handle updated_by field
This commit is contained in:
1 parent
76a21b7899
commit
7e89e93252
8 files changed
+174
-18
No files matched your search
@@ -269,7 +269,16 @@ class GlobalSettingsList(SettingsList):
|
|||||||
return super().list(request, *args, **kwargs)
|
return super().list(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class GlobalSettingsDetail(RetrieveUpdateAPI):
|
class TouchMixin:
|
||||||
|
"""Mark a setting as touched when it is modified by the user."""
|
||||||
|
|
||||||
|
def perform_update(self, serializer):
|
||||||
|
"""Mark a setting as touched when it is modified by the user."""
|
||||||
|
serializer.instance.touched = True
|
||||||
|
super().perform_update(serializer)
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalSettingsDetail(TouchMixin, RetrieveUpdateAPI):
|
||||||
"""Detail view for an individual "global setting" object.
|
"""Detail view for an individual "global setting" object.
|
||||||
|
|
||||||
- User must have 'staff' status to view / edit
|
- User must have 'staff' status to view / edit
|
||||||
@@ -328,7 +337,7 @@ class UserSettingsList(SettingsList):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class UserSettingsDetail(RetrieveUpdateAPI):
|
class UserSettingsDetail(TouchMixin, RetrieveUpdateAPI):
|
||||||
"""Detail view for an individual "user setting" object.
|
"""Detail view for an individual "user setting" object.
|
||||||
|
|
||||||
- User can only view / edit settings their own settings objects
|
- User can only view / edit settings their own settings objects
|
||||||
|
|||||||
+13
-13
@@ -9,25 +9,25 @@ import django.db.models.deletion
|
|||||||
import taggit.managers
|
import taggit.managers
|
||||||
import uuid
|
import uuid
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models, transaction
|
||||||
from moneyed import CURRENCIES
|
from moneyed import CURRENCIES
|
||||||
|
|
||||||
|
|
||||||
def set_default_currency(apps, schema_editor):
|
def set_default_currency(apps, schema_editor):
|
||||||
"""Migrate the currency setting from config.yml to db."""
|
"""Migrate the currency setting from config.yml to db."""
|
||||||
from common.currency import currency_codes
|
InvenTreeSetting = apps.get_model('common', 'InvenTreeSetting')
|
||||||
from common.models import InvenTreeSetting
|
key = 'INVENTREE_DEFAULT_CURRENCY'
|
||||||
|
base_currency = InvenTree.config.get_setting('INVENTREE_BASE_CURRENCY', 'base_currency', 'USD')
|
||||||
|
|
||||||
base_currency = InvenTree.config.get_setting(
|
if base_currency not in CURRENCIES:
|
||||||
'INVENTREE_BASE_CURRENCY', 'base_currency', 'USD'
|
base_currency = 'USD'
|
||||||
)
|
with transaction.atomic():
|
||||||
|
setting = InvenTreeSetting.objects.filter(key=key).first()
|
||||||
if base_currency not in currency_codes():
|
if setting:
|
||||||
base_currency = currency_codes()[0] if currency_codes() else 'USD'
|
setting.value = base_currency
|
||||||
|
setting.save()
|
||||||
InvenTreeSetting.set_setting(
|
else:
|
||||||
'INVENTREE_DEFAULT_CURRENCY', base_currency, None, create=True
|
InvenTreeSetting(key=key, value=base_currency).save()
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def set_currencies(apps, schema_editor):
|
def set_currencies(apps, schema_editor):
|
||||||
|
|||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
# Generated by Django 5.2.17 on 2026-10-12 22:47
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('common', '0052_remove_notesimage_model_id_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventreesetting',
|
||||||
|
name='touched',
|
||||||
|
field=models.BooleanField(default=False, help_text='Indicates whether the setting has been modified by the user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventreesetting',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, help_text='Timestamp of last update', null=True, verbose_name='Updated'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventreesetting',
|
||||||
|
name='updated_by',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='User who last updated this object', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL, verbose_name='Update By'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventreeusersetting',
|
||||||
|
name='touched',
|
||||||
|
field=models.BooleanField(default=False, help_text='Indicates whether the setting has been modified by the user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventreeusersetting',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, help_text='Timestamp of last update', null=True, verbose_name='Updated'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventreeusersetting',
|
||||||
|
name='updated_by',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='User who last updated this object', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL, verbose_name='Update By'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -205,7 +205,15 @@ class ProjectCode(InvenTree.models.InvenTreeMetadataModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseInvenTreeSetting(models.Model):
|
class SettingsManager(models.Manager):
|
||||||
|
"""Ensure that the 'touched' field is deferred in all queries."""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Defer the write-only 'touched' field."""
|
||||||
|
return super().get_queryset().defer('touched', 'updated_by', 'updated')
|
||||||
|
|
||||||
|
|
||||||
|
class BaseInvenTreeSetting(UpdatedUserMixin, models.Model):
|
||||||
"""An base InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values).
|
"""An base InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values).
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
@@ -217,6 +225,8 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
CHECK_SETTING_KEY = False
|
CHECK_SETTING_KEY = False
|
||||||
|
|
||||||
|
objects = SettingsManager()
|
||||||
|
|
||||||
extra_unique_fields: list[str] = []
|
extra_unique_fields: list[str] = []
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -614,7 +624,6 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
access_global_cache = False
|
access_global_cache = False
|
||||||
|
|
||||||
# At this point, we need to query the database
|
# At this point, we need to query the database
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
'key__iexact': key,
|
'key__iexact': key,
|
||||||
# Optionally filter by other keys
|
# Optionally filter by other keys
|
||||||
@@ -806,6 +815,11 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
max_length=2000, blank=True, unique=False, help_text=_('Settings value')
|
max_length=2000, blank=True, unique=False, help_text=_('Settings value')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
touched = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text=_('Indicates whether the setting has been modified by the user'),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return name for setting."""
|
"""Return name for setting."""
|
||||||
|
|||||||
@@ -152,6 +152,15 @@ class SettingsSerializer(InvenTreeModelSerializer):
|
|||||||
})
|
})
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
"""Save the setting, adding the updated_by field."""
|
||||||
|
request = self.context.get('request', None)
|
||||||
|
if request is not None and hasattr(request, 'user'):
|
||||||
|
kwargs['updated_by'] = request.user
|
||||||
|
elif 'updated_by' in kwargs:
|
||||||
|
kwargs.pop('updated_by')
|
||||||
|
return super().save(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class GlobalSettingsSerializer(SettingsSerializer):
|
class GlobalSettingsSerializer(SettingsSerializer):
|
||||||
"""Serializer for the InvenTreeSetting model."""
|
"""Serializer for the InvenTreeSetting model."""
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
import InvenTree.permissions
|
import InvenTree.permissions
|
||||||
import machine.serializers as MachineSerializers
|
import machine.serializers as MachineSerializers
|
||||||
|
from common.api import TouchMixin
|
||||||
from InvenTree.filters import SEARCH_ORDER_FILTER
|
from InvenTree.filters import SEARCH_ORDER_FILTER
|
||||||
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateAPI, RetrieveUpdateDestroyAPI
|
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateAPI, RetrieveUpdateDestroyAPI
|
||||||
from machine import registry
|
from machine import registry
|
||||||
@@ -108,7 +109,7 @@ class MachineSettingList(APIView):
|
|||||||
return Response(results)
|
return Response(results)
|
||||||
|
|
||||||
|
|
||||||
class MachineSettingDetail(RetrieveUpdateAPI):
|
class MachineSettingDetail(TouchMixin, RetrieveUpdateAPI):
|
||||||
"""Detail endpoint for a machine-specific setting.
|
"""Detail endpoint for a machine-specific setting.
|
||||||
|
|
||||||
- GET: Get machine setting detail
|
- GET: Get machine setting detail
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 5.2.17 on 2026-10-12 22:47
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machine', '0002_alter_machineconfig_id'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='machinesetting',
|
||||||
|
name='touched',
|
||||||
|
field=models.BooleanField(default=False, help_text='Indicates whether the setting has been modified by the user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='machinesetting',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, help_text='Timestamp of last update', null=True, verbose_name='Updated'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='machinesetting',
|
||||||
|
name='updated_by',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='User who last updated this object', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL, verbose_name='Update By'),
|
||||||
|
),
|
||||||
|
]
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
# Generated by Django 5.2.17 on 2026-10-12 22:47
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('plugin', '0011_delete_notificationusersetting'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pluginsetting',
|
||||||
|
name='touched',
|
||||||
|
field=models.BooleanField(default=False, help_text='Indicates whether the setting has been modified by the user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pluginsetting',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, help_text='Timestamp of last update', null=True, verbose_name='Updated'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pluginsetting',
|
||||||
|
name='updated_by',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='User who last updated this object', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL, verbose_name='Update By'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pluginusersetting',
|
||||||
|
name='touched',
|
||||||
|
field=models.BooleanField(default=False, help_text='Indicates whether the setting has been modified by the user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pluginusersetting',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, help_text='Timestamp of last update', null=True, verbose_name='Updated'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pluginusersetting',
|
||||||
|
name='updated_by',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='User who last updated this object', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL, verbose_name='Update By'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Reference in new issue
Block a user