mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Merge pull request #1962 from SchrodingersGat/attachment-edit
Attachment edit
This commit is contained in:
		| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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', | ||||
|         ] | ||||
|   | ||||
| @@ -369,6 +369,7 @@ loadAttachmentTable( | ||||
|  | ||||
|             constructForm(url, { | ||||
|                 fields: { | ||||
|                     filename: {}, | ||||
|                     comment: {}, | ||||
|                 }, | ||||
|                 onSuccess: reloadAttachmentTable, | ||||
|   | ||||
| @@ -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', | ||||
|         ] | ||||
|   | ||||
| @@ -122,6 +122,7 @@ | ||||
|  | ||||
|                 constructForm(url, { | ||||
|                     fields: { | ||||
|                         filename: {}, | ||||
|                         comment: {}, | ||||
|                     }, | ||||
|                     onSuccess: reloadAttachmentTable, | ||||
|   | ||||
| @@ -112,6 +112,7 @@ | ||||
|  | ||||
|                 constructForm(url, { | ||||
|                     fields: { | ||||
|                         filename: {}, | ||||
|                         comment: {}, | ||||
|                     }, | ||||
|                     onSuccess: reloadAttachmentTable, | ||||
|   | ||||
| @@ -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', | ||||
|         ] | ||||
|   | ||||
| @@ -868,6 +868,7 @@ | ||||
|      | ||||
|                     constructForm(url, { | ||||
|                         fields: { | ||||
|                             filename: {}, | ||||
|                             comment: {}, | ||||
|                         }, | ||||
|                         title: '{% trans "Edit Attachment" %}', | ||||
|   | ||||
| @@ -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', | ||||
|   | ||||
| @@ -215,6 +215,7 @@ | ||||
|  | ||||
|                 constructForm(url, { | ||||
|                     fields: { | ||||
|                         filename: {}, | ||||
|                         comment: {}, | ||||
|                     }, | ||||
|                     title: '{% trans "Edit Attachment" %}', | ||||
|   | ||||
		Reference in New Issue
	
	Block a user