views:

94

answers:

3

For whatever reason, when I was new to Python and Django, I wrote some import statements like this at the top of a models.py file:

from django.contrib import auth

And I'd use it like this:

class MyModel(models.Model):
    user = models.ForeignKey(auth.models.User)
    # ...

This worked fine. A long time later, I wrote a custom management command, and it would do this:

from myapp.models import MyModel

When I ran my custom command (python manage.py my_command) this would result in Python complaining that the module auth had no attribute models on the line declaring the ForeignKey in models.py.

To work around this problem, I changed my models.py to the more usual:

from django.contrib.auth.models import User

class MyModel(models.Model):
    user = models.ForeignKey(User)
    # ...

Can someone explain to me what I am missing? Is there something different in the environment when you run a management command? Or was I just doing it wrong the whole time? Thanks!

Edit: Following dmitko's hunch about circular imports, here are the imports used in my models.py file. I'm showing the original import of auth commented out, along with the only model that has a foreign key to the auth user model:

import datetime  
from django.db import models 
# from django.contrib import auth
from django.contrib.auth.models import User 

class UserLastVisit(models.Model):
    # user = models.ForeignKey(auth.models.User, unique=True)
    #                          ^^^^^^^^^^^^^^^^
    # after adding mgmt command, error occurred here; change to the line below
    user = models.ForeignKey(User, unique=True)
    last_visit = models.DateTimeField(db_index=True)

And here are the imports of the management command that uncovered the problem:

import datetime   
from django.core.management.base import NoArgsCommand 
from core.models import UserLastVisit, AnonLastVisit, Statistic

Was this setting up a circular import type situation?

A: 

I guess that if you do from django.contrib import auth that means you're importing auth package as a module and what it exports is driven by __init__.py in the auth folder:

>>> from django.contrib import auth
>>> dir(auth)
['BACKEND_SESSION_KEY', 'ImproperlyConfigured', 'REDIRECT_FIELD_NAME', 'SESSION_
KEY', '__builtins__', '__doc__', '__file__', '__name__', '__path__', 'authentica
te', 'datetime', 'get_backends', 'get_user', 'import_module', 'load_backend', 'l
ogin', 'logout']

You can check __init__.py in django\contrib\auth and see the same function list. When you import from django.contrib.auth.models import User that means that you're importing a submodule from the auth package and it works.

BTW. I was unable to use auth.models.User in any case - whether I run from console or from my django app.

dmitko
Hmmm. Thanks for the reply, I will study this some more. I wish I could explain why using `auth.models.User` worked for me before I wrote the management command, though.
Brian Neal
A: 

It's hard to say exactly what's going on without seeing the new manage.py command that you added. However, I often see the " has no attribute " in cases with circular imports, and it's almost always fixed by changing the module-level imports to function- or class-level imports, as you did here. You might check if anything like that is going on here.

gmarcotte
I have edited my question, showing what I hope is the relevant parts of the code. I don't think there was a circular import going on, but do you see anything suspicious?
Brian Neal
+1  A: 

If some random module ever imports module x.y.z, then a later person who imports just x.y will see a z in the x.y namespace.

The reason this happens is that import x.y.z is actually three import statements in one. It works something like this:

x = __internal_import('x')
x.y = __internal_import('x/y')
x.y.z = __internal_import('x/y/z')

Next time someone does __internal_import('x/y'), they'll get the same object, because python is smart enough not to import the same one twice. That object already has its z member assigned to the z module.

In your full app, probably you had a module that did import django.contrib.auth.models. But your minimal standalone program didn't import that module, so the name was never assigned.

(Note: there's no such thing as __internal_import. It's just an illustration. The real function has some other name that you would have to look up.)

apenwarr
Awesome, thank you for the explanation.
Brian Neal