python5.jpg
PyCon [5] 2009 - Caxias do Sul

Ae, faz horas que não escrevo no meu blog, e já andava com saudade dele. A minha auxência é facilmente explicada pois atualmente meus dias não são mais contados em horas, mas sim pelo número de coisas que tenho que fazer por dia. Atualmente, estou me empenhando para concluir a tradução da documentação do Django 1.0 para Português do Brasil, fora a faculdade, que eu achei q seria barbada no início, mas esta exigindo muito trabalho fora de sala de aula. Junto tudo isso estou com 4 projetos em fase de acabamentos e detalhes, tudo contribuindo para aumentar a falta de tempo para escrever, e por em andamento várias idéias que tenho anotadas.

django.jpg

Ae gurizada,

é só começar a utilizar o Django de verdade, e os problemas do mundo real emergem como bolinhas de poliestireno (Isopor) dentro de uma piscina. Mas vamos lá, hoje eu venho mostrar uma forma de corrigir um problema referente a upload de arquivos utilizando os campos models.FileField, models.ImageField, e derivados na Locaweb, muita gente teve esse problema e vamos ver como dar um geito.

O problema

O problema é bem interessante, eu não sei por quê, mas o serviço de hospedagem mais atual da Locaweb utiliza máquinas Storage, até agora eu não sei o que isso significa, mas o fato é que essas máquinas não permitem que se faça lock em arquivos, este é um recurso que possibilita travar um arquivo, para que os outros não mexam, principalmente na hora do upload, que o Django faz utilizando tanto MemoryFileUpload quando TemporaryFileUpload, o último é que possibilita upload gigantes.

Sendo mais específico, quando o upload excede o valor suportado por MemoryFileUpload ele é gerenciado pelo TemporaryFileUpload, que por se tratar de um upload feito em partes, precisa travar o arquivo para que nada altere ele durante o processo.

Processo pelo Django

O Django executa o processo de lock da seguinte forma:

1. Primeiro ele tenta incluir as funções de lock do Windows;

try:
    import win32con
    import win32file
    import pywintypes
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
    LOCK_SH = 0
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
    __overlapped = pywintypes.OVERLAPPED()
    system_type = 'nt'
except (ImportError, AttributeError):
    pass

2. Depois disto o Django tentará carregar as bibliotecas do Unix (MacOSX, Linux, etc):

try:
    import fcntl
    LOCK_EX = fcntl.LOCK_EX
    LOCK_SH = fcntl.LOCK_SH
    LOCK_NB = fcntl.LOCK_NB
    system_type = 'posix'
except (ImportError, AttributeError):
    pass

Prestando um pouco de atenção você nota que isso funciona perfeitamente, porém, não verifica quanto a possibilidade de não ter suporte ao lock de arquivos do ambiente.

Solucionando o problema

O erro ocorre mais precisamente no módulo django.core.files.storate.FileSystemStorage no arquivo django/core/files/storage.py, no seguinte trecho:

try:
    locks.lock(fd, locks.LOCK_EX)
    for chunk in content.chunks():
        os.write(fd, chunk)
finally:
    locks.unlock(fd)
    os.close(fd)

 

Como é possível notar, a exceção IOError é lançada devido o fato servidor não suportar o lock. E o Django não faz o tratamento da exceção pois ele acredita que já foi feito o arquivo django/core/files/locks.py. Para solucionar o problema então, eu utilizei o seguinte workaround, e enviei minha versão alterada do Django para a minha hospedagem da Locaweb.

try:
    _locked = False
    locks.lock(fd, locks.LOCK_EX)
    _locked = True
    for chunk in content.chunks():
        os.write(fd, chunk)
except IOError:
    for chunk in content.chunks():
        os.write(fd, chunk)
finally:
    if _locked:
        locks.unlock(fd)
    os.close(fd)

Confeço que essa não é a solução mais elegante, portanto nem reportei esse problema para a equipe do Django, quando encontrar uma forma de solucionar isso de fato, sem ser chamando a função flock eu comento aqui no blog. Mas com esta solução você já evitará um processo chato de migração de dados e arquivos para outro servidor, e conseguirá terminar seu projeto numa boa.

Para os que acham que isso é uma gambiarra f@#$a, eu digo o seguinte, olhem o código original do Django:

if system_type == 'nt':
    def lock(file, flags):
        hfile = win32file._get_osfhandle(fd(file))
        win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)

    def unlock(file):
        hfile = win32file._get_osfhandle(fd(file))
        win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
elif system_type == 'posix':
    def lock(file, flags):
        fcntl.lockf(fd(file), flags)

    def unlock(file):
        fcntl.lockf(fd(file), fcntl.LOCK_UN)
else:
    # File locking is not supported.
    LOCK_EX = LOCK_SH = LOCK_NB = None

    # Dummy functions that don't do anything.
    def lock(file, flags):
        pass

    def unlock(file):
        pass

Reparem no final as funções django.core.files.locks.lock e django.core.files.locks.unlock sendo declaradas como passivas, elas não farão nada, se não houver suporte a lock de arquivos.

Conclusão

Para concluir existe mais um script que torna possível você ter o Django que você precisar na sua conta de usuário da Locaweb. Faça o seguinte no terminal ssh:

$ mkdir -p $HOME/.python/lib
$ echo "export PYTHONPATH=$HOME/.python/lib/" >> $HOME/.bashrc 
$ source $HOME/.bashrc 

Isso criará uma pasta .python/lib no seu home, e adicionará o caminho dela no $PYTHONPATH. Agora lá no seu ~/public_html/seu_projeto/index.wsgi você deve ter o seguinte código para garantir que o Django a ser utilizado é o que você colocou no .python/lib.

import os, sys

sys.path.insert(0,'/home/seu_usuario/.python/lib')
sys.path.append('/home/seu_usuario/wsgi_apps')
sys.path.append('/home/seu_usuario/wsgi_apps/seu_projeto')

os.environ['DJANGO_SETTINGS_MODULE']='seu_projeto.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Era isso, espero que isso ajude a quem estiver tendo dor de cabeça com o erro de upload no Admin do Django.

Falow!!

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <b> <i> <u> <img> <p> <span> <div> <h1> <h1> <h2> <h3> <h4> <h5> <h6> <pre>
  • Lines and paragraphs break automatically.

More information about formatting options

Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated.