views:

129

answers:

2

I have two Django model classes that are structured similar to the following:

class Build(models.Model):
    project = models.CharField(max_length=100)
    ...

class CustomBuild(Build):
    custom_type = ...
    ...

I want to select all Builds and CustomBuilds (each CustomBuild has a one-to-one relationship with a Build) from the database with a specific project attribute.

I believe Build.objects.filter(project="myproject") will select the correct objects but many of them will be missing the additional data (such as custom_type) that would be provided by a CustomBuild object. On the other hand, filtering CustomBuild.objects will exclude those objects that are not CustomBuilds.

How can I accomplish this? Thanks.

A: 

You can fetch Build objects with Build.objects.filter() and access the subclass when you need to:

qs = Build.objects.all()
build_obj = qs[4]
custom_build_obj = build_obj.custom_build
custom_build_obj.custom_type = ...
shanyu
+2  A: 

There are a couple approaches you can take to handling the QuerySets of "mixed models" that you obtain when you perform this kind of query. A lot of it depends on your ultimate goal.

The "simple and dumb" way, which I use a lot, is to manage the results with utility functions. If you plan to process the result of Build.objects.filter(project="myproject") in a template, for example, you could use custom template tags or filters to take special action. Assume in the below code build_objects contains the result of your filter():

{% for build in build_objects %}
   {% if build|is_custom_build %}
      <p>This is a custom build of custom type {{ build.custom_build.custom_type }}!</p>
   {% endif %}
   <p>This is a custom build OR a standard build</p>
{% endfor %}

The obvious problem here is if you have numerous subclasses, writing template filters may be impractical or grow tedious. However, in my experience I usually have a half-dozen subclasses at most so this is not always a problem.

You could also write a parameterized filter like so:

{% if build|is_of_buildtype:"custom_build" %}

With the filter code as follows:

def is_of_buildtype_filter(value, arg):
    if hasattr(value, arg): return True
    else: return False

This filter simply checks for the presence of the argument as an attribute on the build object (passed in as value). The argument string should be the name of the automatically generated OneToOneField that you want to detect, in this case custom_build.

For view code, similar sorts of helper functions would work the same way but are even easier because you don't need to write custom filters or tags.

This approach works in many cases, but there are more complicated scenarios where it might not be practical. Unfortunately, Django cannot natively provide you with a QuerySet that contains subclass instances when you perform operations on the base class (ie. a QuerySet that truly contains "mixed models"). You may require this in situations where processing the results with helper functions is not possible.

I personally avoid these situations entirely, usually by rethinking my model design. But if that's not possible, there are many interesting attempts at solutions, like this Inheritance MixIn. There are also several Django snippets on the subject. Be aware, though, that almost any solution like this will be performance-limited.

jdl2003
I appreciate the time you put into writing this comment. I was really more interested in how to do this in code rather than templates, though. I wish I could accept both answers since I found your hasattr code useful, but I upvoted your answer instead.
titaniumdecoy