Django App Dependency Cycle

Question:

I am in the middle of developing a Django application, which has quite complicated models (it models a university – courses, modules, lectures, students etc.)

I have separated the project into apps, to make the whole thing more organised (apps are courses, schools, people, modules and timeperiods). I am having a problem whereby a model in one app may depend on a model in another – so I must import it. The second app then in turn depends on a model in the first, so there is a cycle and Python throws up an error.

How do people deal with this? I understand that apps should be relatively “independent”, but in a system like this it doesn’t make sense, for example, to use ContentTypes to link students to a module.

Does anyone have a similar project that could comment on this case?

Asked By: Rob Golding

||

Answers:

Normally I advocate for splitting functionality into smaller apps, but a circular dependence between models reflects such a tight integration that you’re probably not gaining much from the split and might just consider merging the apps. If that results in an app that feels too large, there may be a way to make the split along a different axis, resulting in a more sane dependency graph.

Answered By: Carl Meyer

If you’re seeing circular model dependency I’m guessing that one of three things is happening:

  • You’ve defined an inverse relationship to one that’s already defined (for instance both course has many lectures and lecture has one course) which is redundant in django
  • You have a model method in the wrong app
  • You’re providing functionality in a model method that ought to be in a manager

Maybe you could show us what’s happening in these models and we can try to figure out why the problem is arising. Circular model dependency is rarely an indication that you need to combine two apps – it’s more likely (though not definitely the case) that there’s a problem with one of your model definitions.

p.s. I am working on a similar django application, but my app structure is probably quite different to your’s. I’d be happy to give you a high-level description of it if you’re interested.

Answered By: ozan

If your dependency is with foreign keys referencing models in other applications, you don’t need to import the other model. You can use a string in your ForeignKey definition:

class MyModel(models.Model):
    myfield = models.ForeignKey('myotherapp.MyOtherModel')

This way there’s no need to import MyOtherModel, so no circular reference. Django resolves the string internally, and it all works as expected.

Answered By: Daniel Roseman

I wrote the following a long time ago. Reading it now, it is not good advice for the OP’s question. They should probably just put their models into a single Django app. But the following is some general background on dependencies in code, and it mentions this "put them into one group" solution halfway through:

Ignoring the Django aspect to your question, the general technique for breaking circular dependencies is to break out one of the cross-referenced items into a new module. For example, the cycle:

moduleA: class1, class2
                       |        ^
                       v        |
moduleB: class3, class4

could become:

moduleB2:              class4
                                    |
                                    v
moduleA: class1, class2
                        |
                        v
moduleB1: class3

Or alternatively, you could split up the classes from moduleA:

moduleA1: class1
                       | 
                       v
moduleB: class3, class4
                                    |
                                    v
moduleA2:               class2

Or both:

moduleA1: class1
                       | 
                       v
moduleB1: class3

moduleB2:              class4
                                      |
                                      v
moduleA2               class2

Of course, this is no help if class A & B depend on each other:

moduleA: class1
                    |    ^
                    v    |
moduleB: class2

In that case, maybe they should be in the same module (I suspect this is the solution the OP should use. Put the various interrelated model classes into a single Django app):

moduleA: class1 <--->  class2

or maybe the classes can be broken down in some way. For example, perhaps a first step could be to break out the part of class1 that class2 needs, into a new class3, which both original classes depend upon:

moduleA: class1
                    |    |
                    |    v
moduleB:   |    class2
                    |     |
                    v    v
moduleC: class3(new!)

This has already broken the cycle. We might want to go one step further, and break out the part of class 2 that class 1 depends upon:

moduleA:       class1
                         |     |
                         |     |
moduleB:        |     |     class2
                         |     |      |    |
                         |     v     v   |
moduleC:       |    class3   |
                        |                   |
                        v                 v
moduleD:       class4(new!)

Sometimes this kind of breakdown is difficult. Getting the hang of it is certainly a skill that requires some effort to learn. If it doesn’t go well, the resulting pieces seem very ad-hoc, specific to your problem, and you are left with a feeling that you have made your algorithm harder to understand by breaking it into multiple pieces. But when this process goes well, the resulting pieces feel like useful new primitives, which one could imagine being used elsewhere (even if that doesn’t actually happen in your current project), and using them makes your core algorithm easier to understand.

(Remember in the general case, that the above "dependency" arrows could represent any kind of dependency between the classes, which might be inheritance, or composition, etc. One class method might simply call a static method on another class. And when we talk of "classes", all of this applies equally to other chunks of logic, such as functions which depend on (i.e. call) each other, or database tables that reference each other.

Answered By: Jonathan Hartley
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.