src/ldt/ldt/apps.py
author ymh <ymh.work@gmail.com>
Wed, 28 Oct 2015 15:53:59 +0100
changeset 1465 74b0b90517ed
parent 1420 0637048db5d4
permissions -rw-r--r--
affect permissions on merged project

# -*- 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