django: регистрация разархивированных файлов на локальном диске - PullRequest
2 голосов
/ 17 июня 2011

[Я заранее прошу прощения за длину этого вопроса.]

Я использую Django 1.2.3-3 + squeeze1 в Debian squeeze.

Я пишу приложение, которое загружает zip-файлы на диск в временное местоположение, распаковывает их, а затем сохраняет результаты в постоянное местоположение. Распакованные файлы регистрируются в базе данных как класс FileUpload после распаковки. Загруженные заархивированные файлы также соответствуют классу, но я проигнорирую это для целей этого вопроса. FileUpload выглядит так.

class FileUpload(models.Model):
    folder = models.ForeignKey(FolderUpload, null=True, blank=True, related_name='parentfolder')
    upload_date = models.DateTimeField(default=datetime.now(), blank=True, editable=False)
    upload = models.FileField(upload_to=file_upload_path)
    name = models.CharField(max_length=100)
    description = models.CharField(blank=True, max_length=200)

    def save(self):
        if not self.id:
            if self.folder == None:
                pass
            else:
                self.path = self.folder.path
        super(FileUpload, self).save()

Я также использую форму, определяемую

from django.forms import ChoiceField, Form, ModelForm

class FileUploadForm(ModelForm):
    class Meta:
        model = FileUpload

Функция, которая принимает распакованные файлы на диск. регистрирует их с базой данных, и перемещает их в нужное место называется addFile. Я ранее использовал это:

def addFile(name, filename, description, content, folder_id=None):
    #f = open(filename, 'r')
    #content = f.read()
    from forms import FileUploadForm
    from django.core.files.uploadedfile import SimpleUploadedFile
    if folder_id == None:
        data = {'name':name, 'description':description}
    else:
        data = {'name':name, 'description':description, 'folder':str(folder_id)}
    file_data = {'upload': SimpleUploadedFile(filename, content)}
    ff = FileUploadForm(data, file_data)
    try:
        zf = ff.save(commit=False)
        zf.save()
    except:
        raise RuntimeError, "Form error is %s."%(ff.errors)
    return zf

Это сработало, но проблема заключалась в том, что он выгружал весь файл в объем памяти. С большими файлами, особенно с учетом того, что Python не известен это экономия памяти, это потребляет огромное количество памяти. Так что я переключился на это:

from django.core.files.uploadedfile import UploadedFile

class UnzippedFile(UploadedFile):

    def __init__(self, file, filepath, content_type='text/plain', charset=None):
        import os
        self.filepath = filepath
        self.name = os.path.basename(filepath)
        self.size = os.path.getsize(filepath)
        self.content_type = content_type
        self.charset = charset
        super(UnzippedFile, self).__init__(file, self.name, content_type, self.size, charset)

    def temporary_file_path(self):
        """
        Returns the full path of this file.
        """
        return self.filepath

def addFile(filepath, description, file, folder_id=None):
    import os, sys
    from forms import FileUploadForm
    from django.core.files.uploadedfile import UploadedFile
    name = os.path.basename(filepath)
    if folder_id == None:
        data = {'name':name, 'description':description}
    else:
        data = {'name':name, 'description':description, 'folder':str(folder_id)}
    file_data = {'upload': UnzippedFile(file, filepath)}
    ff = FileUploadForm(data, file_data)
    try:
        zf = ff.save(commit=False)
        zf.save()
    except:
        raise
    return zf

Я был вынужден подкласс UploadedFile, так как ни один из производных классы, которые уже были там (в django/core/files/uploadedfile.py) казалось, делал то, что хотел.

Имеется функция temporary_file_path, поскольку Django File Uploads загружает документы скажем

UploadedFile.temporary_file_path ()

Этот метод доступен только для файлов, загруженных на диск; это возвращает полный путь к временно загруженному файлу.

Кажется, класс FileSystemStorage ищет этот атрибут в _save функция, как описано позже.

Если n - относительный путь к файлу в zip-архиве, то использование

