The problem: I wish to use Postgres Schemas to separate the tables of different parts of my django app at database level.
Aside
You can skip this section, but I think it’s helpful to add context to these things. My app is working on a database of existing data (stored in the public schema, helpfully), which it’s very important I don’t modify. As such, I want to separate “my” data into a separate schema (to which django will be given read/write/play in the sand access), while restricting access to the public schema to read-only. I originally tried to solve this by separating my data out into a separate database and using database routing, but it turns out (if I’d only read the documentation) that django doesn’t support cross database dependencies (which is fair enough I suppose), and my models have foreign keys into the read-only data.
The meat
There exists a workaround for Django’s lack of schema support (which you can read about here) which is to specify the db_table attribute in your model’s meta, like so:
class MyModel(models.Model):
attribute1 = models.CharField()
#Fool django into using the schema
class Meta:
db_table = 'schema_name\".\"table_name'
This is great, but I didn’t really want to have to write this for every single model in my app – for a start, it doesn’t seem pythonic, and also there’s every chance of me forgetting when I have to add a new model.
My solution was the following snippet:
def SchemaBasedModel(cls):
class Meta:
db_table = '%s\".\"%s' % (schema_name, cls.__name__)
cls.Meta = Meta
return cls
@SchemaBasedModel
class MyModel(models.Model):
attribute1 = models.CharField()
...
When I then run python manage.py shell I get the following:
>>> from myapp import models
>>> myModel = models.MyModel
>>> myModel.Meta.db_table
'myschema"."mymodel'
>>>
“Looks good to me,” I thought. I then ran: python manage.py sqlall myapp. Sadly, this yielded the original table names – that is, the table names as they were before I applied this meta info. When I went back and applied the meta info “by hand” (i.e. by adding Meta inner classes to all my models), things were as expected (new table names).
I was hoping somebody could enlighten me as to what was going on here? Or, more usefully, what’s the “right” way to do this? I thought the decorator pattern I’ve talked about here would be just the ticket for this problem, but apparently it’s a non-starter. How can I quickly and easily apply this meta info to all my models, without typing it out every single time?
Edit: Perhaps I was a little unclear when I asked this – I’m as interested in know what’s “actually going on” (i.e. why things aren’t working the way I thought they would – what did I misunderstand here?) as how to solve my problem (clear separation of “my” data from legacy data, preferably on a schema level – but it’s not the end of the world if I have to dump everything into the public schema and manage permissions on a per-table basis).
Second Edit: The accepted answer doesn’t necessarily tell me what I really want to know, but it is probably the right solution for the actual problem. Short answer: don’t do this.
That’s false. Some things have to be written down explicitly. “Explicit is better than Implicit”.
That’s false, also.
You won’t “forget”.
Bottom Line: Don’t mess with this kind of thing. Simply include the 2 lines of code explicitly where necessary.
You don’t have that many tables.
You won’t forget.
Also, be sure to use DB permissions. Grant SELECT permission only on your “legacy” tables (the tables you don’t want to write to). Then you can’t write to them.