63

i'd like to use a view i've created in my database as the source for my django-view.

Is this possible, without using custom sql?

******13/02/09 UPDATE***********

Like many of the answers suggest, you can just make your own view in the database and then use it within the API by defining it in models.py.

some warning though:

  • manage.py syncdb will not work anymore
  • the view need the same thing at the start of its name as all the other models(tables) e.g if your app is called "thing" then your view will need to be called thing_$viewname
frederj
  • 1,483
  • 9
  • 20
spence91
  • 77,143
  • 9
  • 27
  • 19
  • 3
    The view dows not need to have the same name as the app. Just use the Meta field db_table. For example view named its_a_View. class Meta: db_table = u'its_a_view' – grantk Sep 09 '11 at 02:30

5 Answers5

95

Just an update for those who'll encounter this question (from Google or whatever else)...

Currently Django has a simple "proper way" to define model without managing database tables:

Options.managed

Defaults to True, meaning Django will create the appropriate database tables in syncdb and remove them as part of a reset management command. That is, Django manages the database tables' lifecycles.

If False, no database table creation or deletion operations will be performed for this model. This is useful if the model represents an existing table or a database view that has been created by some other means. This is the only difference when managed is False. All other aspects of model handling are exactly the same as normal.

Fernando Macedo
  • 2,518
  • 1
  • 24
  • 25
drdaeman
  • 11,159
  • 7
  • 59
  • 104
  • 3
    To provide some context; this feature is available in Django 1.1 upwards. – spence91 Jan 25 '10 at 13:05
  • 1
    Keep in mind that you will run into TransactionErrors when trying to delete objects that are referenced by objects in your database view by `models.ForeignKey`. – jnns Sep 02 '13 at 22:51
  • If you are using Django 1.3+ then you can avoid TransactionErrors by using [ForeignKey.on_delete](https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.on_delete): `user = models.ForeignKey(User, on_delete=models.DO_NOTHING)` – rhunwicks Nov 06 '13 at 03:44
34

Since Django 1.1, you can use Options.managed for that.

For older versions, you can easily define a Model class for a view and use it like your other views. I just tested it using a Sqlite-based app and it seems to work fine. Just make sure to add a primary key field if your view's "primary key" column is not named 'id' and specify the view's name in the Meta options if your view is not called 'app_classname'.

The only problem is that the "syncdb" command will raise an exception since Django will try to create the table. You can prevent that by defining the 'view models' in a separate Python file, different than models.py. This way, Django will not see them when introspecting models.py to determine the models to create for the app and therefor will not attempt to create the table.

Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145
  • Any idea how it would handle calling the save method on it? Some dbms's have updatable views. – Sam Corder Feb 04 '09 at 23:28
  • I am not sure. Chances are that Django simply tries to run an INSERT or UPDATE query on the view, but I don't have too much insight in the Django source code :-) – Ferdinand Beyer Feb 05 '09 at 22:22
  • 15
    *Sigh*. **No need for downvotes, folks!** This is an ancient answer to an ancient question. In February 2009, there was no `Options.managed` yet, which appeared in Django 1.1 on July 29, 2009... – Ferdinand Beyer Apr 24 '14 at 19:45
  • 10
    If your answer is no longer appropriate/accurate today, I encourage you to delete the answer or replace it with the answer that's correct today. (Downvotes are entirely understandable if the answer is no longer accurate or the best solution today, even if the answer was accurate at the time the question was asked.) StackExchange is intended to be a repository of high-quality answers that will be useful to future readers, not just a historical archive of what the right answer was 5 years ago. – D.W. Jan 16 '15 at 17:58
14

I just implemented a model using a view with postgres 9.4 and django 1.8.

I created custom migration classes like this:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_previousdependency'),
    ]

    sql = """
    create VIEW myapp_myview as
     select your view here
    """

    operations = [
        migrations.RunSQL("drop view if exists myapp_myview;"),
        migrations.RunSQL(sql)
    ]

I wrote the model as I normally would. It works for my purposes.

Note- When I ran makemigrations a new migration file was created for the model, which I manually deleted.

Full disclosure- my view is read only because I am using a view derived from a jsonb data type and have not written an ON UPDATE INSTEAD rule.

eceppda
  • 237
  • 2
  • 7
  • If you don't want to install `sqlparse`, you can wrap the arguments to `RunSQL` in `[]`. – Dan Jun 08 '16 at 10:33
3

From Django Official Documentation, you could call the view like this:

#import library
from django.db import connection

#Create the cursor
cursor = connection.cursor()

#Write the SQL code
sql_string = 'SELECT * FROM myview'

#Execute the SQL
cursor.execute(sql_string)
result = cursor.fetchall()

Hope it helps ;-)

jobima
  • 5,790
  • 1
  • 20
  • 18
3

We've done this quite extensively in our applications with MySQL to work around the single database limitation of Django. Our application has a couple of databases living in a single MySQL instance. We can achieve cross-database model joins this way as long as we have created views for each table in the "current" database.

As far as inserts/updates into views go, with our use cases, a view is basically a "select * from [db.table];". In other words, we don't do any complex joins or filtering so insert/updates trigger from save() work just fine. If your use case requires such complex joins or extensive filtering, I suspect you won't have any problems for read-only scenarios, but may run into insert/update issues. I think there are some underlying constraints in MySQL that prevent you from updating into views that cross tables, have complex filters, etc.

Anyway, your mileage may vary if you are using a RDBMS other than MySQL, but Django doesn't really care if its sitting on top of a physical table or view. It's going to be the RDBMS that determines whether it actually functions as you expect. As a previous commenter noted, you'll likely be throwing syncdb out the window, although we successfully worked around it with a post-syncdb signal that drops the physical table created by Django and runs our "create view..." command. However, the post-syncdb signal is a bit esoteric in the way it gets triggered, so caveat emptor there as well.

EDIT: Of course by "post-syncdb signal" I mean "post-syncdb listener"

Joe Holloway
  • 28,320
  • 15
  • 82
  • 92