mirror of
https://github.com/inventree/InvenTree.git
synced 2026-10-14 14:11:55 +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)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
- User must have 'staff' status to view / edit
|
||||
@@ -328,7 +337,7 @@ class UserSettingsList(SettingsList):
|
||||
return queryset
|
||||
|
||||
|
||||
class UserSettingsDetail(RetrieveUpdateAPI):
|
||||
class UserSettingsDetail(TouchMixin, RetrieveUpdateAPI):
|
||||
"""Detail view for an individual "user setting" object.
|
||||
|
||||
- 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 uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.db import migrations, models, transaction
|
||||
from moneyed import CURRENCIES
|
||||
|
||||
|
||||
def set_default_currency(apps, schema_editor):
|
||||
"""Migrate the currency setting from config.yml to db."""
|
||||
from common.currency import currency_codes
|
||||
from common.models import InvenTreeSetting
|
||||
InvenTreeSetting = apps.get_model('common', 'InvenTreeSetting')
|
||||
key = 'INVENTREE_DEFAULT_CURRENCY'
|
||||
base_currency = InvenTree.config.get_setting('INVENTREE_BASE_CURRENCY', 'base_currency', 'USD')
|
||||
|
||||
base_currency = InvenTree.config.get_setting(
|
||||
'INVENTREE_BASE_CURRENCY', 'base_currency', 'USD'
|
||||
)
|
||||
|
||||
if base_currency not in currency_codes():
|
||||
base_currency = currency_codes()[0] if currency_codes() else 'USD'
|
||||
|
||||
InvenTreeSetting.set_setting(
|
||||
'INVENTREE_DEFAULT_CURRENCY', base_currency, None, create=True
|
||||
)
|
||||
if base_currency not in CURRENCIES:
|
||||
base_currency = 'USD'
|
||||
with transaction.atomic():
|
||||
setting = InvenTreeSetting.objects.filter(key=key).first()
|
||||
if setting:
|
||||
setting.value = base_currency
|
||||
setting.save()
|
||||
else:
|
||||
InvenTreeSetting(key=key, value=base_currency).save()
|
||||
|
||||
|
||||
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).
|
||||
|
||||
Attributes:
|
||||
@@ -217,6 +225,8 @@ class BaseInvenTreeSetting(models.Model):
|
||||
|
||||
CHECK_SETTING_KEY = False
|
||||
|
||||
objects = SettingsManager()
|
||||
|
||||
extra_unique_fields: list[str] = []
|
||||
|
||||
class Meta:
|
||||
@@ -614,7 +624,6 @@ class BaseInvenTreeSetting(models.Model):
|
||||
access_global_cache = False
|
||||
|
||||
# At this point, we need to query the database
|
||||
|
||||
filters = {
|
||||
'key__iexact': key,
|
||||
# Optionally filter by other keys
|
||||
@@ -806,6 +815,11 @@ class BaseInvenTreeSetting(models.Model):
|
||||
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
|
||||
def name(self):
|
||||
"""Return name for setting."""
|
||||
|
||||
@@ -152,6 +152,15 @@ class SettingsSerializer(InvenTreeModelSerializer):
|
||||
})
|
||||
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):
|
||||
"""Serializer for the InvenTreeSetting model."""
|
||||
|
||||
@@ -10,6 +10,7 @@ from rest_framework.views import APIView
|
||||
|
||||
import InvenTree.permissions
|
||||
import machine.serializers as MachineSerializers
|
||||
from common.api import TouchMixin
|
||||
from InvenTree.filters import SEARCH_ORDER_FILTER
|
||||
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateAPI, RetrieveUpdateDestroyAPI
|
||||
from machine import registry
|
||||
@@ -108,7 +109,7 @@ class MachineSettingList(APIView):
|
||||
return Response(results)
|
||||
|
||||
|
||||
class MachineSettingDetail(RetrieveUpdateAPI):
|
||||
class MachineSettingDetail(TouchMixin, RetrieveUpdateAPI):
|
||||
"""Detail endpoint for a machine-specific setting.
|
||||
|
||||
- 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