Coverage for src/backend/InvenTree/importer/serializers.py: 58%
93 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""API serializers for the importer app."""
3import json
5from django.core.exceptions import ValidationError
6from django.utils.translation import gettext_lazy as _
8from rest_framework import serializers
10import importer.models
11import importer.registry
12from InvenTree.serializers import (
13 InvenTreeAttachmentSerializerField,
14 InvenTreeModelSerializer,
15)
16from users.serializers import UserSerializer
19class DataImportColumnMapSerializer(InvenTreeModelSerializer):
20 """Serializer for the DataImportColumnMap model."""
22 class Meta:
23 """Meta class options for the serializer."""
25 model = importer.models.DataImportColumnMap
26 fields = [
27 'pk',
28 'session',
29 'column',
30 'field',
31 'label',
32 'description',
33 'lookup_field',
34 ]
35 read_only_fields = ['field', 'session']
37 label = serializers.CharField(read_only=True)
38 description = serializers.CharField(read_only=True)
41class DataImportSessionSerializer(InvenTreeModelSerializer):
42 """Serializer for the DataImportSession model."""
44 class Meta:
45 """Meta class options for the serializer."""
47 model = importer.models.DataImportSession
48 fields = [
49 'pk',
50 'timestamp',
51 'data_file',
52 'update_records',
53 'model_type',
54 'available_fields',
55 'status',
56 'user',
57 'user_detail',
58 'columns',
59 'column_mappings',
60 'field_defaults',
61 'field_overrides',
62 'field_filters',
63 'row_count',
64 'completed_row_count',
65 'completed_row_count_history',
66 'row_count_history',
67 ]
68 read_only_fields = ['pk', 'user', 'status', 'columns']
70 def __init__(self, *args, **kwargs):
71 """Override the constructor for the DataImportSession serializer."""
72 super().__init__(*args, **kwargs)
74 self.fields['model_type'].choices = importer.registry.supported_model_options()
76 data_file = InvenTreeAttachmentSerializerField()
78 model_type = serializers.ChoiceField(
79 required=True,
80 allow_blank=False,
81 choices=importer.registry.supported_model_options(),
82 )
84 available_fields = serializers.JSONField(read_only=True)
86 row_count = serializers.IntegerField(read_only=True)
87 completed_row_count = serializers.IntegerField(read_only=True)
89 column_mappings = DataImportColumnMapSerializer(many=True, read_only=True)
91 user_detail = UserSerializer(source='user', read_only=True, many=False)
93 def validate_field_defaults(self, defaults):
94 """De-stringify the field defaults."""
95 if defaults is None: 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true
96 return None
98 if type(defaults) is not dict: 98 ↛ 104line 98 didn't jump to line 104 because the condition on line 98 was always true
99 try:
100 defaults = json.loads(str(defaults))
101 except:
102 raise ValidationError(_('Invalid field defaults'))
104 return defaults
106 def validate_field_overrides(self, overrides):
107 """De-stringify the field overrides."""
108 if overrides is None: 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true
109 return None
111 if type(overrides) is not dict: 111 ↛ 117line 111 didn't jump to line 117 because the condition on line 111 was always true
112 try:
113 overrides = json.loads(str(overrides))
114 except:
115 raise ValidationError(_('Invalid field overrides'))
117 return overrides
119 def validate_field_filters(self, filters):
120 """De-stringify the field filters."""
121 if filters is None: 121 ↛ 122line 121 didn't jump to line 122 because the condition on line 121 was never true
122 return None
124 if type(filters) is not dict: 124 ↛ 130line 124 didn't jump to line 130 because the condition on line 124 was always true
125 try:
126 filters = json.loads(str(filters))
127 except:
128 raise ValidationError(_('Invalid field filters'))
130 return filters
132 def create(self, validated_data):
133 """Override create method for this serializer.
135 Attach user information based on provided session data.
136 """
137 session = super().create(validated_data)
139 if request := self.context.get('request', None):
140 session.user = request.user
141 session.save()
143 return session
146class DataImportRowSerializer(InvenTreeModelSerializer):
147 """Serializer for the DataImportRow model."""
149 class Meta:
150 """Meta class options for the serializer."""
152 model = importer.models.DataImportRow
153 fields = [
154 'pk',
155 'session',
156 'row_index',
157 'row_data',
158 'data',
159 'errors',
160 'valid',
161 'complete',
162 ]
164 read_only_fields = [
165 'pk',
166 'session',
167 'row_index',
168 'row_data',
169 'errors',
170 'valid',
171 'complete',
172 ]
175class DataImportAcceptRowSerializer(serializers.Serializer):
176 """Serializer for accepting rows of data."""
178 class Meta:
179 """Serializer meta options."""
181 fields = ['rows']
183 rows = serializers.PrimaryKeyRelatedField(
184 queryset=importer.models.DataImportRow.objects.all(),
185 many=True,
186 required=True,
187 label=_('Rows'),
188 help_text=_('List of row IDs to accept'),
189 )
191 def validate_rows(self, rows):
192 """Ensure that the provided rows are valid.
194 - Row must point to the same import session
195 - Row must contain valid data
196 - Row must not have already been completed
197 """
198 session = self.context.get('session', None)
200 if not rows or len(rows) == 0: 200 ↛ 203line 200 didn't jump to line 203 because the condition on line 200 was always true
201 raise ValidationError(_('No rows provided'))
203 for row in rows:
204 if row.session != session:
205 raise ValidationError(_('Row does not belong to this session'))
207 if not row.valid:
208 raise ValidationError(_('Row contains invalid data'))
210 if row.complete:
211 raise ValidationError(_('Row has already been completed'))
213 return rows
215 def save(self):
216 """Complete the provided rows."""
217 rows = self.validated_data['rows']
219 request = self.context.get('request', None)
221 for row in rows:
222 row.validate(commit=True, request=request)
224 if session := self.context.get('session', None):
225 # ensure current state is available
226 session.refresh_from_db()
227 session.check_complete()
229 return rows