How to alter django settings based on current request? - django

I'm running multiple sites from a single django instance, and expect the framework to eventually serve several hundred sites from one or several installations.
I need to patch some django settings based on the current request. I've written some middleware to monkey patch the settings, but I need these settings to be patched before the middleware gets invoked because other apps aren't taking the monkey-patched changes (i.e. apps get run then middleware gets run so the apps don't use the monkey-patched settings).
I should also add this is mainly for the benefit of third-party apps that I haven't written, so I don't want to go round adding decorators or anything like that because that would mess up my upgrade path.
So:
How can I get access to the current request in an app's init.py file?
Will an app's init.py get called once per request or only once? If it's only once, how else could I do this so I can manipulate the settings once per request?
Is it safe to do this kind of monkey patching? I know it makes code a bit more opaque, but I don't want to use different wsgi files per site because I want to allow users to edit some of these settings and have my monkey patching come from the database.
Is there a better solution that would allow certain settings to be stored in the database?

This module - django-tupperware does what you are asking about: https://bitbucket.org/jiaaro/django-tupperware/
Give it a try.

Never ever ever change settings on the fly. You cannot predict how the application may one day be deployed, and in most of them the project will fail in.. interesting ways.

If you really want to have hundreds of sites which will be dynamically reconfigured all the time, you might try to Djangos django/conf/__init__.py which is used to supply the settings to the rest of the framework. You might be able to alter it in a way it depends on the request and it's Host field. Or you'll get many interesting failures at the very least.

My solution to this problem is to give each request, ALL the settings and update them on the fly with a middleware, how do i do this?
Is rather simple:
This is the middleware that does it all
from django.conf import settings
class DumpObject: pass
class Settings(object):
def process_request(self,request):
request.settings = DumpObject()
for setting in dir(settings):
if not setting.startswith('__'):
setattr(request.settings, setting, getattr(settings,setting))
The DumpObject is just there so i can use the object.property notation, it could have been a dictionary, but i wanted to keep some similarity in the syntax.
This assumes that none of your settings name starts with __ which is a fair assumption.
So if i want to override a particular setting, i don't keep a settings file i do it in this middleware. like this:
class Settings(object):
def process_request(self,request):
request.settings = DumpObject()
for setting in dir(settings):
if not setting.startswith('__'):
setattr(request.settings, setting, getattr(settings,setting))
if 'mydomain' in str(request.host): #this is thanks to django-hosts project
request.settings.GOOGLE_ANALYTICS_ID = '89298393-238'
Of course this doesnt take into account the problem of accessing the settings the old way
from django.conf import settings
settings.GOOGLE_ANALYTICS_ID = 'the value in settings.py'
But it really doesn't matter because you will probably only want to change the settings on the context of having a request object, available.

Related

Exchanging data between django apps in the same project

I would like to exchange data between two django apps that are coming from a single big app that due to the increasing size and functionalities we decided to split.
Specifically I need to retrive only a string in one app from the other. Litterally a 10 character string.
The only thing that I found at the moment that satisfies me, since I would like to avoid import stuff from the other app (to me it seems not a clean way to do it, in the other case please change my mind), is making an http request from one app to the other.
Anyway I found it overkill.
Is there a clean way to achive this without using http request or imports?
If you mean constant app configuration data, that lives in the project settings.py adnd doesn't change except at a server re-start
from django.conf import settings
more
It's possible to make an import fail soft, if you just want to handle the case where the other app is not installed. For example
try:
from other_app.models import Foo
except ModuleNotFoundError:
from .models import Foo_Stub as Foo
(obviously you can get as sophisticated as you want with your stub models and other methods that aren't available from the other app).

Django - Runtime database switching

In my work we want to run a server with multiple databases. The databases switching should occur when you acces a url like http://myapp.webpage.com or http://other.webpage.com. We want to run only one server instance and at the moment of the HTTP request switch the database and return the corresponding response.
We've been looking for a mantainable and 'Django-friendly' solution. In our investigation we have found possible ways to do this, but we have not enough information about.
Option 1: Django middleware
The django middleware runs each time the server receive a HTTP request.
Making a database switch here could be the best option but using django database routers as far as I know only allow to change the database for a model or group or models.
Another option is to set a django model manager instance in the middleware and force all models to re-assign the objects attribute from an added attribute in the custom middleware.
My last option is to create a new attribute in the request object received by the middleware that return the database alias from settings.py and in each model query use the using method.
Option 2: Class-based View Mixin
Create a mixin that use the past three options, but I the mixin must be set in ALL the Class-based views. If a programmer forget to set the mixin and it comes to a production server, the data could be (or stop being) in the right database, and I don't wanna take the risk.
Option 3: Changing the database settings in runtime
This option works but Is not recommended and is too risky.
UPDATE:
How this works?
middlewares.py
import django.conf as conf
import os.path
class SelectDB(object):
def process_request(self, request):
print request.META['HTTP_REFERER']
file_database = open("booklog/database.txt", "r")
database = file_database.read(10)
file_database.close()
if database != 'default':
conf.settings.DATABASES['default']['NAME'] = database
Any information that help us to solve will be greatly appreciated.
Answer (it worked for me)
The question was already answered here, in stackoverflow. I'd love this functionality were in django. It was a bit hard to find the way to make this possible.
I think that is important to comment the great work that Wilduck made with the django plugin django-dynamic-db-router, it's a great plugin that makes possible this operation in (a bit) different way.
Thanks a lot to #JL Peyret and #ire_and_curses.
And as an answer to #ire_and_curses. At least in this moment, in the project I'm working it's what we need. In previous projects we needed a similar behavior and made one server per instance was terrible to mantain and update each server, even automating the process.

User permissions Django for serving media

I want to set up a Django server that allows certain users to access certain media. I'm sure this can't be that hard to do and I'm just being a little bit silly.
For example I want USER1 to be able to access JPEG1, JPEG2 and JPEG3 but not JPEG4, and USER2 to be able to access JPEG3 and JPEG 4.
[I know I should be burnt with fire for using Django to serve up media, but that's what I'm doing at the moment, I'll change it over when I start actually running on gas.]
You can send a file using django by returning the file in the request as shown in Vazquez-Abrams link.
However, you would probably do best by using mod_xsendfile in apache (or similar settings in lighttpd) due to efficiency. Django is not as fast at sending it, one way to do so while keeping the option of using the dev server's static function would be http://pypi.python.org/pypi/django-xsendfile/1.0
As to what user should be able to access what jpeg, you will probably have to implement this yourself. A simple way would be to create an Image model with a many-to-many field to users with access and a function to check if the current user is among those users. Something along the line of:
if image.users_with_access.filter(pk=request.user.id).exists():
return HttpResponse(image.get_file())
With lots of other code of course and only as an example. I actually use a modified mod_xsend in my own project for this very purpose.
You just need to frob the response appropriately.
You can put the media in http://foo.com/media/blah.jpg and set up a media/(?P<file>.*) in urls.py to point to a view blahview that checks the user and their permissions within:
from you_shouldve_made_one_anyways import handler404
def blahview(request,*args,**kwargs):
if cannot_use( request.user, kwargs['username'] ): return handler404(request)
...
Though just to be clear, I do not recommend serving media through Django.

How to register a model in django-tagging anywhere not in the applications?

Is it possible to register a model in django-tagging not in tagging app, nor in my app?
The standard way is to edit apps/myapp/models.py this way:
from apps import tagging
tagging.register(MyModel)
I want to keep both applications without changes, for example, to be able to pull new versions and just replace them. So I tried putting this into project settings.py, in the end, but of course this fails.
from apps.myapp.models import MyModel
from apps import tagging
tagging.register(MyModel)
(This fails when importing MyModel.)
Any other way?
You can't do that in settings.py, as the models haven't been set up yet. One possibility is to do it in urls.py - admin.autodiscover is already there, so this might be a good place for the call to tagging.register as well.
There has been quite a lot of discussion in the django-developers group about the correct place for this sort of thing, but as yet no firm policy has been reached.

What gets executed as the server starts vs. as a request comes in?

I've been doing some class hacking in Django. And I call my changes from settings.py as I figure this runs before anything else.
However, I've just come across a situation where this doesn't work for me. So, is there another place after settings.py, which I can guarantee will always be run by the server before it starts handling any requests?
You should never put code in settings.py that requires importing anything from any part of Django. Since many parts of Django require settings to be available, this is very likely to get you into circular import problems.
Your ROOT_URLCONF (urls.py) is a reasonable place to put project-level code that you want run once for each server Python process, before any requests are served.
If the code is specific to a particular app (and only needed if that app is in use) then you could put it in that app's models.py or __init__.py.
For a broader look at the issue, see this blog post.
I'm not sure exactly what you mean by 'class hacking' but have you tried calling your changes from ./manage.py?
From the docs:
In addition, manage.py is
automatically created in each Django
project. manage.py is a thin wrapper
around django-admin.py that takes care
of two things for you before
delegating to django-admin.py:
It puts your project’s package on
sys.path. It sets the
DJANGO_SETTINGS_MODULE environment
variable so that it points to your
project’s settings.py file.
so if you have some stuff that you want to run after settings.py this might be the case if its a hack you're after.
HTH
Something like the request_started signal?
If you want to put code somewhere in your django project that will get run for certain every time you start up django, pick an app form INSTALLED_APPS. Both the __init__.py and the models.py will be run for sure. They are good places for things like signals or anything you must register.