[Django] #26058: Custom storage backend's not entirely decoupled from FileField

11 views
Skip to first unread message

Django

unread,
Jan 8, 2016, 8:32:50 AM1/8/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: Bug | Status: new
Component: File | Version: 1.9
uploads/storage | Keywords: custom storage
Severity: Normal | filefield
Triage Stage: Unreviewed | Has patch: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Let me start by saying that I've implemented custom FileFields that can
handle Numpy objects, to provide some convenience methods to access the
typed objects.

Later, when implementing a custom storage backend for Azure Storage, I
encountered some issues when handling filenames. Cloud storage doesn't
behave like local file system storage does. Most of Django's code is
agnostic of this problem, except for a small section of FileField:

https://github.com/django/django/blob/77b8d8cb6d6d6345f479c68c4892291c1492ba7e/django/db/models/fields/files.py#L306-L320

In Azure, blobs are stored in containers, and are optionally stored in
subfolders (known as prefixes in Azure-speak). To be able to use the
upload_to property, I need to be able to pass the full path, e.g.:

container/blob
container/sub/blob
container/sub/sub/blob

Unfortunately, FileField strips the directory part and only passes the
blob name to my custom storage backend's get_valid_name implementation,
where I really need to validate the whole string. I ended up subclassing
FileField:

{{{
class CloudStorageFileField(FileField):
"""
Provides some overrides to enable cloud storage backends
"""

def get_directory_name(self):
return
force_text(datetime.datetime.now().strftime(force_str(self.upload_to)))

def get_filename(self, filename):
return filename

def generate_filename(self, instance, filename):
if callable(self.upload_to):
filename = self.upload_to(instance, filename)
filename = self.storage.get_valid_name(filename)
return filename

return self.get_directory_name() +
self.storage.get_valid_name(filename)
}}}

As you can see, all I did was remove the local file system related logic.
Unfortunately, my custom file fields need to inherit from this class to
work with Azure storage, and when I switch to local storage in a different
environment, they have to inherit from the regular FileField! This is
obviously an undesirable situation, imposed by the otherwise-great tight
coupling to local file system logic in the three methods of FileField.

In order to truly decouple this, FielField would need to be a little bit
more agnostic about the storage backend. This could be done by moving the
generate_filename method to the backend entirely, for example.

I classified this as a bug, as this case shows that you're not "entirely"
able to do anything you want when implementing custom storage backends.

You can see the full implementation of both the backend and the
numpyfilefield here: https://gist.github.com/Korijn/e0bcbdcedb494509973a

Your thoughts?

--
Ticket URL: <https://code.djangoproject.com/ticket/26058>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jan 8, 2016, 8:36:35 AM1/8/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: Bug | Status: new
Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution:
Keywords: custom storage | Triage Stage:
filefield | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Korijn):

* needs_better_patch: => 0
* needs_tests: => 0
* needs_docs: => 0


Old description:

New description:

https://github.com/django/django/blob/77b8d8cb6d6d6345f479c68c4892291c1492ba7e/django/db/models/fields/files.py#L306-L320

container/blob
container/sub/blob
container/sub/sub/blob

def get_directory_name(self):
return
force_text(datetime.datetime.now().strftime(force_str(self.upload_to)))

return self.get_directory_name() +
self.storage.get_valid_name(filename)
}}}

obviously an undesirable situation, imposed by the tight coupling to local


file system logic in the three methods of FileField.

In order to truly decouple this, FileField would need to be a little bit


more agnostic about the storage backend. This could be done by moving the
generate_filename method to the backend entirely, for example.

I classified this as a bug, as this case shows that you're not
''entirely'' able to do anything you want when implementing custom storage
backends.

You can see the implementation of both the custom storage backend and the
NumpyFileField here: https://gist.github.com/Korijn/e0bcbdcedb494509973a

Your thoughts?

--

--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:1>

Django

