mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-19 10:19:27 -05:00
Fix: use api version > 7 for new custom field select format
This commit is contained in:
parent
082bf6fb8e
commit
707da93844
@ -573,3 +573,8 @@ Initial API version.
|
|||||||
#### Version 6
|
#### Version 6
|
||||||
|
|
||||||
- Moved acknowledge tasks endpoint to be under `/api/tasks/acknowledge/`.
|
- Moved acknowledge tasks endpoint to be under `/api/tasks/acknowledge/`.
|
||||||
|
|
||||||
|
#### Version 7
|
||||||
|
|
||||||
|
- The format of select type custom fields has changed to return the options
|
||||||
|
as an array of objects with `id` and `label` fields.
|
||||||
|
@ -3,7 +3,7 @@ const base_url = new URL(document.baseURI)
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
apiBaseUrl: document.baseURI + 'api/',
|
apiBaseUrl: document.baseURI + 'api/',
|
||||||
apiVersion: '6',
|
apiVersion: '7',
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
version: '2.14.5',
|
version: '2.14.5',
|
||||||
webSocketHost: window.location.host,
|
webSocketHost: window.location.host,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
apiBaseUrl: 'http://localhost:8000/api/',
|
apiBaseUrl: 'http://localhost:8000/api/',
|
||||||
apiVersion: '6',
|
apiVersion: '7',
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
version: 'DEVELOPMENT',
|
version: 'DEVELOPMENT',
|
||||||
webSocketHost: 'localhost:8000',
|
webSocketHost: 'localhost:8000',
|
||||||
|
@ -495,7 +495,27 @@ class StoragePathField(serializers.PrimaryKeyRelatedField):
|
|||||||
return StoragePath.objects.all()
|
return StoragePath.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ReadWriteSerializerMethodField(serializers.SerializerMethodField):
|
||||||
|
"""
|
||||||
|
Based on https://stackoverflow.com/a/62579804
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, method_name=None, *args, **kwargs):
|
||||||
|
self.method_name = method_name
|
||||||
|
kwargs["source"] = "*"
|
||||||
|
super(serializers.SerializerMethodField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
return {self.field_name: data}
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldSerializer(serializers.ModelSerializer):
|
class CustomFieldSerializer(serializers.ModelSerializer):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.api_version = int(
|
||||||
|
kwargs.pop("api_version", settings.REST_FRAMEWORK["ALLOWED_VERSIONS"][-1]),
|
||||||
|
)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
data_type = serializers.ChoiceField(
|
data_type = serializers.ChoiceField(
|
||||||
choices=CustomField.FieldDataType,
|
choices=CustomField.FieldDataType,
|
||||||
read_only=False,
|
read_only=False,
|
||||||
@ -503,6 +523,8 @@ class CustomFieldSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
document_count = serializers.IntegerField(read_only=True)
|
document_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
extra_data = ReadWriteSerializerMethodField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = [
|
fields = [
|
||||||
@ -544,15 +566,36 @@ class CustomFieldSerializer(serializers.ModelSerializer):
|
|||||||
or "select_options" not in attrs["extra_data"]
|
or "select_options" not in attrs["extra_data"]
|
||||||
or not isinstance(attrs["extra_data"]["select_options"], list)
|
or not isinstance(attrs["extra_data"]["select_options"], list)
|
||||||
or len(attrs["extra_data"]["select_options"]) == 0
|
or len(attrs["extra_data"]["select_options"]) == 0
|
||||||
or not all(
|
or (
|
||||||
|
# version 6 and below require a list of strings
|
||||||
|
self.api_version < 7
|
||||||
|
and not all(
|
||||||
|
len(option) > 0
|
||||||
|
for option in attrs["extra_data"]["select_options"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
# version 7 and above require a list of objects with labels
|
||||||
|
self.api_version >= 7
|
||||||
|
and not all(
|
||||||
len(option.get("label", "")) > 0
|
len(option.get("label", "")) > 0
|
||||||
for option in attrs["extra_data"]["select_options"]
|
for option in attrs["extra_data"]["select_options"]
|
||||||
)
|
)
|
||||||
|
)
|
||||||
):
|
):
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{"error": "extra_data.select_options must be a valid list"},
|
{"error": "extra_data.select_options must be a valid list"},
|
||||||
)
|
)
|
||||||
# labels are valid, generate ids if not present
|
# labels are valid, generate ids if not present
|
||||||
|
if self.api_version < 7:
|
||||||
|
attrs["extra_data"]["select_options"] = [
|
||||||
|
{
|
||||||
|
"label": option,
|
||||||
|
"id": get_random_string(length=16),
|
||||||
|
}
|
||||||
|
for option in attrs["extra_data"]["select_options"]
|
||||||
|
]
|
||||||
|
else:
|
||||||
for option in attrs["extra_data"]["select_options"]:
|
for option in attrs["extra_data"]["select_options"]:
|
||||||
if option.get("id") is None:
|
if option.get("id") is None:
|
||||||
option["id"] = get_random_string(length=16)
|
option["id"] = get_random_string(length=16)
|
||||||
@ -575,19 +618,15 @@ class CustomFieldSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
return super().validate(attrs)
|
return super().validate(attrs)
|
||||||
|
|
||||||
|
def get_extra_data(self, obj):
|
||||||
class ReadWriteSerializerMethodField(serializers.SerializerMethodField):
|
extra_data = obj.extra_data
|
||||||
"""
|
if self.api_version < 7 and obj.data_type == CustomField.FieldDataType.SELECT:
|
||||||
Based on https://stackoverflow.com/a/62579804
|
# Convert the select options with ids to a list of strings
|
||||||
"""
|
extra_data["select_options"] = [
|
||||||
|
option["label"] for option in extra_data["select_options"]
|
||||||
def __init__(self, method_name=None, *args, **kwargs):
|
]
|
||||||
self.method_name = method_name
|
field = serializers.JSONField()
|
||||||
kwargs["source"] = "*"
|
return field.to_representation(extra_data)
|
||||||
super(serializers.SerializerMethodField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
|
||||||
return {self.field_name: data}
|
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldInstanceSerializer(serializers.ModelSerializer):
|
class CustomFieldInstanceSerializer(serializers.ModelSerializer):
|
||||||
|
@ -43,10 +43,13 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
|
|||||||
]:
|
]:
|
||||||
resp = self.client.post(
|
resp = self.client.post(
|
||||||
self.ENDPOINT,
|
self.ENDPOINT,
|
||||||
data={
|
data=json.dumps(
|
||||||
|
{
|
||||||
"data_type": field_type,
|
"data_type": field_type,
|
||||||
"name": name,
|
"name": name,
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
@ -272,6 +275,59 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
|
|||||||
doc.refresh_from_db()
|
doc.refresh_from_db()
|
||||||
self.assertEqual(doc.custom_fields.first().value, None)
|
self.assertEqual(doc.custom_fields.first().value, None)
|
||||||
|
|
||||||
|
def test_custom_field_select_old_version(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Select custom field exists with old version of select options
|
||||||
|
WHEN:
|
||||||
|
- API post request is made for custom fields with api version header < 7
|
||||||
|
- API get request is made for custom fields with api version header < 7
|
||||||
|
THEN:
|
||||||
|
- The select options are returned in the old format
|
||||||
|
"""
|
||||||
|
resp = self.client.post(
|
||||||
|
self.ENDPOINT,
|
||||||
|
headers={"Accept": "application/json; version=6"},
|
||||||
|
data=json.dumps(
|
||||||
|
{
|
||||||
|
"data_type": "select",
|
||||||
|
"name": "Select Field",
|
||||||
|
"extra_data": {
|
||||||
|
"select_options": [
|
||||||
|
"Option 1",
|
||||||
|
"Option 2",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
field = CustomField.objects.get(name="Select Field")
|
||||||
|
self.assertEqual(
|
||||||
|
field.extra_data["select_options"],
|
||||||
|
[
|
||||||
|
{"label": "Option 1", "id": ANY},
|
||||||
|
{"label": "Option 2", "id": ANY},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = self.client.get(
|
||||||
|
f"{self.ENDPOINT}{field.id}/",
|
||||||
|
headers={"Accept": "application/json; version=6"},
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = resp.json()
|
||||||
|
self.assertEqual(
|
||||||
|
data["extra_data"]["select_options"],
|
||||||
|
[
|
||||||
|
"Option 1",
|
||||||
|
"Option 2",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_custom_field_monetary_validation(self):
|
def test_create_custom_field_monetary_validation(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
@ -2065,6 +2065,10 @@ class CustomFieldViewSet(ModelViewSet):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
kwargs.setdefault("api_version", self.request.version)
|
||||||
|
return super().get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SystemStatusView(PassUserMixin):
|
class SystemStatusView(PassUserMixin):
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
|
@ -341,10 +341,10 @@ REST_FRAMEWORK = {
|
|||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
],
|
],
|
||||||
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
|
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
|
||||||
"DEFAULT_VERSION": "1",
|
"DEFAULT_VERSION": "7",
|
||||||
# Make sure these are ordered and that the most recent version appears
|
# Make sure these are ordered and that the most recent version appears
|
||||||
# last
|
# last
|
||||||
"ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6"],
|
"ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6", "7"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user