HoHo HoHo - 2 months ago 8
Python Question

How to use base classes in Django

I'm trying to alter an app I've created so that it is reusable. It's based around a single model which sites using the app will subclass. As it stands, my non-reusable version has the following kind of structure:

# models.py
class Document(models.Model):
contents = models.TextField()
date = models.DateTimeField()

# views.py
from .models import SiteModel
# ...
class MyView(ListView):
def some_method(self, list_of_pks):
model_vals = Document.objects.filter(pk__in = list_of_pks).values()

def perform_action(request):
obj_pk = request.POST.get('obj_pk')
obj = Document.objects.filter(pk = obj_pk)

MySignal.send(sender=Document, instance = obj)

#etc, etc


This works well enough. But my use case calls for different types of
Document
, one per site, that will have additional fields that aren't known in advance. Based on reading the documentation on abstract base classes, I thought the a reasonable solution would look like:

# models.py for the app
class BaseDocument(models.Model):
contents = models.TextField()

class Meta:
abstract = True

# models.py for a hypothetical site using the app
class SiteDocument(myapp.BaseDocument):
date = models.DateTimeField()
# other site-specific fields


What I don't understand is how to then reference the model in the app's
views.py
,
forms.py
, etc. I know
BaseDocument.objects.all()
, for example, won't return anything since it isn't connected to a database. Conversely, I can't have
Document.objects.all()
because
Document
hasn't been created yet and is specific to each site. Is an abstract base class not the correct solution, and if so, what is?

Edit:

It looks like using a
OneToOneField
may be best suited to my use case, although it looks like that precludes inheriting methods from the superclass and that
BaseDocument.objects.all()
won't list out all its children.

Alternatively, I was wondering if I could just add a
get_document_model()
method to my abstract base class, in the style of
get_user_model()
?

Answer

I ended up going with a solution mentioned in my edit, namely creating a get_document_model() method inspired by get_user_model(). This gives me exactly the desired behavior.

# models.py in app1
from django.db import models
from django.apps import apps as django_apps

class BaseDocument(models.Model):
    contents = models.TextField()

    class Meta:
        abstract = True

    def get_document_model():
        # exception handling removed for concision's sake
        return django_apps.get_model(settings.DOCUMENT_MODEL)

# models.py in app2
from django.db import models
from app1.models import BaseDocument

class SiteDocument(BaseDocument):
    date = models.DateTimeField()

Throughout views.py and elsewhere, I changed things that would have been of the form Document.objects.all() to BaseDocument().get_document_model().objects.all().