mirror of
https://github.com/inventree/InvenTree.git
synced 2026-05-22 01:06:50 +00:00
Remove image download support (#11962)
* Remove image download support - Helper function remains (it is used in the supplier plugin mixin) - No longer available to user - Close massive security hole entirely - Will be defunct soon anyway (moving to generic attachments) * Update CHANGELOG.md * Bump API version * Fix for unit tests
This commit is contained in:
@@ -31,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
- [#11962](https://github.com/inventree/InvenTree/pull/11962) removes the "remote_image" field from the Part API endpoint, which (previously) allowed the user to specify a remote URL for an image to be downloaded and associated with the part. This field was removed due to security concerns around downloading images from arbitrary URLs. If you were using this field in an external client application, you will need to update your application to use the new "download_image_from_url" API endpoint instead.
|
||||||
|
|
||||||
## 1.3.0 - 2026-04-11
|
## 1.3.0 - 2026-04-11
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|||||||
@@ -33,14 +33,6 @@ Configuration of basic server settings:
|
|||||||
{{ globalsetting("DISPLAY_FULL_NAMES") }}
|
{{ globalsetting("DISPLAY_FULL_NAMES") }}
|
||||||
{{ globalsetting("DISPLAY_PROFILE_INFO") }}
|
{{ globalsetting("DISPLAY_PROFILE_INFO") }}
|
||||||
{{ globalsetting("WEEK_STARTS_ON") }}
|
{{ globalsetting("WEEK_STARTS_ON") }}
|
||||||
|
|
||||||
Configuration of image download settings:
|
|
||||||
|
|
||||||
| Name | Description | Default | Units |
|
|
||||||
| ---- | ----------- | ------- | ----- |
|
|
||||||
{{ globalsetting("INVENTREE_DOWNLOAD_FROM_URL") }}
|
|
||||||
{{ globalsetting("INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE") }}
|
|
||||||
{{ globalsetting("INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT") }}
|
|
||||||
{{ globalsetting("INVENTREE_STRICT_URLS") }}
|
{{ globalsetting("INVENTREE_STRICT_URLS") }}
|
||||||
|
|
||||||
Configuration of various scheduled tasks:
|
Configuration of various scheduled tasks:
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 488
|
INVENTREE_API_VERSION = 489
|
||||||
"""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 = """
|
||||||
|
|
||||||
|
v489 -> 2026-05-18 : https://github.com/inventree/InvenTree/pull/11962
|
||||||
|
- Removes the "remote_image" field from the Part API endpoint
|
||||||
|
- Removes the "remote_image" field from the Company API endpoint
|
||||||
|
|
||||||
v488 -> 2026-05-17 : https://github.com/inventree/InvenTree/pull/11920
|
v488 -> 2026-05-17 : https://github.com/inventree/InvenTree/pull/11920
|
||||||
- Allow renaming of attachments after upload via the API
|
- Allow renaming of attachments after upload via the API
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,12 @@ def validate_url_no_ssrf(url):
|
|||||||
raise ValueError(_('URL points to a private or reserved IP address'))
|
raise ValueError(_('URL points to a private or reserved IP address'))
|
||||||
|
|
||||||
|
|
||||||
def download_image_from_url(remote_url, timeout=2.5):
|
def download_image_from_url(
|
||||||
|
remote_url: str,
|
||||||
|
timeout: float = 2.5,
|
||||||
|
user_agent: str = '',
|
||||||
|
max_size: Optional[int] = None,
|
||||||
|
):
|
||||||
"""Download an image file from a remote URL.
|
"""Download an image file from a remote URL.
|
||||||
|
|
||||||
This is a potentially dangerous operation, so we must perform some checks:
|
This is a potentially dangerous operation, so we must perform some checks:
|
||||||
@@ -130,8 +135,9 @@ def download_image_from_url(remote_url, timeout=2.5):
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
remote_url: The remote URL to retrieve image
|
remote_url: The remote URL to retrieve image
|
||||||
max_size: Maximum allowed image size (default = 1MB)
|
|
||||||
timeout: Connection timeout in seconds (default = 5)
|
timeout: Connection timeout in seconds (default = 5)
|
||||||
|
user_agent: User-Agent string to use for the request (optional)
|
||||||
|
max_size: Maximum allowed image size (in bytes) (default = 1MB)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An in-memory PIL image file, if the download was successful
|
An in-memory PIL image file, if the download was successful
|
||||||
@@ -151,13 +157,9 @@ def download_image_from_url(remote_url, timeout=2.5):
|
|||||||
validate_url_no_ssrf(remote_url)
|
validate_url_no_ssrf(remote_url)
|
||||||
|
|
||||||
# Calculate maximum allowable image size (in bytes)
|
# Calculate maximum allowable image size (in bytes)
|
||||||
max_size = (
|
max_size = max_size or 1 * 1024 * 1024 # Default to 1MB if not provided
|
||||||
int(get_global_setting('INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE')) * 1024 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add user specified user-agent to request (if specified)
|
# Add user specified user-agent to request (if specified)
|
||||||
user_agent = get_global_setting('INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT')
|
|
||||||
|
|
||||||
headers = {'User-Agent': user_agent} if user_agent else None
|
headers = {'User-Agent': user_agent} if user_agent else None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ from rest_framework.serializers import DecimalField, Serializer
|
|||||||
from rest_framework.utils import model_meta
|
from rest_framework.utils import model_meta
|
||||||
from taggit.serializers import TaggitSerializer
|
from taggit.serializers import TaggitSerializer
|
||||||
|
|
||||||
import common.models as common_models
|
|
||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
from common.currency import currency_code_default, currency_code_mappings
|
from common.currency import currency_code_default, currency_code_mappings
|
||||||
from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField
|
from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField
|
||||||
@@ -785,51 +784,6 @@ class NotesFieldMixin:
|
|||||||
self.fields.pop('notes', None)
|
self.fields.pop('notes', None)
|
||||||
|
|
||||||
|
|
||||||
class RemoteImageMixin(metaclass=serializers.SerializerMetaclass):
|
|
||||||
"""Mixin class which allows downloading an 'image' from a remote URL.
|
|
||||||
|
|
||||||
Adds the optional, write-only `remote_image` field to the serializer
|
|
||||||
"""
|
|
||||||
|
|
||||||
def skip_create_fields(self):
|
|
||||||
"""Ensure the 'remote_image' field is skipped when creating a new instance."""
|
|
||||||
return ['remote_image']
|
|
||||||
|
|
||||||
remote_image = serializers.URLField(
|
|
||||||
required=False,
|
|
||||||
allow_blank=True,
|
|
||||||
write_only=True,
|
|
||||||
label=_('Remote Image'),
|
|
||||||
help_text=_('URL of remote image file'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_remote_image(self, url):
|
|
||||||
"""Perform custom validation for the remote image URL.
|
|
||||||
|
|
||||||
- Attempt to download the image and store it against this object instance
|
|
||||||
- Catches and re-throws any errors
|
|
||||||
"""
|
|
||||||
from InvenTree.helpers_model import download_image_from_url
|
|
||||||
|
|
||||||
if not url:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not common_models.InvenTreeSetting.get_setting(
|
|
||||||
'INVENTREE_DOWNLOAD_FROM_URL'
|
|
||||||
):
|
|
||||||
raise ValidationError(
|
|
||||||
_('Downloading images from remote URL is not enabled')
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.remote_image_file = download_image_from_url(url)
|
|
||||||
except Exception:
|
|
||||||
self.remote_image_file = None
|
|
||||||
raise ValidationError(_('Failed to download image from remote URL'))
|
|
||||||
|
|
||||||
return url
|
|
||||||
|
|
||||||
|
|
||||||
class ContentTypeField(serializers.ChoiceField):
|
class ContentTypeField(serializers.ChoiceField):
|
||||||
"""Serializer field which represents a ContentType as 'app_label.model_name'.
|
"""Serializer field which represents a ContentType as 'app_label.model_name'.
|
||||||
|
|
||||||
|
|||||||
@@ -738,21 +738,10 @@ class TestHelpers(TestCase):
|
|||||||
|
|
||||||
large_img = 'https://github.com/inventree/InvenTree/raw/master/src/backend/InvenTree/InvenTree/static/img/paper_splash_large.jpg'
|
large_img = 'https://github.com/inventree/InvenTree/raw/master/src/backend/InvenTree/InvenTree/static/img/paper_splash_large.jpg'
|
||||||
|
|
||||||
InvenTreeSetting.set_setting(
|
|
||||||
'INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE', 1, change_user=None
|
|
||||||
)
|
|
||||||
|
|
||||||
# Attempt to download an image which is too large
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
InvenTree.helpers_model.download_image_from_url(large_img, timeout=10)
|
|
||||||
|
|
||||||
# Increase allowable download size
|
|
||||||
InvenTreeSetting.set_setting(
|
|
||||||
'INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE', 5, change_user=None
|
|
||||||
)
|
|
||||||
|
|
||||||
# Download a valid image (should not throw an error)
|
# Download a valid image (should not throw an error)
|
||||||
InvenTree.helpers_model.download_image_from_url(large_img, timeout=10)
|
InvenTree.helpers_model.download_image_from_url(
|
||||||
|
large_img, timeout=10, max_size=10 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
def test_model_mixin(self):
|
def test_model_mixin(self):
|
||||||
"""Test the getModelsWithMixin function."""
|
"""Test the getModelsWithMixin function."""
|
||||||
|
|||||||
@@ -286,26 +286,6 @@ SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
|
|||||||
'choices': common.currency.currency_exchange_plugins,
|
'choices': common.currency.currency_exchange_plugins,
|
||||||
'default': 'inventreecurrencyexchange',
|
'default': 'inventreecurrencyexchange',
|
||||||
},
|
},
|
||||||
'INVENTREE_DOWNLOAD_FROM_URL': {
|
|
||||||
'name': _('Download from URL'),
|
|
||||||
'description': _('Allow download of remote images and files from external URL'),
|
|
||||||
'validator': bool,
|
|
||||||
'default': False,
|
|
||||||
},
|
|
||||||
'INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE': {
|
|
||||||
'name': _('Download Size Limit'),
|
|
||||||
'description': _('Maximum allowable download size for remote image'),
|
|
||||||
'units': 'MB',
|
|
||||||
'default': 1,
|
|
||||||
'validator': [int, MinValueValidator(1), MaxValueValidator(25)],
|
|
||||||
},
|
|
||||||
'INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT': {
|
|
||||||
'name': _('User-agent used to download from URL'),
|
|
||||||
'description': _(
|
|
||||||
'Allow to override the user-agent used to download images and files from external URL (leave blank for the default)'
|
|
||||||
),
|
|
||||||
'default': '',
|
|
||||||
},
|
|
||||||
'INVENTREE_STRICT_URLS': {
|
'INVENTREE_STRICT_URLS': {
|
||||||
'name': _('Strict URL Validation'),
|
'name': _('Strict URL Validation'),
|
||||||
'description': _('Require schema specification when validating URLs'),
|
'description': _('Require schema specification when validating URLs'),
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
"""JSON serializers for Company app."""
|
"""JSON serializers for Company app."""
|
||||||
|
|
||||||
import io
|
|
||||||
|
|
||||||
from django.core.files.base import ContentFile
|
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@@ -26,7 +23,6 @@ from InvenTree.serializers import (
|
|||||||
InvenTreeTagModelSerializer,
|
InvenTreeTagModelSerializer,
|
||||||
NotesFieldMixin,
|
NotesFieldMixin,
|
||||||
OptionalField,
|
OptionalField,
|
||||||
RemoteImageMixin,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -113,7 +109,6 @@ class CompanySerializer(
|
|||||||
FilterableSerializerMixin,
|
FilterableSerializerMixin,
|
||||||
DataImportExportSerializerMixin,
|
DataImportExportSerializerMixin,
|
||||||
NotesFieldMixin,
|
NotesFieldMixin,
|
||||||
RemoteImageMixin,
|
|
||||||
InvenTreeModelSerializer,
|
InvenTreeModelSerializer,
|
||||||
):
|
):
|
||||||
"""Serializer for Company object (full detail)."""
|
"""Serializer for Company object (full detail)."""
|
||||||
@@ -145,7 +140,6 @@ class CompanySerializer(
|
|||||||
'notes',
|
'notes',
|
||||||
'parts_supplied',
|
'parts_supplied',
|
||||||
'parts_manufactured',
|
'parts_manufactured',
|
||||||
'remote_image',
|
|
||||||
'primary_address',
|
'primary_address',
|
||||||
'tax_id',
|
'tax_id',
|
||||||
'parameters',
|
'parameters',
|
||||||
@@ -193,27 +187,6 @@ class CompanySerializer(
|
|||||||
|
|
||||||
parameters = common.filters.enable_parameters_filter()
|
parameters = common.filters.enable_parameters_filter()
|
||||||
|
|
||||||
def save(self):
|
|
||||||
"""Save the Company instance."""
|
|
||||||
super().save()
|
|
||||||
|
|
||||||
company = self.instance
|
|
||||||
|
|
||||||
# Check if an image was downloaded from a remote URL
|
|
||||||
remote_img = getattr(self, 'remote_image_file', None)
|
|
||||||
|
|
||||||
if remote_img and company:
|
|
||||||
fmt = remote_img.format or 'PNG'
|
|
||||||
buffer = io.BytesIO()
|
|
||||||
remote_img.save(buffer, format=fmt)
|
|
||||||
|
|
||||||
# Construct a simplified name for the image
|
|
||||||
filename = f'company_{company.pk}_image.{fmt.lower()}'
|
|
||||||
|
|
||||||
company.image.save(filename, ContentFile(buffer.getvalue()))
|
|
||||||
|
|
||||||
return self.instance
|
|
||||||
|
|
||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
class ContactSerializer(DataImportExportSerializerMixin, InvenTreeModelSerializer):
|
class ContactSerializer(DataImportExportSerializerMixin, InvenTreeModelSerializer):
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ImporterTest(ImporterMixin, InvenTreeTestCase):
|
|||||||
|
|
||||||
session.extract_columns()
|
session.extract_columns()
|
||||||
|
|
||||||
self.assertEqual(session.column_mappings.count(), 15)
|
self.assertEqual(session.column_mappings.count(), 14)
|
||||||
|
|
||||||
# Check some of the field mappings
|
# Check some of the field mappings
|
||||||
for field, col in [
|
for field, col in [
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
"""DRF data serializers for Part app."""
|
"""DRF data serializers for Part app."""
|
||||||
|
|
||||||
import io
|
|
||||||
import os
|
import os
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.files.base import ContentFile
|
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import ExpressionWrapper, F, Q
|
from django.db.models import ExpressionWrapper, F, Q
|
||||||
@@ -559,7 +557,6 @@ class PartSerializer(
|
|||||||
InvenTree.serializers.FilterableSerializerMixin,
|
InvenTree.serializers.FilterableSerializerMixin,
|
||||||
DataImportExportSerializerMixin,
|
DataImportExportSerializerMixin,
|
||||||
InvenTree.serializers.NotesFieldMixin,
|
InvenTree.serializers.NotesFieldMixin,
|
||||||
InvenTree.serializers.RemoteImageMixin,
|
|
||||||
InvenTree.serializers.InvenTreeTaggitSerializer,
|
InvenTree.serializers.InvenTreeTaggitSerializer,
|
||||||
InvenTree.serializers.InvenTreeModelSerializer,
|
InvenTree.serializers.InvenTreeModelSerializer,
|
||||||
):
|
):
|
||||||
@@ -592,7 +589,6 @@ class PartSerializer(
|
|||||||
'description',
|
'description',
|
||||||
'full_name',
|
'full_name',
|
||||||
'image',
|
'image',
|
||||||
'remote_image',
|
|
||||||
'existing_image',
|
'existing_image',
|
||||||
'IPN',
|
'IPN',
|
||||||
'is_template',
|
'is_template',
|
||||||
@@ -662,7 +658,7 @@ class PartSerializer(
|
|||||||
# These fields are only used for the LIST API endpoint
|
# These fields are only used for the LIST API endpoint
|
||||||
for f in self.skip_create_fields():
|
for f in self.skip_create_fields():
|
||||||
# Fields required for certain operations, but are not part of the model
|
# Fields required for certain operations, but are not part of the model
|
||||||
if f in ['remote_image', 'existing_image']:
|
if f in ['existing_image']:
|
||||||
continue
|
continue
|
||||||
self.fields.pop(f, None)
|
self.fields.pop(f, None)
|
||||||
|
|
||||||
@@ -1108,6 +1104,7 @@ class PartSerializer(
|
|||||||
part = self.instance
|
part = self.instance
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
|
# TODO: Remove the existing_image field entirely!
|
||||||
existing_image = data.pop('existing_image', None)
|
existing_image = data.pop('existing_image', None)
|
||||||
|
|
||||||
if existing_image:
|
if existing_image:
|
||||||
@@ -1116,19 +1113,6 @@ class PartSerializer(
|
|||||||
part.image = img_path
|
part.image = img_path
|
||||||
part.save()
|
part.save()
|
||||||
|
|
||||||
# Check if an image was downloaded from a remote URL
|
|
||||||
remote_img = getattr(self, 'remote_image_file', None)
|
|
||||||
|
|
||||||
if remote_img and part:
|
|
||||||
fmt = remote_img.format or 'PNG'
|
|
||||||
buffer = io.BytesIO()
|
|
||||||
remote_img.save(buffer, format=fmt)
|
|
||||||
|
|
||||||
# Construct a simplified name for the image
|
|
||||||
filename = f'part_{part.pk}_image.{fmt.lower()}'
|
|
||||||
|
|
||||||
part.image.save(filename, ContentFile(buffer.getvalue()))
|
|
||||||
|
|
||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ import { showNotification } from '@mantine/notifications';
|
|||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { showApiErrorMessage } from '../../functions/notifications';
|
import { showApiErrorMessage } from '../../functions/notifications';
|
||||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
|
||||||
import { useGlobalSettingsState } from '../../states/SettingsStates';
|
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { PartThumbTable } from '../../tables/part/PartThumbTable';
|
import { PartThumbTable } from '../../tables/part/PartThumbTable';
|
||||||
import { vars } from '../../theme';
|
import { vars } from '../../theme';
|
||||||
@@ -320,8 +318,7 @@ function ImageActionButtons({
|
|||||||
apiPath,
|
apiPath,
|
||||||
hasImage,
|
hasImage,
|
||||||
pk,
|
pk,
|
||||||
setImage,
|
setImage
|
||||||
downloadImage
|
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
actions?: DetailImageButtonProps;
|
actions?: DetailImageButtonProps;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -329,10 +326,7 @@ function ImageActionButtons({
|
|||||||
hasImage: boolean;
|
hasImage: boolean;
|
||||||
pk: string;
|
pk: string;
|
||||||
setImage: (image: string) => void;
|
setImage: (image: string) => void;
|
||||||
downloadImage: () => void;
|
|
||||||
}>) {
|
}>) {
|
||||||
const globalSettings = useGlobalSettingsState();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{visible && (
|
{visible && (
|
||||||
@@ -363,25 +357,6 @@ function ImageActionButtons({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{actions.downloadImage &&
|
|
||||||
globalSettings.isSet('INVENTREE_DOWNLOAD_FROM_URL') && (
|
|
||||||
<ActionButton
|
|
||||||
icon={
|
|
||||||
<InvenTreeIcon
|
|
||||||
icon='download'
|
|
||||||
iconProps={{ color: 'white' }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
tooltip={t`Download remote image`}
|
|
||||||
variant='outline'
|
|
||||||
size='lg'
|
|
||||||
tooltipAlignment='top'
|
|
||||||
onClick={(event: any) => {
|
|
||||||
cancelEvent(event);
|
|
||||||
downloadImage();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{actions.uploadFile && (
|
{actions.uploadFile && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={
|
icon={
|
||||||
@@ -439,21 +414,6 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
|
|||||||
|
|
||||||
const permissions = useUserState();
|
const permissions = useUserState();
|
||||||
|
|
||||||
const downloadImage = useEditApiFormModal({
|
|
||||||
url: props.apiPath,
|
|
||||||
title: t`Download Image`,
|
|
||||||
fields: {
|
|
||||||
remote_image: {}
|
|
||||||
},
|
|
||||||
timeout: 10000,
|
|
||||||
successMessage: t`Image downloaded successfully`,
|
|
||||||
onFormSuccess: (response: any) => {
|
|
||||||
if (response.image) {
|
|
||||||
setAndRefresh(response.image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasOverlay: boolean = useMemo(() => {
|
const hasOverlay: boolean = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
props.imageActions?.selectExisting ||
|
props.imageActions?.selectExisting ||
|
||||||
@@ -473,7 +433,6 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{downloadImage.modal}
|
|
||||||
<Grid.Col span={{ base: 12, sm: 4 }}>
|
<Grid.Col span={{ base: 12, sm: 4 }}>
|
||||||
<AspectRatio
|
<AspectRatio
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -502,7 +461,6 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
|
|||||||
hasImage={!!props.src}
|
hasImage={!!props.src}
|
||||||
pk={props.pk}
|
pk={props.pk}
|
||||||
setImage={setAndRefresh}
|
setImage={setAndRefresh}
|
||||||
downloadImage={downloadImage.open}
|
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -59,15 +59,7 @@ export default function SystemSettings() {
|
|||||||
'INVENTREE_RESTRICT_ABOUT',
|
'INVENTREE_RESTRICT_ABOUT',
|
||||||
'DISPLAY_FULL_NAMES',
|
'DISPLAY_FULL_NAMES',
|
||||||
'DISPLAY_PROFILE_INFO',
|
'DISPLAY_PROFILE_INFO',
|
||||||
'WEEK_STARTS_ON'
|
'WEEK_STARTS_ON',
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<GlobalSettingList
|
|
||||||
heading={t`Image Download Settings`}
|
|
||||||
keys={[
|
|
||||||
'INVENTREE_DOWNLOAD_FROM_URL',
|
|
||||||
'INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE',
|
|
||||||
'INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT',
|
|
||||||
'INVENTREE_STRICT_URLS'
|
'INVENTREE_STRICT_URLS'
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user