unread,
Jan 8, 2016, 9:31:40 AM1/8/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution:
Keywords: custom storage | Triage Stage: Accepted
filefield |

Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by timgraham):

* type: Bug => Cleanup/optimization
* stage: Unreviewed => Accepted


Comment:

The main question is whether or not the changes can be made in a backwards
compatible fashion.

--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:2>

Django

unread,
Jan 8, 2016, 11:04:10 AM1/8/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution:
Keywords: custom storage | Triage Stage: Accepted
filefield |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Korijn):

How's this?


{{{
def generate_filename(self, instance, filename):
# allow for customized filename generation
if hasattr(self.storage, 'generate_filename'):


if callable(self.upload_to):
filename = self.upload_to(instance, filename)

return self.storage.generate_filename(filename)

filename =
force_text(datetime.datetime.now().strftime(force_str(self.upload_to)))
return self.storage.generate_filename(filename)

# If upload_to is a callable, make sure that the path it returns
is
# passed through get_valid_name() of the underlying storage.
if callable(self.upload_to):
directory_name, filename =
os.path.split(self.upload_to(instance, filename))
filename = self.storage.get_valid_name(filename)
return os.path.normpath(os.path.join(directory_name,
filename))

return os.path.join(self.get_directory_name(),
self.get_filename(filename))
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:3>

Django

unread,
Jan 12, 2016, 4:29:38 AM1/12/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution:
Keywords: custom storage | Triage Stage: Accepted
filefield |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Korijn):

Not sure if my edit generated a notification, so, bump.

--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:4>

Django

unread,
Mar 22, 2016, 11:42:16 AM3/22/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution:
Keywords: custom storage | Triage Stage: Accepted
filefield |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by timgraham):

* has_patch: 0 => 1


Comment:

#26382 is a duplicate with a patch proposed:
[https://github.com/django/django/pull/6321 PR]

--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:5>

Django

unread,
Mar 23, 2016, 11:28:46 AM3/23/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution:
Keywords: custom storage | Triage Stage: Accepted
filefield |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by timgraham):

* needs_better_patch: 0 => 1


--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:6>

Django

unread,
Apr 30, 2016, 11:11:42 AM4/30/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution:
Keywords: custom storage | Triage Stage: Accepted
filefield |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by timgraham):

* needs_better_patch: 1 => 0


Comment:

[https://github.com/django/django/pull/6538 Updated PR]

--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:7>

Django

unread,
Apr 30, 2016, 7:28:53 PM4/30/16
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn | Owner: nobody
Type: | Status: closed
Cleanup/optimization |

Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution: fixed

Keywords: custom storage | Triage Stage: Accepted
filefield |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Graham <timograham@…>):

* status: new => closed
* resolution: => fixed


Comment:

In [changeset:"914c72be2abb1c6dd860cb9279beaa66409ae1b2" 914c72b]:
{{{
#!CommitTicketReference repository=""
revision="914c72be2abb1c6dd860cb9279beaa66409ae1b2"
Fixed #26058 -- Delegated os.path bits of FileField's filename generation
to the Storage.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:8>

Django

unread,
Jan 17, 2017, 10:09:53 PM1/17/17
to django-...@googlegroups.com
#26058: Custom storage backend's not entirely decoupled from FileField
-------------------------------------+-------------------------------------
Reporter: Korijn van Golen | Owner: nobody
Type: | Status: closed
Cleanup/optimization |

Component: File | Version: 1.9
uploads/storage |
Severity: Normal | Resolution: fixed
Keywords: custom storage | Triage Stage: Accepted
filefield |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Tim Graham <timograham@…>):

In [changeset:"0dfc5479a8e50215866bbf43604bed8416a1b504" 0dfc5479]:
{{{
#!CommitTicketReference repository=""
revision="0dfc5479a8e50215866bbf43604bed8416a1b504"
Refs #26058 -- Removed deprecated
FileField.get_directory_name()/get_filename().
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/26058#comment:9>

Reply all
Reply to author
Forward
0 new messages