# -*- coding: utf-8 -*-
'''
Created on Sep 1, 2015
@author: ymh
'''
from django.apps import AppConfig
from django.core.files.base import File
from django.db.models.fields.files import FieldFile
from django.db.models.fields.files import FileDescriptor
import six
def ldt_get(self, instance=None, owner=None):
if instance is None:
raise AttributeError(
"The '%s' attribute can only be accessed from %s instances."
% (self.field.name, owner.__name__))
# This is slightly complicated, so worth an explanation.
# instance.file`needs to ultimately return some instance of `File`,
# probably a subclass. Additionally, this returned object needs to have
# the FieldFile API so that users can easily do things like
# instance.file.path and have that delegated to the file storage engine.
# Easy enough if we're strict about assignment in __set__, but if you
# peek below you can see that we're not. So depending on the current
# value of the field we have to dynamically construct some sort of
# "thing" to return.
# The instance dict contains whatever was originally assigned
# in __set__.
file = instance.__dict__[self.field.name] # @ReservedAssignment
# If this value is callable (certainly because the field was defined
# with a callable default), we execute the function and assign the
# result to the value
if six.callable(file):
file = file() # @ReservedAssignment
if isinstance(file, FieldFile) and not hasattr(file, 'field'):
instance.__dict__[self.field.name] = file
# If this value is a string (instance.file = "path/to/file") or None
# then we simply wrap it with the appropriate attribute class according
# to the file field. [This is FieldFile for FileFields and
# ImageFieldFile for ImageFields; it's also conceivable that user
# subclasses might also want to subclass the attribute class]. This
# object understands how to convert a path to a file, and also how to
# handle None.
if isinstance(file, six.string_types) or file is None:
attr = self.field.attr_class(instance, self.field, file)
instance.__dict__[self.field.name] = attr
# Other types of files may be assigned as well, but they need to have
# the FieldFile interface added to them. Thus, we wrap any other type of
# File inside a FieldFile (well, the field's attr_class, which is
# usually FieldFile).
elif isinstance(file, File) and not isinstance(file, FieldFile):
file_copy = self.field.attr_class(instance, self.field, file.name)
file_copy.file = file
file_copy._committed = False
instance.__dict__[self.field.name] = file_copy
# Finally, because of the (some would say boneheaded) way pickle works,
# the underlying FieldFile might not actually itself have an associated
# file. So we need to reset the details of the FieldFile in those cases.
elif isinstance(file, FieldFile) and not hasattr(file, 'field'):
file.instance = instance
file.field = self.field
file.storage = self.field.storage
# That was fun, wasn't it?
return instance.__dict__[self.field.name]
class LdtAppConfig(AppConfig):
'''
App config to monkey patch django.db.models.fields.files.FileDescriptor.__get__ (cf django bug #24823 https://code.djangoproject.com/ticket/24823)
'''
name = 'ldt'
verbose_name = 'LDT Platform'
def ready(self):
FileDescriptor.__get__ = ldt_get