[Django]-Django: Querying read-only view with no primary key

26👍

When you say ‘I have no control over the view I am accessing with Django. MySQL browser shows columns there but no primary key.’

I assume you mean that this is a legacy table and you are not allowed to add or change columns?

If so and there really isn’t a primary key (even a string or non-int column*) then the table hasn’t been set up very well and performance might well stink.

It doesn’t matter to you though. All you need is a column that is guaranteed to be unique for every row. Set that to be ‘primary_key = True in your model and Django will be happy.

  • There is one other possibility that would be problemmatic. If there is no column that is guaranteed to be unique then the table might be using composite primary keys. That is – it is specifying that two columns taken together will provide a unique primary key. This is perfectly valid relational modelling but unfortunatly unsupported by Django. In that case you can’t do much besides raw SQL unless you can get another column added.

17👍

I have this issue all the time. I have a view that I can’t or don’t want to change, but I want to have a page to display composite information (maybe in the admin section). I just override the save and raise a NotImplementedError:

    def save(self, **kwargs):
        raise NotImplementedError()

(although this is probably not needed in most cases, but it makes me feel a bit better)

I also set managed to False in the Meta class.

    class Meta:
       managed = False

Then I just pick any field and tag it as the primary key. It doesn’t matter if it’s really unique with you are just doing filters for displaying information on a page, etc.

Seems to work fine for me. Please commment if there are any problems with this technique that I’m overlooking.

3👍

If there really is no primary key in the view, then there is no workaround.

Django requires each model to have exactly one field primary_key=True.

1👍

There should have been an auto-generated id field when you ran syncdb (if there is no primary key defined in your model, then Django will insert an AutoField for you).

This error means that Django is asking your database for the id field, but none exists. Can you run django manage.py dbshell and then DESCRIBE read_only_view; and post the result? This will show all of the columns that are in the database.

Alternatively, can you include the model definition you excluded? (and confirm that you haven’t altered the model definition since you ran syncdb?)

1👍

I know this post is over a decade old, but I ran into this recently and came to SO looking for a good answer. I had to come up with a solution that addresses the OP’s original question, and, additionally, allows for us to add new objects to the model for unit testing purposes, which is a problem I still had with all of the provided solutions.

main.py

from django.db import models

def in_unit_test_mode():
    """some code to detect if you're running unit tests with a temp SQLite DB, like..."""
    import sys
    return "test" in sys.argv
    """You wouldn't want to actually implement it with the import inside here. We have a setting in our django.conf.settings that tests to see if we're running unit tests when the project starts."""

class AbstractReadOnlyModel(models.Model):
    class Meta(object):
        abstract = True
        managed = in_unit_test_mode()

    """This is just to help you fail fast in case a new developer, or future you, doesn't realize this is a database view and not an actual table and tries to update it."""
    def save(self, *args, **kwargs):
        if not in_unit_test_mode():
            raise NotImplementedError(
                "This is a read only model.  We shouldn't be writing "
                "to the {0} table.".format(self.__class__.__name__)
            )
        else:
            super(AbstractReadOnlyModel, self).save(*args, **kwargs)

class DbViewBaseModel(AbstractReadOnlyModel):
    not_actually_unique_field = IntegerField(primary_key=True)
    # the rest of your field definitions

    class Meta:
        db_table = 'read_only_view'

if in_unit_test_mode():
    class DbView(DbViewBaseModel):
        not_actually_unique_field = IntegerField()
        """This line removes the primary key property from the 'not_actually_unique_field' when running unit tests, so Django will create an AutoField named 'id' on the table it creates in the temp DB that it creates for running unit tests."""
else:
    class DbView(DbViewBaseModel):
        pass

class MainClass(object):
    @staticmethod
    def main_method(request):
        return DbView.objects.all()

test.py

from django.test import TestCase
from main import DbView
from main import MainClass

class TestMain(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.object_in_view = DbView.objects.create(
            """Enter fields here to create test data you expect to be returned from your method."""
        )
    
    def testMain(self):
        objects_from_view = MainClass.main_method()
        returned_ids = [object.id for object in objects_from_view]
        self.assertIn(self.object_in_view.id, returned_ids)
👤Matt L

Leave a comment