11

I've run into this problem a few times in different situations but my setup is the following:

I have two Django models files. One that contains User models and CouponCodes that a user can use to sign up for a Course. Those are both in the account/models.py file. Course and the related many-to-many field are in a different models file, course/models.py. I usually refer to these in my code as amod and cmod respectively.

In course/models.py I have an import statement:

from account import models as amod

class Course(ExtendedModel):
    stuff = stuff

I need to import the account/models.py file for the many-to-many model/table between Course and User which is not shown here. So far, so good.

In the account/models.py file I have the CouponCode model. Each instance gets created and then can be assigned to a particular Course object after creation to allow a student to use it to sign up for a course in the system.

class CouponCode(ExtendedModel):
    assigned_course = UniqueIDForeignKey("course.Course", blank=True, null=True, related_name='assigned_coupon_code_set')
    ...
    ...

    @staticmethod
    def assign_batch(c, upper_limit):
        import course.models as cmod # Why is this allowed here?
        assert isinstance(c, cmod.Course)

        # Do other stuff here

That static method allows me to pass in a course object and a number of CouponCodes that I want to assign to it and then it will assign the next N number of unassigned codes to that course. My question arises from the assert statement.

I need to import the Course object from course/models.py in order to insure that the object being passed in is actually an instance of Course, but if I do that at the top of the file, I get problems because this file is already being imported into course/models.py. (amod is being imported into cmod and then in amod I need to import cmod).

Why does it allow me to do this if I import it in the method right before I need it versus at the top of the file?

Spartacus
  • 305
  • 2
  • 11

1 Answers1

8

When a module is imported (well, the first time it's imported in a given process), all the top-level statements are executed (remember that import is an executable statement). So you cannot have module1 with an import module2 statement at the top-level and module2 with an import module1 at the top-level - it cannot obviously work.

Now if in module2 you move the import module1 statement within a function, this statement won't be executed before the function is actually called, so it won't prevent module1 from importing module2.

Note that this is still considered bad practice, most of the time a circular dependency means you should refactor your code to avoid the problem (extract the parts both modules depends on into a third module that depends from both but none of the others depends on, or simply merge the modules) - but some cases are complex due to other constraints so having this as a last-resort solution is fine.

Also, you do not need to import a model to reference it in a ForeignKey or Many2Many field - you can pass a "appname.ModelName" string, cf https://docs.djangoproject.com/en/1.8/ref/models/fields/#foreignkey

To refer to models defined in another application, you can explicitly specify a model with the full application label. For example, if the Manufacturer model above is defined in another application called production, you’d need to use:

class Car(models.Model):
    manufacturer = models.ForeignKey('production.Manufacturer')

This sort of reference can be useful when resolving circular import dependencies between two applications.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • In your experience, has there ever been a reason not use the string reference when creating a foreign key versus a reference to the Class itself? To me it seems like always doing that would be a good way to avoid problems. – Spartacus Nov 23 '15 at 20:56
  • 1
    @Spartacus late answer sorry - well, using a string reference forces the orm to do some extra lookups and eventually imports to resolve the reference so you have a slight penalty at process startup. Whether this is a problem or not is left to your appreciation... But you could turn the question the other way around: why use a string reference when you already have the class available ?-) – bruno desthuilliers Jun 20 '17 at 15:35