name = os.path.normpath(os.path.basename(n)) # name of file
pathname = os.path.join(dirname, n) # full file path
description = "from zip file '" + zipfilename + "'" # `zipfilename` is the name of the zip file
fname = open(pathname) # file handle
f = addFile(pathname, description, fname)

Это работает, но я проследил код и обнаружил, что код используя потоковую передачу, когда явно оптимальная вещь, которую нужно сделать в этом случае будет просто скопировать файл из временного расположения в постоянное местоположение. Код в вопросе находится в django/core/files/storage.py, в функции _save FileSystemStorage класс. В _save, name - относительный путь пункт назначения, а content является File объектом.

def _save(self, name, content):
    full_path = self.path(name)
     directory = os.path.dirname(full_path)
     [...]
    while True:
            try:
                # This file has a file path that we can move.
                if hasattr(content, 'temporary_file_path'):
                    file_move_safe(content.temporary_file_path(), full_path)
                    content.close()

                # This is a normal uploadedfile that we can stream.
                else:
                    # This fun binary flag incantation makes os.open throw an
                    # OSError if the file already exists before we open it.
                    fd = os.open(full_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0))
                    try:
                        locks.lock(fd, locks.LOCK_EX)
                        for chunk in content.chunks():
                            os.write(fd, chunk)
                    finally:
                        locks.unlock(fd)
                        os.close(fd)
            except OSError, e:
                if e.errno == errno.EEXIST:
                    # Ooops, the file exists. We need a new file name.
                    name = self.get_available_name(name)
                    full_path = self.path(name)
                else:
                    raise
            else:
                # OK, the file save worked. Break out of the loop.
                break

Функция _save ищет атрибут temporary_file_path. Я считаю, что этот код предназначен для запуска с помощью функции temporary_file_path, упомянутой ранее в django/core/files/uploadedfile.py. Тем не менее, класс, который фактически переданный (соответствующий аргументу content) равен <class 'django.db.models.fields.files.FieldFile'>, и вот что Атрибут dict (content.__dict__) для этого объекта выглядит так:

{'_committed': False, 'name': u'foo', 'instance': <FileUpload: foo>,
'_file': <UnzippedFile: foo (text/plain)>, 'storage':<django.core.files.storage.DefaultStorage object at 0x9a70ccc>,
'field': <django.db.models.fields.files.FileField object at0x9ce9b4c>, 'mode': None}

temporary_file_path прикреплен к Класс UnzippedFile, который находится внутри элемента данных _file. Так content._file имеет атрибут temporary_file_path, а не content сам по себе.

Так выглядит обычная загрузка файлов. Как видите, это похоже.

[Fri Jun 17 08:05:33 2011] [error] type of content is <class 'django.db.models.fields.files.FieldFile'>
[Fri Jun 17 08:05:33 2011] [error] {'_committed': False, 'name': u'behavior.py',
                                    'instance': <FileUpload: b>, '_file': <TemporaryUploadedFile: behavior.py (text/x-python)>,
                                    'storage': <django.core.files.storage.DefaultStorage object at 0xb8d7fd8c>,
                                    'field': <django.db.models.fields.files.FileField object at 0xb8eb584c>, 'mode': None}

Мне трудно подробно описать, как код получается из FileUploadForm save к Storage объекту. Форма Джанго код, в частности, довольно неясен.

Во всяком случае, мой вопрос, после всех этих настроек, как / когда первый опция ниже, с file_move_safe предполагается активировать? я видя несоответствие здесь. Это ошибка? Кто-нибудь может уточнить?

1 Ответ

0 голосов
/ 18 июня 2011
if hasattr(content, 'temporary_file_path') 

Приведенное выше никогда не будет равно true с этим условным условием, поскольку вы заявляете, что у контента нет идентификатора временного_файла_путей.Однако, так как content._file делает это, вы можете использовать следующее, чтобы получить функциональность, которую вы ищете

if hasattr(content, '_file'):
   if hasattr(content._file,'temporary_file_path'):
...