mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-02 11:40:58 +00:00
[FR] Add Feature flags (#4982)
* make currency choices independend
* Remove check for field, just try to get rid of it
* Add IF EXISTS to avoid error (works in postgres)
* Look for operational error, not programming error
* Use variants, depending on errors caused
* [FR] Add Feature flags
Fixes #4965
* Add option to define custom flags
* Revert "make currency choices independend"
This reverts commit ab84a7ff83
.
* try fixing mysql
* more safeguards
* fix executioner call
* a fck
* use migrations. syntax
* and another round for mysql
* revert print change
* use UTC for datetime
* Update part.migrations.0112
- Add custom migration class which handles errors
* Add unit test for migration
- Ensure that the new fields are added to the model
* Update reference to PR
* fix ruleset for missing_models
* fix ruleset for flags_flagstate
* add API endpoints for flags
* add tests for new API endpoints
* fix tests
* fix merge
* fix tests
---------
Co-authored-by: martin <martin@iggland.com>
Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.http.response import HttpResponse
|
||||
from django.urls import include, path, re_path
|
||||
from django.utils.decorators import method_decorator
|
||||
@ -480,6 +481,29 @@ class ProjectCodeDetail(RetrieveUpdateDestroyAPI):
|
||||
permission_classes = [permissions.IsAuthenticated, IsStaffOrReadOnly]
|
||||
|
||||
|
||||
class FlagList(ListAPI):
|
||||
"""List view for feature flags."""
|
||||
|
||||
queryset = settings.FLAGS
|
||||
serializer_class = common.serializers.FlagSerializer
|
||||
permission_classes = [permissions.AllowAny, ]
|
||||
|
||||
|
||||
class FlagDetail(RetrieveAPI):
|
||||
"""Detail view for an individual feature flag."""
|
||||
|
||||
serializer_class = common.serializers.FlagSerializer
|
||||
permission_classes = [permissions.AllowAny, ]
|
||||
|
||||
def get_object(self):
|
||||
"""Attempt to find a config object with the provided key."""
|
||||
key = self.kwargs['key']
|
||||
value = settings.FLAGS.get(key, None)
|
||||
if not value:
|
||||
raise NotFound()
|
||||
return {key: value}
|
||||
|
||||
|
||||
settings_api_urls = [
|
||||
# User settings
|
||||
re_path(r'^user/', include([
|
||||
@ -552,6 +576,11 @@ common_api_urls = [
|
||||
re_path(r'^.*$', NewsFeedEntryList.as_view(), name='api-news-list'),
|
||||
])),
|
||||
|
||||
# Flags
|
||||
path('flags/', include([
|
||||
path('<str:key>/', FlagDetail.as_view(), name='api-flag-detail'),
|
||||
re_path(r'^.*$', FlagList.as_view(), name='api-flag-list'),
|
||||
])),
|
||||
]
|
||||
|
||||
admin_api_urls = [
|
||||
|
@ -1,7 +1,9 @@
|
||||
"""JSON serializers for common components."""
|
||||
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from flags.state import flag_state
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.models import (InvenTreeSetting, InvenTreeUserSetting,
|
||||
@ -269,3 +271,19 @@ class ProjectCodeSerializer(InvenTreeModelSerializer):
|
||||
'code',
|
||||
'description'
|
||||
]
|
||||
|
||||
|
||||
class FlagSerializer(serializers.Serializer):
|
||||
"""Serializer for feature flags."""
|
||||
|
||||
def to_representation(self, instance):
|
||||
"""Return the configuration data as a dictionary."""
|
||||
request = self.context.get('request')
|
||||
if not isinstance(instance, str):
|
||||
instance = list(instance.keys())[0]
|
||||
data = {'key': instance, 'state': flag_state(instance, request=request)}
|
||||
|
||||
if request and request.user.is_superuser:
|
||||
data['conditions'] = self.instance[instance]
|
||||
|
||||
return data
|
||||
|
@ -875,6 +875,43 @@ class CommonTest(InvenTreeAPITestCase):
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
|
||||
def test_flag_api(self):
|
||||
"""Test flag URLs."""
|
||||
# Not superuser
|
||||
response = self.get(reverse('api-flag-list'), expected_code=200)
|
||||
self.assertEqual(len(response.data), 2)
|
||||
self.assertEqual(response.data[0]['key'], 'EXPERIMENTAL')
|
||||
|
||||
# Turn into superuser
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
|
||||
# Successful checks
|
||||
response = self.get(reverse('api-flag-list'), expected_code=200)
|
||||
self.assertEqual(len(response.data), 2)
|
||||
self.assertEqual(response.data[0]['key'], 'EXPERIMENTAL')
|
||||
self.assertTrue(response.data[0]['conditions'])
|
||||
|
||||
response = self.get(reverse('api-flag-detail', kwargs={'key': 'EXPERIMENTAL'}), expected_code=200)
|
||||
self.assertEqual(len(response.data), 3)
|
||||
self.assertEqual(response.data['key'], 'EXPERIMENTAL')
|
||||
self.assertTrue(response.data['conditions'])
|
||||
|
||||
# Try without param -> false
|
||||
response = self.get(reverse('api-flag-detail', kwargs={'key': 'NEXT_GEN'}), expected_code=200)
|
||||
self.assertFalse(response.data['state'])
|
||||
|
||||
# Try with param -> true
|
||||
response = self.get(reverse('api-flag-detail', kwargs={'key': 'NEXT_GEN'}), {'ngen': ''}, expected_code=200)
|
||||
self.assertTrue(response.data['state'])
|
||||
|
||||
# Try non existent flag
|
||||
response = self.get(reverse('api-flag-detail', kwargs={'key': 'NON_EXISTENT'}), expected_code=404)
|
||||
|
||||
# Turn into normal user again
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
|
||||
|
||||
class ColorThemeTest(TestCase):
|
||||
"""Tests for ColorTheme."""
|
||||
|
Reference in New Issue
Block a user