mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
[Bug] mport fix (#9008)
* Better handling of request object in serializers * Pass request object through - Required to extract user information * Strip column header during import - Prevent mismatch due to whitespace * Fix for "minimum stock" field * Fix for part serializer * Extract default values on import * Remove outdated migration message * Bump API version
This commit is contained in:
parent
eba004d835
commit
c077e2b605
@ -1,13 +1,17 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 308
|
INVENTREE_API_VERSION = 309
|
||||||
|
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v309 - 2025-02-02 : https://github.com/inventree/InvenTree/pull/9008
|
||||||
|
- Bug fixes for the "Part" serializer
|
||||||
|
- Fixes for data import API endpoints
|
||||||
|
|
||||||
v308 - 2025-02-01 : https://github.com/inventree/InvenTree/pull/9003
|
v308 - 2025-02-01 : https://github.com/inventree/InvenTree/pull/9003
|
||||||
- Adds extra detail to the ReportOutput and LabelOutput API endpoints
|
- Adds extra detail to the ReportOutput and LabelOutput API endpoints
|
||||||
- Allows ordering of output list endpoints
|
- Allows ordering of output list endpoints
|
||||||
|
@ -33,6 +33,9 @@ def log_error(path, error_name=None, error_info=None, error_data=None):
|
|||||||
"""
|
"""
|
||||||
from error_report.models import Error
|
from error_report.models import Error
|
||||||
|
|
||||||
|
if not path:
|
||||||
|
path = ''
|
||||||
|
|
||||||
kind, info, data = sys.exc_info()
|
kind, info, data = sys.exc_info()
|
||||||
|
|
||||||
# Check if the error is on the ignore list
|
# Check if the error is on the ignore list
|
||||||
@ -104,17 +107,19 @@ def exception_handler(exc, context):
|
|||||||
else:
|
else:
|
||||||
error_detail = _('Error details can be found in the admin panel')
|
error_detail = _('Error details can be found in the admin panel')
|
||||||
|
|
||||||
|
request = context.get('request')
|
||||||
|
path = request.path if request else ''
|
||||||
|
|
||||||
response_data = {
|
response_data = {
|
||||||
'error': type(exc).__name__,
|
'error': type(exc).__name__,
|
||||||
'error_class': str(type(exc)),
|
'error_class': str(type(exc)),
|
||||||
'detail': error_detail,
|
'detail': error_detail,
|
||||||
'path': context['request'].path,
|
'path': path,
|
||||||
'status_code': 500,
|
'status_code': 500,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = Response(response_data, status=500)
|
response = Response(response_data, status=500)
|
||||||
|
log_error(path)
|
||||||
log_error(context['request'].path)
|
|
||||||
|
|
||||||
if response is not None:
|
if response is not None:
|
||||||
# Convert errors returned under the label '__all__' to 'non_field_errors'
|
# Convert errors returned under the label '__all__' to 'non_field_errors'
|
||||||
|
@ -253,11 +253,12 @@ class BuildOutputSerializer(serializers.Serializer):
|
|||||||
# The build output must have all tracked parts allocated
|
# The build output must have all tracked parts allocated
|
||||||
if not build.is_output_fully_allocated(output):
|
if not build.is_output_fully_allocated(output):
|
||||||
# Check if the user has specified that incomplete allocations are ok
|
# Check if the user has specified that incomplete allocations are ok
|
||||||
|
if request := self.context.get('request'):
|
||||||
accept_incomplete = InvenTree.helpers.str2bool(
|
accept_incomplete = InvenTree.helpers.str2bool(
|
||||||
self.context['request'].data.get(
|
request.data.get('accept_incomplete_allocation', False)
|
||||||
'accept_incomplete_allocation', False
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
accept_incomplete = False
|
||||||
|
|
||||||
if not accept_incomplete:
|
if not accept_incomplete:
|
||||||
raise ValidationError(_('This build output is not fully allocated'))
|
raise ValidationError(_('This build output is not fully allocated'))
|
||||||
@ -439,6 +440,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
|
|||||||
"""Generate the new build output(s)."""
|
"""Generate the new build output(s)."""
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
|
request = self.context.get('request')
|
||||||
build = self.get_build()
|
build = self.get_build()
|
||||||
|
|
||||||
build.create_build_output(
|
build.create_build_output(
|
||||||
@ -447,7 +449,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
|
|||||||
batch=data.get('batch_code', ''),
|
batch=data.get('batch_code', ''),
|
||||||
location=data.get('location', None),
|
location=data.get('location', None),
|
||||||
auto_allocate=data.get('auto_allocate', False),
|
auto_allocate=data.get('auto_allocate', False),
|
||||||
user=self.context['request'].user,
|
user=request.user if request else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -531,7 +533,7 @@ class BuildOutputScrapSerializer(serializers.Serializer):
|
|||||||
def save(self):
|
def save(self):
|
||||||
"""Save the serializer to scrap the build outputs."""
|
"""Save the serializer to scrap the build outputs."""
|
||||||
build = self.context['build']
|
build = self.context['build']
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
outputs = data.get('outputs', [])
|
outputs = data.get('outputs', [])
|
||||||
|
|
||||||
@ -544,7 +546,7 @@ class BuildOutputScrapSerializer(serializers.Serializer):
|
|||||||
output,
|
output,
|
||||||
quantity,
|
quantity,
|
||||||
data.get('location', None),
|
data.get('location', None),
|
||||||
user=request.user,
|
user=request.user if request else None,
|
||||||
notes=data.get('notes', ''),
|
notes=data.get('notes', ''),
|
||||||
discard_allocations=data.get('discard_allocations', False),
|
discard_allocations=data.get('discard_allocations', False),
|
||||||
)
|
)
|
||||||
@ -619,7 +621,7 @@ class BuildOutputCompleteSerializer(serializers.Serializer):
|
|||||||
def save(self):
|
def save(self):
|
||||||
"""Save the serializer to complete the build outputs."""
|
"""Save the serializer to complete the build outputs."""
|
||||||
build = self.context['build']
|
build = self.context['build']
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
@ -642,7 +644,7 @@ class BuildOutputCompleteSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
build.complete_build_output(
|
build.complete_build_output(
|
||||||
output,
|
output,
|
||||||
request.user,
|
request.user if request else None,
|
||||||
location=location,
|
location=location,
|
||||||
status=status,
|
status=status,
|
||||||
notes=notes,
|
notes=notes,
|
||||||
@ -715,12 +717,12 @@ class BuildCancelSerializer(serializers.Serializer):
|
|||||||
def save(self):
|
def save(self):
|
||||||
"""Cancel the specified build."""
|
"""Cancel the specified build."""
|
||||||
build = self.context['build']
|
build = self.context['build']
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
build.cancel_build(
|
build.cancel_build(
|
||||||
request.user,
|
request.user if request else None,
|
||||||
remove_allocated_stock=data.get('remove_allocated_stock', False),
|
remove_allocated_stock=data.get('remove_allocated_stock', False),
|
||||||
remove_incomplete_outputs=data.get('remove_incomplete_outputs', False),
|
remove_incomplete_outputs=data.get('remove_incomplete_outputs', False),
|
||||||
)
|
)
|
||||||
@ -837,13 +839,13 @@ class BuildCompleteSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Complete the specified build output."""
|
"""Complete the specified build output."""
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
build = self.context['build']
|
build = self.context['build']
|
||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
build.complete_build(
|
build.complete_build(
|
||||||
request.user,
|
request.user if request else None,
|
||||||
trim_allocated_stock=data.get(
|
trim_allocated_stock=data.get(
|
||||||
'accept_overallocated', OverallocationChoice.REJECT
|
'accept_overallocated', OverallocationChoice.REJECT
|
||||||
)
|
)
|
||||||
|
@ -251,8 +251,8 @@ class NotificationMessageSerializer(InvenTreeModelSerializer):
|
|||||||
target['link'] = obj.target_object.get_absolute_url()
|
target['link'] = obj.target_object.get_absolute_url()
|
||||||
else:
|
else:
|
||||||
# check if user is staff - link to admin
|
# check if user is staff - link to admin
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
if request.user and request.user.is_staff:
|
if request and request.user and request.user.is_staff:
|
||||||
meta = obj.target_object._meta
|
meta = obj.target_object._meta
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -145,6 +145,7 @@ class DataImportSession(models.Model):
|
|||||||
|
|
||||||
- Extract column names from the data file
|
- Extract column names from the data file
|
||||||
- Create a default mapping for each field in the serializer
|
- Create a default mapping for each field in the serializer
|
||||||
|
- Find a default "backup" value for each field (if one exists)
|
||||||
"""
|
"""
|
||||||
# Extract list of column names from the file
|
# Extract list of column names from the file
|
||||||
self.columns = importer.operations.extract_column_names(self.data_file)
|
self.columns = importer.operations.extract_column_names(self.data_file)
|
||||||
@ -158,6 +159,7 @@ class DataImportSession(models.Model):
|
|||||||
|
|
||||||
matched_columns = set()
|
matched_columns = set()
|
||||||
|
|
||||||
|
self.field_defaults = self.field_defaults or {}
|
||||||
field_overrides = self.field_overrides or {}
|
field_overrides = self.field_overrides or {}
|
||||||
|
|
||||||
# Create a default mapping for each available field in the database
|
# Create a default mapping for each available field in the database
|
||||||
@ -167,6 +169,10 @@ class DataImportSession(models.Model):
|
|||||||
if field in field_overrides:
|
if field in field_overrides:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Extract a "default" value for the field, if one exists
|
||||||
|
if 'default' in field_def:
|
||||||
|
self.field_defaults[field] = field_def['default']
|
||||||
|
|
||||||
# Generate a list of possible column names for this field
|
# Generate a list of possible column names for this field
|
||||||
field_options = [
|
field_options = [
|
||||||
field,
|
field,
|
||||||
@ -558,7 +564,7 @@ class DataImportRow(models.Model):
|
|||||||
if not available_fields:
|
if not available_fields:
|
||||||
available_fields = self.session.available_fields()
|
available_fields = self.session.available_fields()
|
||||||
|
|
||||||
overrride_values = self.override_values
|
override_values = self.override_values
|
||||||
default_values = self.default_values
|
default_values = self.default_values
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
@ -566,8 +572,8 @@ class DataImportRow(models.Model):
|
|||||||
# We have mapped column (file) to field (serializer) already
|
# We have mapped column (file) to field (serializer) already
|
||||||
for field, col in field_mapping.items():
|
for field, col in field_mapping.items():
|
||||||
# Data override (force value and skip any further checks)
|
# Data override (force value and skip any further checks)
|
||||||
if field in overrride_values:
|
if field in override_values:
|
||||||
data[field] = overrride_values[field]
|
data[field] = override_values[field]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Default value (if provided)
|
# Default value (if provided)
|
||||||
@ -617,16 +623,19 @@ class DataImportRow(models.Model):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def construct_serializer(self):
|
def construct_serializer(self, request=None):
|
||||||
"""Construct a serializer object for this row."""
|
"""Construct a serializer object for this row."""
|
||||||
if serializer_class := self.session.serializer_class:
|
if serializer_class := self.session.serializer_class:
|
||||||
return serializer_class(data=self.serializer_data())
|
return serializer_class(
|
||||||
|
data=self.serializer_data(), context={'request': request}
|
||||||
|
)
|
||||||
|
|
||||||
def validate(self, commit=False) -> bool:
|
def validate(self, commit=False, request=None) -> bool:
|
||||||
"""Validate the data in this row against the linked serializer.
|
"""Validate the data in this row against the linked serializer.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
commit: If True, the data is saved to the database (if validation passes)
|
commit: If True, the data is saved to the database (if validation passes)
|
||||||
|
request: The request object (if available) for extracting user information
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if the data is valid, False otherwise
|
True if the data is valid, False otherwise
|
||||||
@ -638,7 +647,7 @@ class DataImportRow(models.Model):
|
|||||||
# Row has already been completed
|
# Row has already been completed
|
||||||
return True
|
return True
|
||||||
|
|
||||||
serializer = self.construct_serializer()
|
serializer = self.construct_serializer(request=request)
|
||||||
|
|
||||||
if not serializer:
|
if not serializer:
|
||||||
self.errors = {
|
self.errors = {
|
||||||
@ -660,12 +669,12 @@ class DataImportRow(models.Model):
|
|||||||
try:
|
try:
|
||||||
serializer.save()
|
serializer.save()
|
||||||
self.complete = True
|
self.complete = True
|
||||||
self.save()
|
|
||||||
|
|
||||||
self.session.check_complete()
|
except ValueError as e: # Exception as e:
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.errors = {'non_field_errors': str(e)}
|
self.errors = {'non_field_errors': str(e)}
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
self.session.check_complete()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -72,6 +72,8 @@ def extract_column_names(data_file) -> list:
|
|||||||
headers = []
|
headers = []
|
||||||
|
|
||||||
for idx, header in enumerate(data.headers):
|
for idx, header in enumerate(data.headers):
|
||||||
|
header = header.strip()
|
||||||
|
|
||||||
if header:
|
if header:
|
||||||
headers.append(header)
|
headers.append(header)
|
||||||
else:
|
else:
|
||||||
|
@ -207,8 +207,10 @@ class DataImportAcceptRowSerializer(serializers.Serializer):
|
|||||||
"""Complete the provided rows."""
|
"""Complete the provided rows."""
|
||||||
rows = self.validated_data['rows']
|
rows = self.validated_data['rows']
|
||||||
|
|
||||||
|
request = self.context.get('request', None)
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
row.validate(commit=True)
|
row.validate(commit=True, request=request)
|
||||||
|
|
||||||
if session := self.context.get('session', None):
|
if session := self.context.get('session', None):
|
||||||
session.check_complete()
|
session.check_complete()
|
||||||
|
@ -950,7 +950,7 @@ class PurchaseOrderReceiveSerializer(serializers.Serializer):
|
|||||||
"""Perform the actual database transaction to receive purchase order items."""
|
"""Perform the actual database transaction to receive purchase order items."""
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
order = self.context['order']
|
order = self.context['order']
|
||||||
|
|
||||||
items = data['items']
|
items = data['items']
|
||||||
@ -973,7 +973,7 @@ class PurchaseOrderReceiveSerializer(serializers.Serializer):
|
|||||||
item['line_item'],
|
item['line_item'],
|
||||||
loc,
|
loc,
|
||||||
item['quantity'],
|
item['quantity'],
|
||||||
request.user,
|
request.user if request else None,
|
||||||
status=item['status'],
|
status=item['status'],
|
||||||
barcode=item.get('barcode', ''),
|
barcode=item.get('barcode', ''),
|
||||||
batch_code=item.get('batch_code', ''),
|
batch_code=item.get('batch_code', ''),
|
||||||
@ -1434,8 +1434,8 @@ class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
user = request.user
|
user = request.user if request else None
|
||||||
|
|
||||||
# Extract shipping date (defaults to today's date)
|
# Extract shipping date (defaults to today's date)
|
||||||
now = current_date()
|
now = current_date()
|
||||||
@ -1572,10 +1572,10 @@ class SalesOrderCompleteSerializer(OrderAdjustSerializer):
|
|||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Save the serializer to complete the SalesOrder."""
|
"""Save the serializer to complete the SalesOrder."""
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
user = getattr(request, 'user', None)
|
user = request.user if request else None
|
||||||
|
|
||||||
self.order.ship_order(
|
self.order.ship_order(
|
||||||
user, allow_incomplete_lines=str2bool(data.get('accept_incomplete', False))
|
user, allow_incomplete_lines=str2bool(data.get('accept_incomplete', False))
|
||||||
@ -2020,7 +2020,7 @@ class ReturnOrderReceiveSerializer(serializers.Serializer):
|
|||||||
def save(self):
|
def save(self):
|
||||||
"""Saving this serializer marks the returned items as received."""
|
"""Saving this serializer marks the returned items as received."""
|
||||||
order = self.context['order']
|
order = self.context['order']
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
items = data['items']
|
items = data['items']
|
||||||
@ -2033,7 +2033,7 @@ class ReturnOrderReceiveSerializer(serializers.Serializer):
|
|||||||
order.receive_line_item(
|
order.receive_line_item(
|
||||||
line_item,
|
line_item,
|
||||||
location,
|
location,
|
||||||
request.user,
|
request.user if request else None,
|
||||||
note=data.get('note', ''),
|
note=data.get('note', ''),
|
||||||
status=item.get('status', None),
|
status=item.get('status', None),
|
||||||
)
|
)
|
||||||
|
@ -683,7 +683,7 @@ class PartSerializer(
|
|||||||
Used when displaying all details of a single component.
|
Used when displaying all details of a single component.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import_exclude_fields = ['duplicate']
|
import_exclude_fields = ['creation_date', 'creation_user', 'duplicate']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass defining serializer fields."""
|
"""Metaclass defining serializer fields."""
|
||||||
@ -760,7 +760,7 @@ class PartSerializer(
|
|||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
read_only_fields = ['barcode_hash', 'creation_date']
|
read_only_fields = ['barcode_hash', 'creation_date', 'creation_user']
|
||||||
|
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
@ -972,7 +972,9 @@ class PartSerializer(
|
|||||||
category_default_location = serializers.IntegerField(read_only=True)
|
category_default_location = serializers.IntegerField(read_only=True)
|
||||||
variant_stock = serializers.FloatField(read_only=True, label=_('Variant Stock'))
|
variant_stock = serializers.FloatField(read_only=True, label=_('Variant Stock'))
|
||||||
|
|
||||||
minimum_stock = serializers.FloatField()
|
minimum_stock = serializers.FloatField(
|
||||||
|
required=False, label=_('Minimum Stock'), default=0
|
||||||
|
)
|
||||||
|
|
||||||
image = InvenTree.serializers.InvenTreeImageSerializerField(
|
image = InvenTree.serializers.InvenTreeImageSerializerField(
|
||||||
required=False, allow_null=True
|
required=False, allow_null=True
|
||||||
@ -1062,8 +1064,8 @@ class PartSerializer(
|
|||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
|
|
||||||
# Save user information
|
# Save user information
|
||||||
if self.context['request']:
|
if request := self.context.get('request'):
|
||||||
instance.creation_user = self.context['request'].user
|
instance.creation_user = request.user
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
# Copy data from original Part
|
# Copy data from original Part
|
||||||
@ -1123,7 +1125,9 @@ class PartSerializer(
|
|||||||
part=instance, quantity=quantity, location=location
|
part=instance, quantity=quantity, location=location
|
||||||
)
|
)
|
||||||
|
|
||||||
stockitem.save(user=self.context['request'].user)
|
request = self.context.get('request', None)
|
||||||
|
user = request.user if request else None
|
||||||
|
stockitem.save(user=user)
|
||||||
|
|
||||||
# Create initial supplier information
|
# Create initial supplier information
|
||||||
if initial_supplier:
|
if initial_supplier:
|
||||||
@ -1222,9 +1226,8 @@ class PartStocktakeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
# Add in user information automatically
|
# Add in user information automatically
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
data['user'] = request.user
|
data['user'] = request.user if request else None
|
||||||
|
|
||||||
super().save()
|
super().save()
|
||||||
|
|
||||||
|
|
||||||
@ -1979,7 +1982,7 @@ class BomImportUploadSerializer(InvenTree.serializers.DataFileUploadSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class BomImportExtractSerializer(InvenTree.serializers.DataFileExtractSerializer):
|
class BomImportExtractSerializer(InvenTree.serializers.DataFileExtractSerializer):
|
||||||
"""Serializer class for exatracting BOM data from an uploaded file.
|
"""Serializer class for extracting BOM data from an uploaded file.
|
||||||
|
|
||||||
The parent class DataFileExtractSerializer does most of the heavy lifting here.
|
The parent class DataFileExtractSerializer does most of the heavy lifting here.
|
||||||
|
|
||||||
|
@ -46,9 +46,7 @@ def convert_legacy_labels(table_name, model_name, template_model):
|
|||||||
try:
|
try:
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Table likely does not exist
|
# Table likely does not exist - database was created more recently
|
||||||
if not InvenTree.ready.isInTestMode():
|
|
||||||
print(f"\nLegacy label table {table_name} not found - skipping migration")
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
@ -764,8 +764,8 @@ class SerializeStockItemSerializer(serializers.Serializer):
|
|||||||
def save(self):
|
def save(self):
|
||||||
"""Serialize stock item."""
|
"""Serialize stock item."""
|
||||||
item = self.context['item']
|
item = self.context['item']
|
||||||
request = self.context['request']
|
request = self.context.get('request')
|
||||||
user = request.user
|
user = request.user if request else None
|
||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user