2
0
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:
Matthias Mair
2023-06-12 05:13:53 +02:00
committed by GitHub
parent 15ab911da6
commit 9f56ee1023
8 changed files with 118 additions and 0 deletions

View File

@ -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 = [

View File

@ -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

View File

@ -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."""