Ante todo agradecer la respuesta, y además puntualizar, que es muy sencillo hacer pruebas y pruebas en tu proyecto local donde puedes hacer mil y una modificaciones como root sin prácticamente restricciones, pero a la hora de hacerlo en un servicio más critico y además compartido, como este, se tiene que volver enormemente más complejo, el famoso "works on my machine...", como ya digo, mi experiencia con apache es poca, y me encuentro cometiendo errores de novato continuamente, así que de antemano pido disculpas y paciencia con mis errores.
Por otro lado voy a unir los 2 posts que tengo ahora mismo en marcha y que están bastante relacionados, este
http://quijost.com/foro/asistencia-al-cliente/permisos-de-escritura-del-proceso-python/ continuo ambos por aquí ya que me parece más apropiado por la temática, y como la cosa es bastante larga, aprovecho para hace un pequeño tuto, con la info que he recopilado en pruebas.
Ahora ya, sigo...
Tengo 2 errores ahora mismo con las pruebas utilizando virtualenv...
1-> Acceso a los staticfiles
Esto parece provenir de una mala configuración en mi .htaccess:
SetHandler wsgi-script
RewriteEngine on
RewriteCond %{REQUEST_URI} !(django.wsgi)
RewriteRule ^(.*)$ django.wsgi/$1 [L]
Ya que cuando cambio la configuración en setting.py y hago recoger los staticfiles (imagenes/js/css) y la hago apuntar a un directorio fuere del directorio base donde se aloja el proyecto, no hay problema en recogerlas, sin embargo cuando está detrás parece que el sólo responde el script python.
../public_html/proyectodjango/static/... -> 404
../public_html/proyecto_django/
../public_htm l/static/ -> Bien
En el settings.py debes definir el STATIC_URL a donde desees y que este directorio sea existente, por ejemplo:
STATIC_URL = '/static/'
No olvides definir tambien el STATICFILES_DIRS:
STATICFILES_DIRS = (
os.path.join(os.path.dirname(__file__), 'static').replace('\\','/'),
)
A lo que viene mi segundo problema:
2 -> Escritura de ficheros:
Cuando la estructura de staticfiles apunta al ..public_html/static/
Si intento subir un archivo, a través de la aplicación, pej: imageField me da "IOError: [Errno 13] Permission denied:", no puede escribir, lo que parece ser que el interprete de python se ejecuta con los permisos del "nobody" del apache del servidor, con lo que no tengo permisos de escritura...ni ahí, ni en el directorio del proyecto...
Al utilizar virtualenv, el usuario es nobody, por lo que sólo tienes permisos de lectura (y menos en un compartido podrían otorgarse permisos de escritura como nobody,
ya que sino cualquier usuario podría escribir datos en el directorio de usuario de otro usuario que no fuera el mismo).
Con tu usuario no tendrías esos problemas, pero esto es una limitación de virtualenv.
En un compartido, para ejecutar WGSI debemos hacerlo con nobody. De otra forma, seria asignar un proceso WGSI por cada usuario del servidor compartido y
mantenerlo en memoria, lo que es elevado y costoso. Si tienes mayores necesidades, tal vez te conviene un VPS (http://quijost.com/vps/)
Por otro lado, acabamos de instalar Paste 1.7.5.1 en el server1 por defecto.
Para hacer las pruebas, pongo en marcha 2 subdominios,
http://pythonvirtualenv.slothy.quijost.com/ &
http://pythonsistema.slothy.quijost.com/, como su nombre indica, uno hará uso de las LIBRERIAS de un virtualenv y el otro de las de la instalación del sistema.
La estructura de directorios será la siguiente:
/home/usuario/public_html/pythonvirtualenv
--> .htaccess
--> app.wsgi
------------------------------------------------------
/home/usuario/public_html/pythonsistema
--> .htaccess
--> app.wsgi
Además en cada uno de los directorios creo otro subdirectorio imagenes donde meto una imagen
El contenido del .htaccess es para pythonvirtualenv, el siguiente:
## PYTHONVIRTUALENV
Options +Indexes
SetHandler wsgi-script
RewriteEngine On
RewriteRule ^(imagenes/.+)$ - [L]
RewriteCond %{REQUEST_URI} !(app.wsgi)
RewriteRule ^(.*)$ app.wsgi/$1 [L]
Para pythonsistema:
## PYTHONSISTEMA
SetHandler wsgi-script
RewriteEngine On
RewriteCond %{REQUEST_URI} !(app.wsgi)
RewriteRule ^(.*)$ app.wsgi/$1 [L]
Ahora, sobre este .htaccess y las directivas del mod_rewrite que tiene, tengo bastantes dudas que sea lo más correcto para servir ficheros que estén detrás del .wsgi, aquí aún estoy leyendo documentación al respecto.
Teóricamente una petición a
http://pythonsistema.slothy.quijost.com/imagenes/Seta_de_la_muerte.png, será interpretada siempre con el app.wsgi, y así es, una petición ahora a
http://pythonvirtualenv.slothy.quijost.com/imagenes/Seta_de_la_muerte.png teóricamente debería no ser interpretada por el mod_wsgi y ser servida por apache, de ahí mis dudas respecto a servir los staticfiles, ya que si se meten "detrás" del .wsgi que lanzaría el django, (no es el caso actual, ya que ahora servimos una app.wsgi con código de pruebas, que más adelante está publicado), cuando django está
modo debug = True si está todo bien configurado, (urls.py, abajo muestro el código) los staticfiles,
SI se servirán, no siendo así, cuando se supone que la aplicación está lista para "producción" y se pone en
modo debug = False, entonces esa url es capturada por el .wsgi y servirá un 500 o un 404. Este es uno de mis errores/dudas, y creo que la solución está en unas reglas del .htaccess bien definidas... Aunque también se puede solucionar sirviendo los "statics" desde otro directorio fuera del path de la aplicación o anterior. Por ejemplo: /home/usuario/public_html/statics/
OJO QUE FALTAN LOS IMPORTS...
urls.py CON staticfiles
if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns()
urls.py CON appmedia
if settings.DEBUG:
urlpatterns = patterns('',
(r'^' + settings.MEDIA_URL.lstrip('/'), include('appmedia.urls')),
) + urlpatterns
Otra prueba que he hecho es con el siguiente .htacces, aunque con identicos resultados:
## PYTHONVIRTUALENV
Options +Indexes
SetHandler wsgi-script
RewriteEngine On
RewriteRule ^(imagenes/.+)$ - [PT,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /app.wsgi/$1 [QSA,PT,L]
Continuemos ahora con el interprete de python:
El contenido de las app.wsgi es una serie de pequeños tests de escritura y variables de configuración para ver que hace mod_wsig y que está ocurriendo cuando la aplicación es interpretada por este, primero pythonsistema:
import os
import sys
import datetime
import string
import random
import pwd
import grp
base = os.path.abspath("/home/slothy/")
rutas = [os.path.join(base,"public_html/pythonsistema/"), os.path.join(base,"public_html/pythonsistema/imagenes/"), os.path.join(base,"prueba_escritura")]
map(lambda x: sys.path.append(x), rutas)
sys.stdout = sys.stderr
def application(environ, start_response):
status = '200 OK'
output = 'Pruebas de escritura \n'
output +="\nTEST_ESCRITURA\n"
archivo = datetime.datetime.isoformat(datetime.datetime.now(),"-") + ".txt"
try:
output += "Directorios de Escritura: \n"
for direc in rutas:
output += str(direc) + "\n"
except Exception as e:
output += "%s \n" % e
output += "ARCHIVO: %s \n" % archivo
for test in rutas:
try:
fi = open(os.path.join(test,archivo),"w")
chars = "".join( [random.choice(string.letters[:26]) for i in xrange(15)] )
fi.write(chars + "\n")
fi.close()
output += "Escrito %s \n" % test
except Exception as e:
output += "%s %s USER: %s GROUP: %s\n" % (e, test, str(pwd.getpwuid(os.stat(test).st_uid)[0]), str(grp.getgrgid(os.stat(test).st_gid)[0]))
pass
output += "\nPROCESO PYTHON\nVERSION: %s EJECUTABLE: %s PID: %s USUARIO: %s \n\n SYS_PATH\n" % (str(sys.version), str(sys.executable),str(os.getpid()), str(pwd.getpwuid(os.getuid())[0]))
try:
for tup in sys.path:
output += str(tup) + "\n"
except Exception as e:
output += "%s \n" % e
output += "\n\nENVIRON:\n"
try:
for tup in environ.items():
output += str(tup) + "\n"
except Exception as e:
output += "%s \n" % e
if 'HOME' in os.environ:
output += "\nHOME declarada en environ: %s" % os.environ['HOME']
else:
output += "\nHOME no declarada en environ: %s" % pwd.getpwuid(os.getuid()).pw_dir
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
from paste.exceptions.errormiddleware import ErrorMiddleware
application = ErrorMiddleware(application, debug=True, show_exceptions_in_wsgi_errors=True)
Primero trata de escribir en un fichero en 3 directorios concretos, en el mismo directorio de app.wsgi, detrás, en imágenesy fuera del document_root. en todos falla, ya que no tiene permisos de escritura.
Después muestra información sobre el interprete python que ejecuta el script, versión, ejecutable, pid y usuario que ejecuta el proceso.
Tras esto imprime el sys.path.
Y por último imprime la variable environ al completo de la específicación wsgi
Ahora lo mismo para la app que corre USANDO LAS LIBRERIAS instaladas en el virtualenv, que no el ejecutable:
VIRTUALENV_DIR = 'pythonvirtualenv'
ALLDIRS = ['/home/slothy/.virtualenvs/pythonvirtualenv/lib/python2.6/site-packages','/home/slothy/.virtualenvs/pythonvirtualenv']
import os
import sys
import datetime
import string
import random
import pwd
import grp
import site
prev_sys_path = list(sys.path)
base = os.path.abspath("/home/slothy/")
rutas = [os.path.join(base,"public_html/pythonvirtualenv/"), os.path.join(base,"public_html/pythonvirtualenv/imagenes/"), \
os.path.join(base,"prueba_escritura"), os.path.abspath('/home/slothy/.virtualenvs/pythonvirtualenv')]
map(lambda x: sys.path.append(x), rutas)
sys.stdout = sys.stderr
for directory in ALLDIRS:
site.addsitedir(directory)
# Reorder sys.path so new directories at the front.
new_sys_path = []
for item in list(sys.path):
if item not in prev_sys_path:
new_sys_path.append(item)
sys.path.remove(item)
sys.path[:0] = new_sys_path
activate_this = '/home/slothy/.virtualenvs/' + VIRTUALENV_DIR + '/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))
os.environ['PYTHON_EGG_CACHE'] = '/home/slothy/.virtualenvs/' + VIRTUALENV_DIR + '/lib/python2.6/site-packages'
import pytranslate
def application(environ, start_response):
status = '200 OK'
output = 'Pruebas de escritura \n'
output +="\nTEST_ESCRITURA\n"
archivo = datetime.datetime.isoformat(datetime.datetime.now(),"-") + ".txt"
try:
output += "Directorios de Escritura: \n"
for direc in rutas:
output += str(direc) + "\n"
except Exception as e:
output += "%s \n" % e
output += "ARCHIVO: %s \n" % archivo
for test in rutas:
try:
fi = open(os.path.join(test,archivo),"w")
chars = "".join( [random.choice(string.letters[:26]) for i in xrange(15)] )
fi.write(chars + "\n")
fi.close()
output += "Escrito %s \n" % test
except Exception as e:
output += "%s %s USER: %s GROUP: %s\n" % (e, test, str(pwd.getpwuid(os.stat(test).st_uid)[0]), str(grp.getgrgid(os.stat(test).st_gid)[0]))
pass
output += "\nPROCESO PYTHON\nVERSION: %s EJECUTABLE: %s PID: %s USUARIO: %s \n\n SYS_PATH\n" % (str(sys.version), str(sys.executable),str(os.getpid()), str(pwd.getpwuid(os.getuid())[0]))
try:
for tup in sys.path:
output += str(tup) + "\n"
except Exception as e:
output += "%s \n" % e
output += "\n\nENVIRON:\n"
try:
for tup in sorted(environ.items()):
output += str(tup) + "\n"
except Exception as e:
output += "%s \n" % e
if 'HOME' in os.environ:
output += "\nHOME declarada en environ: %s" % os.environ['HOME']
else:
output += "\nHOME no declarada en environ: %s" % pwd.getpwuid(os.getuid()).pw_dir
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
from paste.exceptions.errormiddleware import ErrorMiddleware
application = ErrorMiddleware(application, debug=True, show_exceptions_in_wsgi_errors=True)
La diferencia radica en reordenar el sys.path, para que el interprete utilize las librerías del virtualenv, el interprete es el mismo corriendo con los mismos permisos. De cualquier manera, si en lugar de ejecutarse el python del sistema se ejecutase el python del virtualenv, ¿correría con los permisos del usuario?, creo que igualmente no. Esto está sacado de la docu de mod_wsgi
Defining Process Groups
By default all WSGI applications will run in what is called 'embedded' mode. That is, the applications are run within Python sub interpreters hosted within the Apache child processes. Although this results in the best performance possible, there are a few down sides.
First off, embedded mode is not recommended where you are not adept at tuning Apache. This is because the default MPM settings are never usually suitable for Python web applications, instead being biased towards static file serving and PHP applications. If you run embedded mode without tuning the MPM settings, you can experience problems with memory usage, due to default number of processes being too may, and can also experience load spikes, due to how Apache performs lazy creation of processes to meet demand.
Secondly, embedded mode would not be suitable for shared web hosting environments as all applications run as the same user and through various means could interfere with each other.
Running multiple Python applications within the same process, even if separated into distinct sub interpreters also presents other challenges and problems. These include problems with Python extension modules not being implemented correctly such that they work from a secondary sub interpreter, or when used from multiple sub interpreters at the same time....
Y esto otro:
Defining Application Groups
By default each WSGI application is placed into its own distinct application group. This means that each application will be given its own distinct Python sub interpreter to run code within. Although this means that applications will be isolated and cannot in general interfere with the Python code components of each other, each will load its own copy of all Python modules it requires into memory. If you have many applications and they use a lot of different Python modules this can result in large process sizes.
To avoid large process sizes, if you know that applications within a directory can safely coexist and run together within the same Python sub interpreter, you can specify that all applications within a certain context should be placed in the same application group. This is indicated by using the WSGIApplicationGroup directive..
Ya por último, una solución "parche" que he aplicado se crea un directorio, se le dan permisos de escritura a+w, con un script .wsgi se crea la estructura necesaria, dentro de ese directorio, de manera que lo que se cree tiene permisos nobody:nobody, una vez hecho esto, se le devuelven los permisos normales al directorio, y tiene un lugar "semi-privado" donde escribir con python.
Por cierto muchas gracias shakaran por el paste.
Un saludo a todos y disculpad el tostón.