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):
pass2. 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):
passPrestando 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!!





![PythonBrasil[5]](http://www.pythonbrasil.org.br/2009/saiba-mais/apoio-divulgue/pythonbrasil-rectangle.gif)

Post new comment