Webfaction apache + mod_wsgi + django configuration issue - python

A problem that I stumbled upon recently, and, even though I solved it, I would like to hear your opinion of what correct/simple/adopted solution would be.
I'm developing website using Django + python. When I run it on local machine with "python manage.py runserver", local address is http://127.0.0.1:8000/ by default.
However, on production server my app has other url, with path - like "http://server.name/myproj/"
I need to generate and use permanent urls. If I'm using {% url view params %}, I'm getting paths that are relative to / , since my urls.py contains this
urlpatterns = patterns('',
(r'^(\d+)?$', 'myproj.myapp.views.index'),
(r'^img/(.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT + '/img' }),
(r'^css/(.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT + '/css' }),
)
So far, I see 2 solutions:
modify urls.py, include '/myproj/' in case of production run
use request.build_absolute_uri() for creating link in views.py or pass some variable with 'hostname:port/path' in templates
Are there prettier ways to deal with this problem? Thank you.
Update: Well, the problem seems to be not in django, but in webfaction way to configure wsgi. Apache configuration for application with URL "hostname.com/myapp" contains the following line
WSGIScriptAlias / /home/dreamiurg/webapps/pinfont/myproject.wsgi
So, SCRIPT_NAME is empty, and the only solution I see is to get to mod_python or serve my application from root. Any ideas?

You shouldn't need to do anything special. Django honours the SCRIPT_NAME environment variable that is set by mod_wsgi when you serve a Django site other than from the root, and prepends it to the url reversing code automatically.
If you're using mod_python (you shouldn't be), you may need to set django.root in your Apache configuration.
Updated I suspect this is due to the way that Webfaction serves Django sites via a proxy instance of Apache - this instance has no knowledge of the actual mount point as determined by Webfaction's control panel.
In this case, you'll probably need to set SCRIPT_NAME manually in your .wsgi script. I think this should work:
_application = django.core.handlers.wsgi.WSGIHandler()
def application(environ, start_response):
os.environ['SCRIPT_NAME'] = '/myproj/'
return _application(environ, start_response)

Change:
WSGIScriptAlias / /home/dreamiurg/webapps/pinfont/myproject.wsgi
to:
WSGIScriptAlias /myproj /home/dreamiurg/webapps/pinfont/myproject.wsgi
Then change the nginx front end configuration of WebFaction to proxy to '/myproj' on back end instead of '/'.
That should be all that is required. You should not use '/myproj' prefix in urls.py.
In other words, just ensure the mount point for back end is same as where it appears mounted at the front end.
Modify WSGI script file to fudge SCRIPT_NAME, although it may work, is not generally recommended as not allowing Apache/mod_wsgi to do the proper thing, which may have other implications.

Related

Serving django app in an intended location (NGINX)

I have a web server serving my Django app (using NGINX) and I need to access it in a defined "location".
For example, I access my Django app XPTO in "ip:port/" but I need to access it like "ip:port/XPTO/". All urls specified in Django have to be resolved "after" this "base url".
Anyway I can do this without messing with my "urls.py" in Django? I had tried some configurations on NGINX but nothing worked.
Thanks in advance!
You need to use the location directive in your nginx config.
You probably have something right now that looks like this:
location / {
...
}
To serve under XPTO instead you want this:
location /XPTO/ {
...
}
You also need to be sure that you generate all internal links via the url tag or the reverse function, so that they will automatically include the prefix.
If this doesn't work, please show us your current nginx config (edit it into the question) and we may be able to provide more specific advice.

Django - Protecting Media files served with Apache with custom login_required decorator

I deployed a Django app using Apache, and I check for authentication in most views using a decorator.
#custom_decorator
def myView(request):
bla bla bla...
It's not the #login_required decorator that comes with Django, but it's almost the same thing, except that allows access only to users from certain groups. This works as intended.
Also, I'm serving media (user uploaded) files with Apache, like this:
Alias /media /path/to/media
<Directory /path/to/media>
Require all granted
</Directory
I can access the media files just fine, but the problem is that I can access them even if I'm not logged in, simply by typing the url manually, like:
mySite/media/myFile.png
Is there a way to limit access to the media files, hopefully using the custom decorator?
I stumbled across a similar question: How do you Require Login for Media Files in Django, but unfortunately the answer went way over my head.
Thanks in advance!
When you mention media path to the apache, those files are served directly by Apache (or Nginx or any other web server). Those requests do not even goes through your Django application. Hence you do not have a control over those requests or the data served by them.
One way is to create your separate API to serve the static/media files. In that API, use the same validation that you do for other content.
Even better, if you have AWS (Amazon Web Services) or GCP (Google Cloud Platform) account, store the static files on the S3 or Cloud Storage respectively and serve their URL of files via your API.
PS: Do not forget to remove the media path from the Apache configuration. Else, Apache will keeps on serving those file.
Alternatively, as mentioned in Sarafeim's answer to Restricting access to private file downloads in Django which requires modification in both sever and application side. You need a way for your HTTP server to ask the application server if it is ok to serve a file to a specific user requesting it. You may achieve this using django-sendfile which uses the X-SendFile mechanism. As per the django-sendfile's README:
This is a wrapper around web-server specific methods for sending files to web clients. This is useful when Django needs to check permissions associated files, but does not want to serve the actual bytes of the file itself. i.e. as serving large files is not what Django is made for.
To understand more about the sendfile mechanism, read: Django - Understanding X-Sendfile
Okay, so based on #MoinuddinQuadri answer and links, it seems that the easiest solution is to serve the files using a regular Django view, and apply the desired decorator, like this:
#custom_decorator
viewFile(request, objectID):
object = MyModel.object.get(id = objectID)
return HttpResponse(object.file, content_type = "image/png")
(In my case, I wanted to serve a FileField related to a Model, so in the view I pass the ID of the object instead of the file name).
Also, I commented out the corresponding code in the Apache conf:
### Alias /media /path/to/media
### <Directory /path/to/media>
### Require all granted
###</Directory
I had to change some templates to use the new view instead of the URL of the media file, but now it works as intended, locking out non-logged users.
However, this no longer uses Apache to serve the files, it uses Django itself, which according to the docs, is inneficient and not recommended.
Ideally you want to still serve the files using Apache and just use the view to protect its access, and for that you can use mod_xsendfile for Apache, or simply use Django Sendfile, which is a wrapper for the module just mentioned.
I tried the latter, but unfortunately it has problems with file names that have non-ascii characters. As my target are spanish-speaking users, I had to resort of just serving the files with Django, at least for now.
I used solution #1 in the post "Django protected media files". There are two other solutions described here as well: "Unpredictable Urls" and "X-Sendfile", but the one I'm describing was my choice.
As #Sauvent mentioned, this causes the files to be served by Django and not by a web server (e.g. Apache). But it's quick and easy if you're not dealing with a lot of traffic or large files.
Basically, add the following to your urls.py:
#login_required
def protected_serve(request, path, document_root=None, show_indexes=False):
return serve(request, path, document_root, show_indexes)
urlpatterns = patterns('',
url(r'^{}(?P<path>.*)$'.format(settings.MEDIA_URL[1:]), protected_serve, {'document_root': settings.MEDIA_ROOT}),
)
In my case I edited it to the following because my directories are set up differently and I use Login Required Middleware to ensure login is required everywhere (Django: How can I apply the login_required decorator to my entire site (excluding static media)?:
urlpatterns = patterns('',
url(r'^media/(?P<path>.*)$', "django.views.static.serve", {'document_root': settings.MEDIA_ROOT}),
)

What could cause a Django error when debug=False that isn't there when debug=True

Using the development server, it works with debug=True or False.
In production, everything works if debug=True, but if debug=False, I get a 500 error and the apache logs end with an import error: "ImportError: cannot import name Project".
Nothing in the import does anything conditional on debug - the only code that does is whether the development server should serve static files or not (in production, apache should handle this - and this is tested separately and works fine).
Just to say, I ran into a similar error today and it's because Django 1.5 requires the ALLOWED_HOSTS parameter in the settings.
You simply need to place this row to make it work ;)
...
ALLOWED_HOSTS = '*'
...
However, be aware that you need to set this parameter properly according to your actual host(s) (https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts)!
Values in this list can be fully qualified names (e.g. 'www.example.com'), in which case they will be matched against the request’s Host header exactly (case-insensitive, not including port). A value beginning with a period can be used as a subdomain wildcard: '.example.com' will match example.com, www.example.com, and any other subdomain of example.com. A value of '*' will match anything; in this case you are responsible to provide your own validation of the Host header (perhaps in a middleware; if so this middleware must be listed first in MIDDLEWARE_CLASSES).
So basically it's better for you to use this type of configuration once you're in production:
...
ALLOWED_HOSTS = [
'.yourdomain.com',
]
...
thanks to gertvdijk for pointing this out
This happens if you have a circular import in one of your files. Check and see if you are importing something from Project and then importing something in Project from the original file that originally imported Project.
I ran into this same problem recently, and rearranging some of my imports helped fix the problem.
This can also happen if you do not have both a 500.html and 404.html template present. Just the 500 isn't good enough, even for URIs that won't produce a 404!
I had this problem as well. Although it persisted even when setting Allowed_hosts and already having 404 and 500 templates.
I also checked for circular imports, but that was not it.
I finally had django produce a log file, https://stackoverflow.com/a/15100463/1577916
I accidentally left in a "get_host" function which now exists under
HttpRequest (changed to HttpRequest.get_host())with Django 1.5.
for some reason that was not raising an error with Debug True OR False.

How to configure the webserver to server a particular page as the default page?

I am using Lighttpd and Django. I have configured my Lighttpd server to pass all the requests ending with ".psp" extension to Django.
My startup page is a page served through Django, which is accessed as "http://192.168.1.198/home.psp". I want to enable the user to browse this page without writing "home.psp" explicitly in the url i.e. using "http://192.168.1.198"
Is this possible?
Thanks for any help in advance.
I think you're confusing concepts here between the "old" method of having individual files represent web pages which themselves contain code that is passed off to an interpreter before being sent in a response to how django/frameworks work.
If you're familiar with apache, imagine django as in part taking on the role of mod_rewrite. Django, and other frameworks, have what's called a dispatcher, or routing, mechanism.
Basically, they subscribe to the MVC pattern that says you should separate out the model, controller and view (in django parlance, model, template and view).
Now what then happens is you have a file called urls.py in django, which contains a list of routes (urls) and names of methods (usually contained in views.py) which handle them. Here's an example:
urlpatterns = patterns('',
url(r'^dologin$', 'testapp.views.auth_login', name="auth-login-uri"),
url(r'^doopenidlogin$', 'testapp.views.auth_openid_login', name="auth-openid-login-uri"),
url(r'^dologout$', 'testapp.views.auth_logout', name="auth-logout-uri"),
url(r'^login$', 'testapp.views.loginform', name="login-form"),
url(r'^openidlogin$', 'testapp.views.openidloginform', name="openid-login-form"),
url(r'^$', 'testapp.views.index', name="index"),
)
Here testapp is a python package, views.py is a python file and index is a django view. The url is constructed from regex, so I can have whatever I want as the url, much how stackoverflow urls are formed.
So basically, you never need file extensions again. I'd strongly suggest getting a good book on django - there are a few around.
What you might be looking for is the index-file.names directive in Lighty's configuration file. Just add "home.psp" to the list in your configuration file, and Lighty will look for it when no filename is specified.
I solved the problem myself. Here is what I did:
I had to add a statement inside url.rewrite-once block of lighttpd's configuration file like:
url.rewrite-once = (
"^(/media.*)$" => "$1",
"^(/static.*)$" => "$1",
"^/favicon\.ico$" => "/media/favicon.ico",
"^(/)$" => "/my_project_dir/home.psp",
"^(/.*)$" => "/my_project_dir$1",
)
Apart from this, I added the following line in my urls.py:
(r'^$',my_index_view_name),
Hope this helps someone in the future. Thanks everybody for your replies above. Cheers!

How should Django Apps bundle static media?

Background:
I'm starting to use Django for the first time, which is also my first foray into web development. I just got stuck on the whole "serving static media" problem. After spending a while looking at all the documentation and StackOverflow questions, I think I understand how it's supposed to work (i.e. MEDIA_ROOT, MEDIA_URL, updating the urls file, etc).
My Question:
Ok, so here's the part I'm not sure about. Django applications are supposed to be "pluggable", i.e. I can move an application from one project to another. So, how should these applications bundle static media?
For example, let's say I have a "foo" application, which has templates that load some css/image files. Where am I supposed to put these files, so that they'll automatically get served once I include the application?
The only solution I see, is that installing an application has to include the extra step of copying its static media to some place on your own server that serves that media.
Is this the accepted way to do it? It includes an extra step, but maybe that's standard when dealing with web-dev (I'm new so I don't really know).
Also, if this is the way, is there a standard way to collect all my static media to make it easy to know what I need to serve? (I.e., is it standard to have a folder named "media" or something inside the app?).
Thanks,
Convention is to put static media in either media/appname/ or static/appname/ within the app (similar to templates).
For using apps in your project that come with media, I strongly recommend using django-staticfiles. It will automatically serve media (including media within apps) in development through a view that replaces django.views.static.serve, and it comes with a build_static management command that will copy media from all apps into a single directory for serving in production.
Update: django-staticfiles has become part of Django 1.3. It now expects app media to live in a "static/" subdirectory of the app, not "media/". And the management command is now "collectstatic."
The only app I know of that deals with this without any intervention is the rather wonderful django-debug-toolbar, though it's arguable that this isn't a great example, since it's an app specifically designed for debug mode only.
The way it deals with it is that it serves its media through Django itself - see the source for urls.py:
url(r'^%s/m/(.*)$' % _PREFIX, 'debug_toolbar.views.debug_media'),
In general, this is a bad idea (you don't want to serve static files through Django), per this comment from the documentation:
[Serving static files through Django] is inefficient and
insecure. Do not use this in a
production setting. Use this only for
development.
Obviously, the django-debug-toolbar is only used for development, so I think its method of deployment makes sense, but this is very much an exception.
In general, the best way I know to do it is to create symbolic links wherever your media is stored to the media inside your app code. For example, create a folder called media within your app, and then require users installing your app to either add a symbolic link from their media directory, or copy the whole thing.
i usually put apps media in ./apps/appname/static (my apps resides in an apps subfolder)
then i have something similar in the vhost in apache :
AliasMatch ^/apps/([^/]+)/static/(.*) /home/django/projectname/apps/$1/static/$2
<DirectoryMatch "^/home/django/projectname/apps/([^/]+)/static/*">
Order deny,allow
Options -Indexes
deny from all
Options +FollowSymLinks
<FilesMatch "\.(flv|gif|jpg|jpeg|png|ico|swf|js|css|pdf|txt|htm|html|json)$">
allow from all
</FilesMatch>
</DirectoryMatch>
i also have this in my urls.py for dev server (use only for debug) :
def statics_wrapper(request, **dict):
from django.views import static
return static.serve(request, dict['path'], document_root = os.path.join(settings.BASE_DIR, 'apps', dict['app'], 'static'), show_indexes=True)
urlpatterns += patterns('', (r'^apps/(?P<app>[^/]+)/static/(?P<path>.+)$', statics_wrapper))
this is very handy because statics url are simply mapped to filesystem, eg :
http://wwww.ecample.com/apps/calendar/static/js/calendar.js resides in [BASE_DIR]/apps/calendar/static/js/calendar.js
hope this helps

Categories