mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #1962 from SchrodingersGat/attachment-edit
Attachment edit
This commit is contained in:
commit
fa163b8866
@ -5,8 +5,10 @@ Generic models which provide extra functionality over base Django model types.
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -21,6 +23,9 @@ from mptt.exceptions import InvalidMove
|
|||||||
from .validators import validate_tree_name
|
from .validators import validate_tree_name
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
def rename_attachment(instance, filename):
|
def rename_attachment(instance, filename):
|
||||||
"""
|
"""
|
||||||
Function for renaming an attachment file.
|
Function for renaming an attachment file.
|
||||||
@ -77,6 +82,72 @@ class InvenTreeAttachment(models.Model):
|
|||||||
def basename(self):
|
def basename(self):
|
||||||
return os.path.basename(self.attachment.name)
|
return os.path.basename(self.attachment.name)
|
||||||
|
|
||||||
|
@basename.setter
|
||||||
|
def basename(self, fn):
|
||||||
|
"""
|
||||||
|
Function to rename the attachment file.
|
||||||
|
|
||||||
|
- Filename cannot be empty
|
||||||
|
- Filename cannot contain illegal characters
|
||||||
|
- Filename must specify an extension
|
||||||
|
- Filename cannot match an existing file
|
||||||
|
"""
|
||||||
|
|
||||||
|
fn = fn.strip()
|
||||||
|
|
||||||
|
if len(fn) == 0:
|
||||||
|
raise ValidationError(_('Filename must not be empty'))
|
||||||
|
|
||||||
|
attachment_dir = os.path.join(
|
||||||
|
settings.MEDIA_ROOT,
|
||||||
|
self.getSubdir()
|
||||||
|
)
|
||||||
|
|
||||||
|
old_file = os.path.join(
|
||||||
|
settings.MEDIA_ROOT,
|
||||||
|
self.attachment.name
|
||||||
|
)
|
||||||
|
|
||||||
|
new_file = os.path.join(
|
||||||
|
settings.MEDIA_ROOT,
|
||||||
|
self.getSubdir(),
|
||||||
|
fn
|
||||||
|
)
|
||||||
|
|
||||||
|
new_file = os.path.abspath(new_file)
|
||||||
|
|
||||||
|
# Check that there are no directory tricks going on...
|
||||||
|
if not os.path.dirname(new_file) == attachment_dir:
|
||||||
|
logger.error(f"Attempted to rename attachment outside valid directory: '{new_file}'")
|
||||||
|
raise ValidationError(_("Invalid attachment directory"))
|
||||||
|
|
||||||
|
# Ignore further checks if the filename is not actually being renamed
|
||||||
|
if new_file == old_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
forbidden = ["'", '"', "#", "@", "!", "&", "^", "<", ">", ":", ";", "/", "\\", "|", "?", "*", "%", "~", "`"]
|
||||||
|
|
||||||
|
for c in forbidden:
|
||||||
|
if c in fn:
|
||||||
|
raise ValidationError(_(f"Filename contains illegal character '{c}'"))
|
||||||
|
|
||||||
|
if len(fn.split('.')) < 2:
|
||||||
|
raise ValidationError(_("Filename missing extension"))
|
||||||
|
|
||||||
|
if not os.path.exists(old_file):
|
||||||
|
logger.error(f"Trying to rename attachment '{old_file}' which does not exist")
|
||||||
|
return
|
||||||
|
|
||||||
|
if os.path.exists(new_file):
|
||||||
|
raise ValidationError(_("Attachment with this filename already exists"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.rename(old_file, new_file)
|
||||||
|
self.attachment.name = os.path.join(self.getSubdir(), fn)
|
||||||
|
self.save()
|
||||||
|
except:
|
||||||
|
raise ValidationError(_("Error renaming file"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
@ -167,6 +167,18 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
"""
|
||||||
|
Catch any django ValidationError, and re-throw as a DRF ValidationError
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
instance = super().update(instance, validated_data)
|
||||||
|
except (ValidationError, DjangoValidationError) as exc:
|
||||||
|
raise ValidationError(detail=serializers.as_serializer_error(exc))
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
def run_validation(self, data=empty):
|
def run_validation(self, data=empty):
|
||||||
"""
|
"""
|
||||||
Perform serializer validation.
|
Perform serializer validation.
|
||||||
@ -188,7 +200,10 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
# Update instance fields
|
# Update instance fields
|
||||||
for attr, value in data.items():
|
for attr, value in data.items():
|
||||||
setattr(instance, attr, value)
|
try:
|
||||||
|
setattr(instance, attr, value)
|
||||||
|
except (ValidationError, DjangoValidationError) as exc:
|
||||||
|
raise ValidationError(detail=serializers.as_serializer_error(exc))
|
||||||
|
|
||||||
# Run a 'full_clean' on the model.
|
# Run a 'full_clean' on the model.
|
||||||
# Note that by default, DRF does *not* perform full model validation!
|
# Note that by default, DRF does *not* perform full model validation!
|
||||||
@ -208,6 +223,22 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeAttachmentSerializer(InvenTreeModelSerializer):
|
||||||
|
"""
|
||||||
|
Special case of an InvenTreeModelSerializer, which handles an "attachment" model.
|
||||||
|
|
||||||
|
The only real addition here is that we support "renaming" of the attachment file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The 'filename' field must be present in the serializer
|
||||||
|
filename = serializers.CharField(
|
||||||
|
label=_('Filename'),
|
||||||
|
required=False,
|
||||||
|
source='basename',
|
||||||
|
allow_blank=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvenTreeAttachmentSerializerField(serializers.FileField):
|
class InvenTreeAttachmentSerializerField(serializers.FileField):
|
||||||
"""
|
"""
|
||||||
Override the DRF native FileField serializer,
|
Override the DRF native FileField serializer,
|
||||||
|
@ -10,7 +10,8 @@ from django.db.models import BooleanField
|
|||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializerField, UserSerializerBrief
|
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
|
||||||
|
from InvenTree.serializers import InvenTreeAttachmentSerializerField, UserSerializerBrief
|
||||||
|
|
||||||
from stock.serializers import StockItemSerializerBrief
|
from stock.serializers import StockItemSerializerBrief
|
||||||
from stock.serializers import LocationSerializer
|
from stock.serializers import LocationSerializer
|
||||||
@ -158,7 +159,7 @@ class BuildItemSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BuildAttachmentSerializer(InvenTreeModelSerializer):
|
class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for a BuildAttachment
|
Serializer for a BuildAttachment
|
||||||
"""
|
"""
|
||||||
@ -172,6 +173,7 @@ class BuildAttachmentSerializer(InvenTreeModelSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'build',
|
'build',
|
||||||
'attachment',
|
'attachment',
|
||||||
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
]
|
]
|
||||||
|
@ -369,6 +369,7 @@ loadAttachmentTable(
|
|||||||
|
|
||||||
constructForm(url, {
|
constructForm(url, {
|
||||||
fields: {
|
fields: {
|
||||||
|
filename: {},
|
||||||
comment: {},
|
comment: {},
|
||||||
},
|
},
|
||||||
onSuccess: reloadAttachmentTable,
|
onSuccess: reloadAttachmentTable,
|
||||||
|
@ -14,6 +14,7 @@ from rest_framework import serializers
|
|||||||
from sql_util.utils import SubqueryCount
|
from sql_util.utils import SubqueryCount
|
||||||
|
|
||||||
from InvenTree.serializers import InvenTreeModelSerializer
|
from InvenTree.serializers import InvenTreeModelSerializer
|
||||||
|
from InvenTree.serializers import InvenTreeAttachmentSerializer
|
||||||
from InvenTree.serializers import InvenTreeMoneySerializer
|
from InvenTree.serializers import InvenTreeMoneySerializer
|
||||||
from InvenTree.serializers import InvenTreeAttachmentSerializerField
|
from InvenTree.serializers import InvenTreeAttachmentSerializerField
|
||||||
|
|
||||||
@ -160,7 +161,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class POAttachmentSerializer(InvenTreeModelSerializer):
|
class POAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||||
"""
|
"""
|
||||||
Serializers for the PurchaseOrderAttachment model
|
Serializers for the PurchaseOrderAttachment model
|
||||||
"""
|
"""
|
||||||
@ -174,6 +175,7 @@ class POAttachmentSerializer(InvenTreeModelSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'order',
|
'order',
|
||||||
'attachment',
|
'attachment',
|
||||||
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
]
|
]
|
||||||
@ -381,7 +383,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SOAttachmentSerializer(InvenTreeModelSerializer):
|
class SOAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||||
"""
|
"""
|
||||||
Serializers for the SalesOrderAttachment model
|
Serializers for the SalesOrderAttachment model
|
||||||
"""
|
"""
|
||||||
@ -395,6 +397,7 @@ class SOAttachmentSerializer(InvenTreeModelSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'order',
|
'order',
|
||||||
'attachment',
|
'attachment',
|
||||||
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
]
|
]
|
||||||
|
@ -122,6 +122,7 @@
|
|||||||
|
|
||||||
constructForm(url, {
|
constructForm(url, {
|
||||||
fields: {
|
fields: {
|
||||||
|
filename: {},
|
||||||
comment: {},
|
comment: {},
|
||||||
},
|
},
|
||||||
onSuccess: reloadAttachmentTable,
|
onSuccess: reloadAttachmentTable,
|
||||||
|
@ -112,6 +112,7 @@
|
|||||||
|
|
||||||
constructForm(url, {
|
constructForm(url, {
|
||||||
fields: {
|
fields: {
|
||||||
|
filename: {},
|
||||||
comment: {},
|
comment: {},
|
||||||
},
|
},
|
||||||
onSuccess: reloadAttachmentTable,
|
onSuccess: reloadAttachmentTable,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
JSON serializers for Part app
|
JSON serializers for Part app
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import imghdr
|
import imghdr
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
@ -16,7 +17,9 @@ from djmoney.contrib.django_rest_framework import MoneyField
|
|||||||
from InvenTree.serializers import (InvenTreeAttachmentSerializerField,
|
from InvenTree.serializers import (InvenTreeAttachmentSerializerField,
|
||||||
InvenTreeImageSerializerField,
|
InvenTreeImageSerializerField,
|
||||||
InvenTreeModelSerializer,
|
InvenTreeModelSerializer,
|
||||||
|
InvenTreeAttachmentSerializer,
|
||||||
InvenTreeMoneySerializer)
|
InvenTreeMoneySerializer)
|
||||||
|
|
||||||
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus
|
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem
|
||||||
|
|
||||||
@ -51,7 +54,7 @@ class CategorySerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PartAttachmentSerializer(InvenTreeModelSerializer):
|
class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for the PartAttachment class
|
Serializer for the PartAttachment class
|
||||||
"""
|
"""
|
||||||
@ -65,6 +68,7 @@ class PartAttachmentSerializer(InvenTreeModelSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'part',
|
'part',
|
||||||
'attachment',
|
'attachment',
|
||||||
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
]
|
]
|
||||||
|
@ -868,6 +868,7 @@
|
|||||||
|
|
||||||
constructForm(url, {
|
constructForm(url, {
|
||||||
fields: {
|
fields: {
|
||||||
|
filename: {},
|
||||||
comment: {},
|
comment: {},
|
||||||
},
|
},
|
||||||
title: '{% trans "Edit Attachment" %}',
|
title: '{% trans "Edit Attachment" %}',
|
||||||
|
@ -25,7 +25,7 @@ import common.models
|
|||||||
from company.serializers import SupplierPartSerializer
|
from company.serializers import SupplierPartSerializer
|
||||||
from part.serializers import PartBriefSerializer
|
from part.serializers import PartBriefSerializer
|
||||||
from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer
|
from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer
|
||||||
from InvenTree.serializers import InvenTreeAttachmentSerializerField
|
from InvenTree.serializers import InvenTreeAttachmentSerializer, InvenTreeAttachmentSerializerField
|
||||||
|
|
||||||
|
|
||||||
class LocationBriefSerializer(InvenTreeModelSerializer):
|
class LocationBriefSerializer(InvenTreeModelSerializer):
|
||||||
@ -253,7 +253,7 @@ class LocationSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockItemAttachmentSerializer(InvenTreeModelSerializer):
|
class StockItemAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||||
""" Serializer for StockItemAttachment model """
|
""" Serializer for StockItemAttachment model """
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -277,6 +277,7 @@ class StockItemAttachmentSerializer(InvenTreeModelSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'stock_item',
|
'stock_item',
|
||||||
'attachment',
|
'attachment',
|
||||||
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
'user',
|
'user',
|
||||||
|
@ -215,6 +215,7 @@
|
|||||||
|
|
||||||
constructForm(url, {
|
constructForm(url, {
|
||||||
fields: {
|
fields: {
|
||||||
|
filename: {},
|
||||||
comment: {},
|
comment: {},
|
||||||
},
|
},
|
||||||
title: '{% trans "Edit Attachment" %}',
|
title: '{% trans "Edit Attachment" %}',
|
||||||
|
Loading…
Reference in New Issue
Block a user