Merge pull request #1962 from SchrodingersGat/attachment-edit

Attachment edit
This commit is contained in:
Oliver 2021-08-16 11:14:14 +10:00 committed by GitHub
commit fa163b8866
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 125 additions and 8 deletions

View File

@ -5,8 +5,10 @@ Generic models which provide extra functionality over base Django model types.
from __future__ import unicode_literals
import os
import logging
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
@ -21,6 +23,9 @@ from mptt.exceptions import InvalidMove
from .validators import validate_tree_name
logger = logging.getLogger('inventree')
def rename_attachment(instance, filename):
"""
Function for renaming an attachment file.
@ -77,6 +82,72 @@ class InvenTreeAttachment(models.Model):
def basename(self):
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:
abstract = True

View File

@ -167,6 +167,18 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
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):
"""
Perform serializer validation.
@ -188,7 +200,10 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
# Update instance fields
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.
# Note that by default, DRF does *not* perform full model validation!
@ -208,6 +223,22 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
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):
"""
Override the DRF native FileField serializer,

View File

@ -10,7 +10,8 @@ from django.db.models import BooleanField
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 LocationSerializer
@ -158,7 +159,7 @@ class BuildItemSerializer(InvenTreeModelSerializer):
]
class BuildAttachmentSerializer(InvenTreeModelSerializer):
class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
"""
Serializer for a BuildAttachment
"""
@ -172,6 +173,7 @@ class BuildAttachmentSerializer(InvenTreeModelSerializer):
'pk',
'build',
'attachment',
'filename',
'comment',
'upload_date',
]

View File

@ -369,6 +369,7 @@ loadAttachmentTable(
constructForm(url, {
fields: {
filename: {},
comment: {},
},
onSuccess: reloadAttachmentTable,

View File

@ -14,6 +14,7 @@ from rest_framework import serializers
from sql_util.utils import SubqueryCount
from InvenTree.serializers import InvenTreeModelSerializer
from InvenTree.serializers import InvenTreeAttachmentSerializer
from InvenTree.serializers import InvenTreeMoneySerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField
@ -160,7 +161,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
]
class POAttachmentSerializer(InvenTreeModelSerializer):
class POAttachmentSerializer(InvenTreeAttachmentSerializer):
"""
Serializers for the PurchaseOrderAttachment model
"""
@ -174,6 +175,7 @@ class POAttachmentSerializer(InvenTreeModelSerializer):
'pk',
'order',
'attachment',
'filename',
'comment',
'upload_date',
]
@ -381,7 +383,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer):
]
class SOAttachmentSerializer(InvenTreeModelSerializer):
class SOAttachmentSerializer(InvenTreeAttachmentSerializer):
"""
Serializers for the SalesOrderAttachment model
"""
@ -395,6 +397,7 @@ class SOAttachmentSerializer(InvenTreeModelSerializer):
'pk',
'order',
'attachment',
'filename',
'comment',
'upload_date',
]

View File

@ -122,6 +122,7 @@
constructForm(url, {
fields: {
filename: {},
comment: {},
},
onSuccess: reloadAttachmentTable,

View File

@ -112,6 +112,7 @@
constructForm(url, {
fields: {
filename: {},
comment: {},
},
onSuccess: reloadAttachmentTable,

View File

@ -1,6 +1,7 @@
"""
JSON serializers for Part app
"""
import imghdr
from decimal import Decimal
@ -16,7 +17,9 @@ from djmoney.contrib.django_rest_framework import MoneyField
from InvenTree.serializers import (InvenTreeAttachmentSerializerField,
InvenTreeImageSerializerField,
InvenTreeModelSerializer,
InvenTreeAttachmentSerializer,
InvenTreeMoneySerializer)
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus
from stock.models import StockItem
@ -51,7 +54,7 @@ class CategorySerializer(InvenTreeModelSerializer):
]
class PartAttachmentSerializer(InvenTreeModelSerializer):
class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
"""
Serializer for the PartAttachment class
"""
@ -65,6 +68,7 @@ class PartAttachmentSerializer(InvenTreeModelSerializer):
'pk',
'part',
'attachment',
'filename',
'comment',
'upload_date',
]

View File

@ -868,6 +868,7 @@
constructForm(url, {
fields: {
filename: {},
comment: {},
},
title: '{% trans "Edit Attachment" %}',

View File

@ -25,7 +25,7 @@ import common.models
from company.serializers import SupplierPartSerializer
from part.serializers import PartBriefSerializer
from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField
from InvenTree.serializers import InvenTreeAttachmentSerializer, InvenTreeAttachmentSerializerField
class LocationBriefSerializer(InvenTreeModelSerializer):
@ -253,7 +253,7 @@ class LocationSerializer(InvenTreeModelSerializer):
]
class StockItemAttachmentSerializer(InvenTreeModelSerializer):
class StockItemAttachmentSerializer(InvenTreeAttachmentSerializer):
""" Serializer for StockItemAttachment model """
def __init__(self, *args, **kwargs):
@ -277,6 +277,7 @@ class StockItemAttachmentSerializer(InvenTreeModelSerializer):
'pk',
'stock_item',
'attachment',
'filename',
'comment',
'upload_date',
'user',

View File

@ -215,6 +215,7 @@
constructForm(url, {
fields: {
filename: {},
comment: {},
},
title: '{% trans "Edit Attachment" %}',