Skip to content

Commit 67f200f

Browse files
Add custom SecureFileField class in FileField.
1 parent cff0dc9 commit 67f200f

File tree

9 files changed

+47
-71
lines changed

9 files changed

+47
-71
lines changed

api/models.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from django.utils.translation import gettext_lazy as _
2323
from tinymce.models import HTMLField
2424

25+
from main.fields import SecureFileField
26+
2527
from .utils import validate_slug_number # is_user_ifrc,
2628

2729

@@ -984,7 +986,7 @@ def sitrep_document_path(instance, filename):
984986
class SituationReport(models.Model):
985987
created_at = models.DateTimeField(verbose_name=_("created at"), auto_now_add=True)
986988
name = models.CharField(verbose_name=_("name"), max_length=100)
987-
document = models.FileField(verbose_name=_("document"), null=True, blank=True, upload_to=sitrep_document_path)
989+
document = SecureFileField(verbose_name=_("document"), null=True, blank=True, upload_to=sitrep_document_path)
988990
document_url = models.URLField(verbose_name=_("document url"), blank=True)
989991

990992
event = models.ForeignKey(Event, verbose_name=_("event"), on_delete=models.CASCADE)
@@ -1287,7 +1289,7 @@ class GeneralDocument(models.Model):
12871289
name = models.CharField(verbose_name=_("name"), max_length=100)
12881290
# Don't set `auto_now_add` so we can modify it on save
12891291
created_at = models.DateTimeField(verbose_name=_("created at"), blank=True)
1290-
document = models.FileField(verbose_name=_("document"), null=True, blank=True, upload_to=general_document_path)
1292+
document = SecureFileField(verbose_name=_("document"), null=True, blank=True, upload_to=general_document_path)
12911293
document_url = models.URLField(verbose_name=_("document url"), blank=True)
12921294

12931295
class Meta:

country_plan/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.utils.translation import gettext_lazy as _
66

77
from api.models import Country
8+
from main.fields import SecureFileField
89

910

1011
def file_upload_to(instance, filename):
@@ -63,7 +64,7 @@ def save(self, *args, **kwargs):
6364

