djresttoolkit 0.8.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- README.md +72 -5
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.9.0.dist-info}/METADATA +73 -6
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.9.0.dist-info}/RECORD +11 -7
- src/djresttoolkit/envconfig/_env_settings.py +3 -3
- src/djresttoolkit/serializers/__init__.py +0 -0
- src/djresttoolkit/serializers/_serializer_create_mixin.py +87 -0
- src/djresttoolkit/serializers/mixins/__init__.py +6 -0
- src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py +94 -0
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.9.0.dist-info}/WHEEL +0 -0
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.9.0.dist-info}/entry_points.txt +0 -0
- {djresttoolkit-0.8.0.dist-info → djresttoolkit-0.9.0.dist-info}/licenses/LICENSE +0 -0
README.md
CHANGED
@@ -162,11 +162,18 @@ Supports **nested configuration** using double underscores (`__`) in environment
|
|
162
162
|
|
163
163
|
#### Class Attributes
|
164
164
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
165
|
+
- Attributes
|
166
|
+
- `env_file`
|
167
|
+
- Type: `str`
|
168
|
+
- Default: `.env`
|
169
|
+
- Description: Environment variable file path.
|
170
|
+
- `yaml_file`
|
171
|
+
- Type: `str`
|
172
|
+
- Default: `.environ.yaml`
|
173
|
+
- Description: YAML configuration file path.
|
174
|
+
- `model_config`
|
175
|
+
- Type: `SettingsConfigDict`
|
176
|
+
- Description: Pydantic settings configuration (file encoding, nested delimiter).
|
170
177
|
|
171
178
|
#### Methods
|
172
179
|
|
@@ -402,6 +409,66 @@ ThrottleInspector(
|
|
402
409
|
- `attach_headers(response: Response, throttle_info: dict | None)`
|
403
410
|
Attaches throttle data to HTTP headers.
|
404
411
|
|
412
|
+
### 8. AbsoluteUrlFileMixin — API Reference
|
413
|
+
|
414
|
+
```python
|
415
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
416
|
+
```
|
417
|
+
|
418
|
+
### `AbsoluteUrlFileMixin`
|
419
|
+
|
420
|
+
A **serializer mixin** that converts **FileField** and **ImageField** URLs to **absolute URLs**, ensuring compatibility with cloud storage backends.
|
421
|
+
|
422
|
+
---
|
423
|
+
|
424
|
+
### Attributes
|
425
|
+
|
426
|
+
- `file_fields`
|
427
|
+
- type: `list[str] | None`
|
428
|
+
- default: `None`
|
429
|
+
- description: Manual list of file field names for non-model serializers.
|
430
|
+
|
431
|
+
### Absolute Url File Mixin Methods
|
432
|
+
|
433
|
+
#### `to_representation(self, instance: Any) -> dict[str, Any]`
|
434
|
+
|
435
|
+
- Overrides default serializer `to_representation`.
|
436
|
+
- Enhances all file-related fields in the serialized output to **absolute URLs**.
|
437
|
+
|
438
|
+
#### `enhance_file_fields(self, instance: Any, representation: dict[str, Any], request: Any) -> dict[str, Any]`
|
439
|
+
|
440
|
+
- Core logic to process each file field.
|
441
|
+
- Converts relative URLs to absolute URLs using `request.build_absolute_uri()`.
|
442
|
+
- Supports model serializers or manual `file_fields`.
|
443
|
+
- Logs warnings if request context is missing or file is not found.
|
444
|
+
|
445
|
+
#### Exceptions
|
446
|
+
|
447
|
+
- `MissingRequestContext`: Raised if the request object is missing in serializer context and `DEBUG=True`.
|
448
|
+
|
449
|
+
### Absolute Url File Mixin Example
|
450
|
+
|
451
|
+
```python
|
452
|
+
from rest_framework import serializers
|
453
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
454
|
+
from myapp.models import Document
|
455
|
+
|
456
|
+
class DocumentSerializer(AbsoluteUrlFileMixin, serializers.ModelSerializer):
|
457
|
+
class Meta:
|
458
|
+
model = Document
|
459
|
+
fields = ["id", "title", "file"]
|
460
|
+
|
461
|
+
# Output will convert `file` field to an absolute URL
|
462
|
+
serializer = DocumentSerializer(instance, context={"request": request})
|
463
|
+
data = serializer.data
|
464
|
+
```
|
465
|
+
|
466
|
+
#### Notes
|
467
|
+
|
468
|
+
- Works with both Django model serializers and custom serializers.
|
469
|
+
- Relative file paths are automatically converted to absolute URLs.
|
470
|
+
- Can manually specify fields via `file_fields` for non-model serializers.
|
471
|
+
|
405
472
|
## 🛠️ Planned Features
|
406
473
|
|
407
474
|
- Add more utils
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: djresttoolkit
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.0
|
4
4
|
Summary: A collection of Django and DRF utilities to simplify API development.
|
5
5
|
Project-URL: Homepage, https://github.com/shaileshpandit141/djresttoolkit
|
6
6
|
Project-URL: Documentation, https://shaileshpandit141.github.io/djresttoolkit
|
@@ -220,11 +220,18 @@ Supports **nested configuration** using double underscores (`__`) in environment
|
|
220
220
|
|
221
221
|
#### Class Attributes
|
222
222
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
223
|
+
- Attributes
|
224
|
+
- `env_file`
|
225
|
+
- Type: `str`
|
226
|
+
- Default: `.env`
|
227
|
+
- Description: Environment variable file path.
|
228
|
+
- `yaml_file`
|
229
|
+
- Type: `str`
|
230
|
+
- Default: `.environ.yaml`
|
231
|
+
- Description: YAML configuration file path.
|
232
|
+
- `model_config`
|
233
|
+
- Type: `SettingsConfigDict`
|
234
|
+
- Description: Pydantic settings configuration (file encoding, nested delimiter).
|
228
235
|
|
229
236
|
#### Methods
|
230
237
|
|
@@ -460,6 +467,66 @@ ThrottleInspector(
|
|
460
467
|
- `attach_headers(response: Response, throttle_info: dict | None)`
|
461
468
|
Attaches throttle data to HTTP headers.
|
462
469
|
|
470
|
+
### 8. AbsoluteUrlFileMixin — API Reference
|
471
|
+
|
472
|
+
```python
|
473
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
474
|
+
```
|
475
|
+
|
476
|
+
### `AbsoluteUrlFileMixin`
|
477
|
+
|
478
|
+
A **serializer mixin** that converts **FileField** and **ImageField** URLs to **absolute URLs**, ensuring compatibility with cloud storage backends.
|
479
|
+
|
480
|
+
---
|
481
|
+
|
482
|
+
### Attributes
|
483
|
+
|
484
|
+
- `file_fields`
|
485
|
+
- type: `list[str] | None`
|
486
|
+
- default: `None`
|
487
|
+
- description: Manual list of file field names for non-model serializers.
|
488
|
+
|
489
|
+
### Absolute Url File Mixin Methods
|
490
|
+
|
491
|
+
#### `to_representation(self, instance: Any) -> dict[str, Any]`
|
492
|
+
|
493
|
+
- Overrides default serializer `to_representation`.
|
494
|
+
- Enhances all file-related fields in the serialized output to **absolute URLs**.
|
495
|
+
|
496
|
+
#### `enhance_file_fields(self, instance: Any, representation: dict[str, Any], request: Any) -> dict[str, Any]`
|
497
|
+
|
498
|
+
- Core logic to process each file field.
|
499
|
+
- Converts relative URLs to absolute URLs using `request.build_absolute_uri()`.
|
500
|
+
- Supports model serializers or manual `file_fields`.
|
501
|
+
- Logs warnings if request context is missing or file is not found.
|
502
|
+
|
503
|
+
#### Exceptions
|
504
|
+
|
505
|
+
- `MissingRequestContext`: Raised if the request object is missing in serializer context and `DEBUG=True`.
|
506
|
+
|
507
|
+
### Absolute Url File Mixin Example
|
508
|
+
|
509
|
+
```python
|
510
|
+
from rest_framework import serializers
|
511
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
512
|
+
from myapp.models import Document
|
513
|
+
|
514
|
+
class DocumentSerializer(AbsoluteUrlFileMixin, serializers.ModelSerializer):
|
515
|
+
class Meta:
|
516
|
+
model = Document
|
517
|
+
fields = ["id", "title", "file"]
|
518
|
+
|
519
|
+
# Output will convert `file` field to an absolute URL
|
520
|
+
serializer = DocumentSerializer(instance, context={"request": request})
|
521
|
+
data = serializer.data
|
522
|
+
```
|
523
|
+
|
524
|
+
#### Notes
|
525
|
+
|
526
|
+
- Works with both Django model serializers and custom serializers.
|
527
|
+
- Relative file paths are automatically converted to absolute URLs.
|
528
|
+
- Can manually specify fields via `file_fields` for non-model serializers.
|
529
|
+
|
463
530
|
## 🛠️ Planned Features
|
464
531
|
|
465
532
|
- Add more utils
|
@@ -1,5 +1,5 @@
|
|
1
1
|
LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
|
2
|
-
README.md,sha256=
|
2
|
+
README.md,sha256=OoZENrGn6D6SPnRlXO1CI_rdRaqZfSJunHlE9QOm-zI,12188
|
3
3
|
demo/staticfiles/admin/img/LICENSE,sha256=0RT6_zSIwWwxmzI13EH5AjnT1j2YU3MwM9j3U19cAAQ,1081
|
4
4
|
src/djresttoolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
src/djresttoolkit/admin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -11,7 +11,7 @@ src/djresttoolkit/dbseed/models/_choice_field.py,sha256=T7LAzbyXqlYp2mtCAKL8E1Da
|
|
11
11
|
src/djresttoolkit/dbseed/models/_gen.py,sha256=qBPQaLvh1rcEam0YmE4JBJqpa-Vv5IFlIIagkEMHDVw,206
|
12
12
|
src/djresttoolkit/dbseed/models/_seed_model.py,sha256=0cmbi0VNKjmJbwhjeCFsvb3iKYjok6TJOk6Y2MF_3N4,2443
|
13
13
|
src/djresttoolkit/envconfig/__init__.py,sha256=PcLaPaVfQmz3-4m6SwoOQF2W4U7F0agBGJ4Qjqbcyfw,74
|
14
|
-
src/djresttoolkit/envconfig/_env_settings.py,sha256=
|
14
|
+
src/djresttoolkit/envconfig/_env_settings.py,sha256=X-pgHqNtUNQLBHiGpbYB4ViCuY14n6aff5HZqYL4Tlc,3236
|
15
15
|
src/djresttoolkit/mail/__init__.py,sha256=tB9SdMlhfWQ640q4aobZ0H1c7fTWalpDL2I-onkr2VI,268
|
16
16
|
src/djresttoolkit/mail/_email_sender.py,sha256=bPMqgD5HibJcOZgO6xxHOhdK9HEhnGNC6BoMPpo-h7k,3096
|
17
17
|
src/djresttoolkit/mail/_models.py,sha256=of5KsLGvsN2OWgDYgdtLEijulg817TXgsLKuUdsnDQc,1447
|
@@ -26,13 +26,17 @@ src/djresttoolkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
26
26
|
src/djresttoolkit/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
27
|
src/djresttoolkit/renderers/__init__.py,sha256=kmFMPRiMfD8CuJTN1_-6Z_Hqil3x8GBM0IN1roZESm0,107
|
28
28
|
src/djresttoolkit/renderers/_throttle_info_json_renderer.py,sha256=aP2cN4cB_Imcpy732zsPBQrMQqcKEs5R3dld5Y_4AMU,1089
|
29
|
+
src/djresttoolkit/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
+
src/djresttoolkit/serializers/_serializer_create_mixin.py,sha256=KprWF_aeEgRygcWG--xtVfnWsOowKqjmdF0aHmrNlzE,3253
|
31
|
+
src/djresttoolkit/serializers/mixins/__init__.py,sha256=fCDNLYLx7Y_19mp8Ya_0akCAViSU3a3LhqgS_sFDMsA,154
|
32
|
+
src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py,sha256=5ewael0_RsJZ9b36IfXacxjb-Vx1eQ9Dk6dWuj5D_dc,3261
|
29
33
|
src/djresttoolkit/throttling/__init__.py,sha256=01sjMymjx8XjqnAw3bEBLc-JtfhCDrp5dGxSNXMvPpU,84
|
30
34
|
src/djresttoolkit/throttling/_throttle_inspector.py,sha256=Kss6ZxKy-EXq9UGaGprGDhpSuJ5992bmEYZSWmUVBHo,6480
|
31
35
|
src/djresttoolkit/views/__init__.py,sha256=XrxBrs6sH4HmUzp41omcmy_y94pSaXAVn01ttQ022-4,76
|
32
36
|
src/djresttoolkit/views/_exceptions/__init__.py,sha256=DrCUxuPNyBR4WhzNutn5HDxLa--q51ykIxSG7_bFsOI,83
|
33
37
|
src/djresttoolkit/views/_exceptions/_exception_handler.py,sha256=_o7If47bzWLl57LeSXSWsIDsJGo2RIpwYAwNQ-hsHVY,2839
|
34
|
-
djresttoolkit-0.
|
35
|
-
djresttoolkit-0.
|
36
|
-
djresttoolkit-0.
|
37
|
-
djresttoolkit-0.
|
38
|
-
djresttoolkit-0.
|
38
|
+
djresttoolkit-0.9.0.dist-info/METADATA,sha256=8Kb2jWYW9ZTev9M6ciwtPk5A0z0lFJVrDhXVZkoIis8,15197
|
39
|
+
djresttoolkit-0.9.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
40
|
+
djresttoolkit-0.9.0.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
|
41
|
+
djresttoolkit-0.9.0.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
|
42
|
+
djresttoolkit-0.9.0.dist-info/RECORD,,
|
@@ -6,7 +6,7 @@ import yaml
|
|
6
6
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
7
7
|
|
8
8
|
|
9
|
-
class EnvBaseSettings(BaseSettings):
|
9
|
+
class EnvBaseSettings[T: "EnvBaseSettings"](BaseSettings):
|
10
10
|
""" "
|
11
11
|
EnvBaseSettings is a base settings class for managing application configuration
|
12
12
|
using both YAML files and environment variables.
|
@@ -60,12 +60,12 @@ class EnvBaseSettings(BaseSettings):
|
|
60
60
|
|
61
61
|
@classmethod
|
62
62
|
def load(
|
63
|
-
cls,
|
63
|
+
cls: type[T],
|
64
64
|
*,
|
65
65
|
env_file: str | None = None,
|
66
66
|
ymal_file: str | None = None,
|
67
67
|
warning: bool = True,
|
68
|
-
) ->
|
68
|
+
) -> T:
|
69
69
|
"""Load from YAML first, then override with .env."""
|
70
70
|
if env_file:
|
71
71
|
cls.env_file = env_file
|
File without changes
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Any, cast
|
3
|
+
|
4
|
+
from django.core.exceptions import FieldDoesNotExist
|
5
|
+
from django.db.models import Model
|
6
|
+
from rest_framework.serializers import Field as SerializerField
|
7
|
+
from django.db.models import Field as ModelField
|
8
|
+
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
class RecordsCreationMixin:
|
14
|
+
"""
|
15
|
+
A mixin for DRF serializers that supports:
|
16
|
+
- Single instance creation with extra context fields
|
17
|
+
- Bulk creation from a list of validated_data dicts
|
18
|
+
- Updating field error messages with model-specific messages
|
19
|
+
|
20
|
+
Notes:
|
21
|
+
- bulk_create() does not trigger model signals or .save()
|
22
|
+
- Meta.model must be defined
|
23
|
+
"""
|
24
|
+
|
25
|
+
def create(
|
26
|
+
self, validated_data: dict[str, Any] | list[dict[str, Any]]
|
27
|
+
) -> Model | list[Model]:
|
28
|
+
logger.debug("Starting creation", extra={"validated_data": validated_data})
|
29
|
+
|
30
|
+
model: type[Model] | None = getattr(getattr(self, "Meta", None), "model", None)
|
31
|
+
if model is None:
|
32
|
+
logger.error("Meta.model not defined.")
|
33
|
+
raise AttributeError(f"{self.__class__.__name__} missing Meta.model.")
|
34
|
+
|
35
|
+
# Bulk creation
|
36
|
+
if isinstance(validated_data, list):
|
37
|
+
instances = [model(**item) for item in validated_data]
|
38
|
+
if instances:
|
39
|
+
logger.info(
|
40
|
+
"Bulk creating instances",
|
41
|
+
extra={"count": len(instances), "model": model.__name__},
|
42
|
+
)
|
43
|
+
return model.objects.bulk_create(instances)
|
44
|
+
logger.info("No instances to create.")
|
45
|
+
return []
|
46
|
+
|
47
|
+
# Single instance creation
|
48
|
+
if not hasattr(super(), "create"):
|
49
|
+
raise NotImplementedError(
|
50
|
+
f"{self.__class__.__name__} must be used with a DRF serializer "
|
51
|
+
"that implements create()."
|
52
|
+
)
|
53
|
+
|
54
|
+
logger.info("Creating a single instance", extra={"model": model.__name__})
|
55
|
+
return super().create({**validated_data}) # type: ignore[misc]
|
56
|
+
|
57
|
+
def get_fields(self) -> dict[str, SerializerField[Any, Any, Any, Any]]:
|
58
|
+
# DRF serializer fields
|
59
|
+
fields = cast(
|
60
|
+
dict[str, SerializerField[Any, Any, Any, Any]],
|
61
|
+
super().get_fields(), # type: ignore
|
62
|
+
)
|
63
|
+
|
64
|
+
meta = getattr(self, "Meta", None)
|
65
|
+
model: type[Model] | None = getattr(meta, "model", None)
|
66
|
+
|
67
|
+
if model is None:
|
68
|
+
raise ValueError(f"{self.__class__.__name__}.Meta.model must be defined.")
|
69
|
+
|
70
|
+
logger.debug("Setting up serializer fields", extra={"model": model.__name__})
|
71
|
+
|
72
|
+
for field_name, serializer_field in fields.items():
|
73
|
+
try:
|
74
|
+
# Django model field
|
75
|
+
model_field = cast(
|
76
|
+
ModelField[Any, Any],
|
77
|
+
model._meta.get_field(field_name), # type: ignore
|
78
|
+
)
|
79
|
+
if hasattr(model_field, "error_messages"):
|
80
|
+
serializer_field.error_messages.update(model_field.error_messages)
|
81
|
+
except FieldDoesNotExist:
|
82
|
+
logger.warning(
|
83
|
+
"Skipping serializer field not present on model",
|
84
|
+
extra={"field_name": field_name, "model": model.__name__},
|
85
|
+
)
|
86
|
+
|
87
|
+
return fields
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Any, cast
|
3
|
+
from urllib.parse import urlparse
|
4
|
+
|
5
|
+
from django.conf import settings
|
6
|
+
from django.db import models
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class MissingRequestContext(Exception):
|
12
|
+
"""Custom exception for missing request in serializer context."""
|
13
|
+
|
14
|
+
...
|
15
|
+
|
16
|
+
|
17
|
+
class AbsoluteUrlFileMixin:
|
18
|
+
"""
|
19
|
+
A mixin that updates FileField and ImageField URLs in serializer output
|
20
|
+
to be absolute URLs, compatible with cloud storage backends.
|
21
|
+
"""
|
22
|
+
|
23
|
+
# manually specify file fields for non-model serializers
|
24
|
+
file_fields: list[str] | None = None
|
25
|
+
|
26
|
+
def to_representation(self, instance: Any) -> dict[str, Any]:
|
27
|
+
"""Extend serializer representation to enhance file field URLs."""
|
28
|
+
representation = cast(dict[str, Any], super().to_representation(instance)) # type: ignore[misc]
|
29
|
+
request = self.context.get("request") # type: ignore
|
30
|
+
return self.enhance_file_fields(instance, representation, request)
|
31
|
+
|
32
|
+
def enhance_file_fields(
|
33
|
+
self,
|
34
|
+
instance: Any,
|
35
|
+
representation: dict[str, Any],
|
36
|
+
request: Any,
|
37
|
+
) -> dict[str, Any]:
|
38
|
+
if request is None:
|
39
|
+
logger.warning("Request not found in serializer context.")
|
40
|
+
if settings.DEBUG:
|
41
|
+
raise MissingRequestContext("Request not found in serializer context.")
|
42
|
+
return representation
|
43
|
+
|
44
|
+
# Collect only file-related model fields if available
|
45
|
+
model_fields = (
|
46
|
+
{
|
47
|
+
field.name: field
|
48
|
+
for field in instance._meta.get_fields()
|
49
|
+
if isinstance(field, (models.FileField, models.ImageField))
|
50
|
+
}
|
51
|
+
if hasattr(instance, "_meta")
|
52
|
+
else {}
|
53
|
+
)
|
54
|
+
|
55
|
+
manual_fields = getattr(self, "file_fields", []) or []
|
56
|
+
|
57
|
+
for field_name, field_value in representation.items():
|
58
|
+
model_field = model_fields.get(field_name)
|
59
|
+
|
60
|
+
is_file_field = model_field or (field_name in manual_fields)
|
61
|
+
if not is_file_field:
|
62
|
+
continue
|
63
|
+
|
64
|
+
try:
|
65
|
+
# Get file URL from instance or raw serializer value
|
66
|
+
if model_field:
|
67
|
+
file_instance = getattr(instance, field_name, None)
|
68
|
+
file_url = (
|
69
|
+
getattr(file_instance, "url", None) if file_instance else None
|
70
|
+
)
|
71
|
+
else:
|
72
|
+
file_url = field_value
|
73
|
+
|
74
|
+
if not file_url:
|
75
|
+
logger.info("No file found for field: %s", field_name)
|
76
|
+
representation[field_name] = None
|
77
|
+
continue
|
78
|
+
|
79
|
+
# Only build absolute URL if it's relative
|
80
|
+
parsed_url = urlparse(str(file_url))
|
81
|
+
if not parsed_url.netloc: # relative path
|
82
|
+
file_url = request.build_absolute_uri(file_url)
|
83
|
+
|
84
|
+
representation[field_name] = file_url
|
85
|
+
logger.debug("Enhanced URL for %s: %s", field_name, file_url)
|
86
|
+
|
87
|
+
except Exception as error:
|
88
|
+
logger.error(
|
89
|
+
"Unexpected error processing file field %s: %s",
|
90
|
+
field_name,
|
91
|
+
error,
|
92
|
+
)
|
93
|
+
|
94
|
+
return representation
|
File without changes
|
File without changes
|
File without changes
|