6465
class CountryPlan(CountryPlanAbstract):
6566
country = models.OneToOneField(Country, on_delete=models.CASCADE, related_name="country_plan", primary_key=True)
66-
internal_plan_file = models.FileField(
67+
internal_plan_file = SecureFileField(
6768
verbose_name=_("Internal Plan"),
6869
upload_to=pdf_upload_to,
6970
validators=[FileExtensionValidator(["pdf"])],

dref/models.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pdf2image import convert_from_bytes
1313

1414
from api.models import Country, DisasterType, District, FieldReport
15+
from main.fields import SecureFileField
1516

1617

1718
@reversion.register()
@@ -536,7 +537,7 @@ class Status(models.IntegerChoices):
536537
verbose_name=_("budget file"),
537538
related_name="budget_file_dref",
538539
)
539-
budget_file_preview = models.FileField(verbose_name=_("budget file preview"), null=True, blank=True, upload_to="dref/images/")
540+
budget_file_preview = SecureFileField(verbose_name=_("budget file preview"), null=True, blank=True, upload_to="dref/images/")
540541
assessment_report = models.ForeignKey(
541542
"DrefFile",
542543
on_delete=models.SET_NULL,
@@ -648,7 +649,7 @@ def get_for(user):
648649

649650

650651
class DrefFile(models.Model):
651-
file = models.FileField(
652+
file = SecureFileField(
652653
verbose_name=_("file"),
653654
upload_to="dref/images/",
654655
)
@@ -750,7 +751,7 @@ class DrefOperationalUpdate(models.Model):
750751
verbose_name=_("budget file"),
751752
related_name="budget_file_dref_operational_update",
752753
)
753-
budget_file_preview = models.FileField(
754+
budget_file_preview = SecureFileField(
754755
verbose_name=_("budget file preview"), null=True, blank=True, upload_to="dref-op-update/images/"
755756
)
756757
assessment_report = models.ForeignKey(
@@ -1316,7 +1317,7 @@ class DrefFinalReport(models.Model):
13161317
verbose_name=_("financial report"),
13171318
related_name="financial_report_dref_final_report",
13181319
)
1319-
financial_report_preview = models.FileField(
1320+
financial_report_preview = SecureFileField(
13201321
verbose_name=_("financial preview"), null=True, blank=True, upload_to="dref/images/"
13211322
)
13221323
num_assisted = models.IntegerField(verbose_name=_("number of assisted"), blank=True, null=True)

flash_update/migrations/0013_alter_flashgraphicmap_file.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

flash_update/migrations/0014_alter_flashupdate_extracted_file.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

flash_update/models.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from django.utils.translation import gettext_lazy as _
99
from tinymce.models import HTMLField
1010

11-
from main.utils import custom_upload_to
1211
from api.models import (
1312
ActionCategory,
1413
ActionOrg,
@@ -17,16 +16,12 @@
1716
DisasterType,
1817
District,
1918
)
19+
from main.fields import SecureFileField
2020

21-
def flash_map_upload_to(instance, filename):
22-
return custom_upload_to('flash_update/images/')(instance, filename)
23-
24-
def flash_extracted_file_upload_to(instance, filename):
25-
return custom_upload_to('flash_update/pdf/')(instance, filename)
2621

2722
@reversion.register()
2823
class FlashGraphicMap(models.Model):
29-
file = models.FileField(verbose_name=_("file"), upload_to=flash_map_upload_to)
24+
file = SecureFileField(verbose_name=_("file"), upload_to="flash_update/images")
3025
caption = models.CharField(max_length=225, blank=True, null=True)
3126
created_by = models.ForeignKey(
3227
settings.AUTH_USER_MODEL,
@@ -124,7 +119,7 @@ class FlashShareWith(models.TextChoices):
124119
verbose_name=_("share with"),
125120
)
126121
references = models.ManyToManyField(FlashReferences, blank=True, verbose_name=_("references"))
127-
extracted_file = models.FileField(verbose_name=_("extracted file"), upload_to=flash_extracted_file_upload_to, blank=True, null=True)
122+
extracted_file = SecureFileField(verbose_name=_("extracted file"), upload_to="flash_update/pdf/", blank=True, null=True)
128123
extracted_at = models.DateTimeField(verbose_name=_("extracted at"), blank=True, null=True)
129124

130125
class Meta:

main/fields.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import datetime
2+
import posixpath
3+
from uuid import uuid4
4+
5+
from django.core.files.utils import validate_file_name
6+
from django.db.models.fields.files import FileField
7+
8+
9+
class SecureFileField(FileField):
10+
def generate_filename(self, instance, filename):
11+
"""
12+
Apply (if callable) or prepend (if a string) upload_to to the filename,
13+
then delegate further processing of the name to the storage backend.
14+
Until the storage layer, all file paths are expected to be Unix style
15+
(with forward slashes).
16+
Add random uuid in the file name.
17+
"""
18+
extension = filename.split(".")[-1]
19+
old_file_name = filename.split(".")[0]
20+
# Create a unique filename using uuid4
21+
filename = f"{old_file_name}-{uuid4().hex}.{extension}"
22+
23+
if callable(self.upload_to):
24+
filename = self.upload_to(instance, filename)
25+
else:
26+
dirname = datetime.datetime.now().strftime(str(self.upload_to))
27+
filename = posixpath.join(dirname, filename)
28+
filename = validate_file_name(filename, allow_relative_path=True)
29+
30+
return self.storage.generate_filename(filename)

main/utils.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import os
21
import datetime
32
import json
43
import typing
5-
from uuid import uuid4
64
from collections import defaultdict
75
from tempfile import NamedTemporaryFile, _TemporaryFileWrapper
86

@@ -17,20 +15,6 @@
1715
from reversion.revisions import _get_options
1816

1917

20-
def custom_upload_to(directory):
21-
"""
22-
Rename file name with adding uuid
23-
"""
24-
def upload_to(instance, filename):
25-
# Get the file extension
26-
extension = filename.split('.')[-1]
27-
old_file_name = filename.split('.')[0]
28-
# Create a unique filename using uuid4
29-
new_filename = f"{old_file_name}-{uuid4().hex}.{extension}"
30-
# Return the new file path
31-
return os.path.join(directory, new_filename)
32-
return upload_to
33-
3418
def is_tableau(request):
3519
"""Checking the request for the 'tableau' parameter
3620
(used mostly for switching to the *TableauSerializers)

per/models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from api.models import Appeal, Country
88
from deployments.models import SectorTag
9+
from main.fields import SecureFileField
910

1011

1112
class ProcessPhase(models.IntegerChoices):
@@ -422,7 +423,7 @@ def save(self, *args, **kwargs):
422423

423424

424425
class PerFile(models.Model):
425-
file = models.FileField(
426+
file = SecureFileField(
426427
verbose_name=_("file"),
427428
upload_to="per/images/",
428429
)
@@ -738,7 +739,7 @@ def save(self, *args, **kwargs):
738739

739740

740741
class PerDocumentUpload(models.Model):
741-
file = models.FileField(
742+
file = SecureFileField(
742743
verbose_name=_("file"),
743744
upload_to="per/documents/",
744745
)

0 commit comments

Comments
 (0)