Visibility of class definition - python

This may be a simple Python question, however after searching for "imported name not defined" and similar criteria, I did not find an answer to this.
I have a models.py, as prescribed by django, with class definitions for all models.
I then have a loadtestdata.py which is simply a programmatic way to populate the database with some initial entries.
Within loadtestdata.py, I import all the models using "from myproject.models import *".
This seems to work as 'global code' within loadtestdata.py is able to see all the class names imported from models.py. It can populate the database just fine.
However if I define a function inside loadtestdata.py, suddenly that function is unable to see any of the imported class names.
For example, this function would not see ImportedModelName:
def AddSomeEntries(list1):
for value in list1:
new_model = ImportedModelName()
# set some stuff on the model
new_model.save()
AddSomeEntries([1,2,3,4,5])
However, I could write this and it sees ImportedModelName just fine
new_model = ImportedModelName()
# set some stuff on the model
new_model.save()
Why does the former example not see the imported class, but the latter example can?
EDIT:
I cut down the loadtestdata.py to its bare minimum and this is it
from django.contrib.auth.models import User
User.objects.filter(email='someemail#here.com').delete()
User.objects.create_superuser('someuser', 'someuser#here.com', 'somepassword')
from django.db import models
from myproject.models import *
from datetime import datetime
def AddTestModel(list_data):
for entry in list_data:
test_model = TestModel()
test_model.name = entry
test_model.save()
AddTestModel([1,2,3])
In this file it fails because NameError: name 'TestModel' is not defined.
However if I replace the function AddTestModel with this:
for entry in [1,2,3]:
test_model = TestModel()
test_model.name = entry
test_model.save()
It works just fine. Note: the above, aside from changing the database login particulars, is now exactly what is in my loadtestdata.py.
The models.py begins with this (it has many other model definitions which I can't strip out because django will complain, but they are all simple definitions which certainly cannot clash with a 'TestModel' name):
from django.db import models
class TestModel(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name

In working through explaining this post, I had a random idea which (after some google searching) resulted in this:
When I execute the script I am doing so with this:
python manage.py shell < loadtestdata.py
I don't know how that is functionally different to this:
python manage.py shell
>>> exec(open('loadtestdata.py').read())
But the latter works (being that functions I define in loadtestdata.py are able to see the imported class names) whereas the former produces the 'name is not defined' error.

Related

Django cyclic imports and typechecking with mypy

I'm trying to create a simple custom manager with one of my Django models. It causes a cyclic import since I'm trying to import the manager from models.py and the model from managers.py. However, because my manager is creating the model and adding some extra attributes, the type hint for the method is the model instance. I'm having trouble with fixing that type hint since it's not yet imported.
# models.py
from .managers import PublishedBundleManager
class PublishedBundle(models.Model):
data = JSONField()
md5_checksum = models.CharField(max_length=128)
objects = PublishedBundleManager()
The manager has a method to help me create the model instance, but as a convenience, calculates a checksum to fill in during creation. To fix the cyclic imports, I made use of typing.TYPE_CHECKING
# managers.py
import typing as t
from django.apps import apps
from django.db import models
if t.TYPE_CHECKING:
PublishedBundle = apps.get_model(app_label="the_hugh", model_name="PublishedBundle")
class PublishedBundleManager(models.Manager): # Error 1
def create_publish(self, data: t.Dict, description: str) -> PublishedBundle: # Error 2
PublishedBundle = apps.get_model(app_label="my_app", model_name="PublishedBundle")
json_data = json.dumps(data, sort_keys=True)
md5_checksum = hashlib.md5(json_data.encode("utf-8")).hexdigest()
return PublishedBundle.objects.create(data=data, md5_checksum=md5_checksum)
However, I'm getting 2 errors.
Missing type parameters for generic type "Manager" [type-arg]mypy(error)
name 'PublishedBundle' is not defined
I'm fairly new with typed python and never faced this issue before. I understand that 2 is happening because PublishedBundle hasn't yet been defined, but I can't define it because it causes a cyclic import. Can anyone help me out please?
One simple way to work around this is to have the manager and the model in the same file. They are intricately related anyway.

Importing from views to models in Django

I have something like this in one of my apps's models.py :
class Account(AbstractBaseUser):
email = models.EmailField(unique=True)
I want to import a class from another app's views.py as below:
from anotherapp.views import MyClass
The problem is in the first lines of anotherapp.views file, I imported the Account class. So when I want to import MyClass into my models.py file, this error raises :
ImportError: cannot import name Account
That is circular import error you are encountering. While it is bad practice to import from views to models in Django, if you still want to, you can follow methods in this question to resolve it.
Here are few alternative ways that can be considered as good practice:
You can consider importing other low-level modules exist within anotherapp instead of MyClass (not depending on views.py)
You can use Django's signals to catch anotherapp's events project wide and act according to.
You can create a third file, say utils.py in anotherapp, move MyClass there and let anotherapp/views.py and your app's models.py import from anotherapp.utils

Python recursive import issue

I have a Python/Flask app that uses MongoEngine for the database. I have defined my models, and everything was working until the newest models were added. I believe the problem occurs because both models reference each other and it's causing a recursive import loop. I'm not sure what to do to solve it though. This is going to be a large project with lots of models referencing each other. This particular instance is because users are in practices, and practices have users, so it's a many to many relationship.
User Model
from utilities.common import utc_now_ts as now
from mongoengine import *
from models.practice import Practice
class User(Document):
name = StringField()
created = IntField(db_field="cr", default=now)
practices = ListField(ReferenceField(Practice))
And the practice model
from utilities.common import utc_now_ts as now
from mongoengine import *
from models import user
class Practice(Document):
name = StringField()
created = IntField(db_field="cr", default=now)
users = ListField(ReferenceField(user.User))
admins = ListField(ReferenceField(user.User))
The error I get is ImportError: cannot import name 'Practice'
I have two other models that are running into the same issue. The models worked fine until I added in the imports to the other model.
I am by no means an expert on MongoEngine, but it looks like you can reference a model by string as opposed to by class. In that case you can change your Practice model to look like this.
from utilities.common import utc_now_ts as now
from mongoengine import *
class Practice(Document):
name = StringField()
created = IntField(db_field="cr", default=now)
users = ListField(ReferenceField('User'))
admins = ListField(ReferenceField('User'))
I hope this helps.
The short answer is that you can't have circular references. The compiler doesn't trust that you will properly "bottom out" on resolving references, and it's not going to iterate through the graph to find out.
One way to fix this is to use a master class that imports the various classes; your worker classes then import their needs from the master class.

Python cannot import name

I can't seem to figure out how to import two classes to each other. When running the application it simply says
from room import Room
ImportError: cannot import name Room
It might be a design problem but I don't think there's another way to reference the two classes, so this needs to be as it is. The only reason the imports are needed is because they are required by the redisco module in the objects (they need to know the types)
#Room class.
class Room(models.Model):
from player import Player
players = models.ListField(Player, required = True)
#Player class
class Player(models.Model):
from room import Room
room = models.ReferenceField(Room, required = True)
How do I get this to work?
E:
The framework is Redisco (Redis)
Most ORM models either support back references (where the target model of a reference field is given an extra attribute that points back to the referencing object), and / or lets you specify relationships through other means.
Redisco doesn't have back-references that I could discover, but it does support string references. If you pass in a string it'll be interpreted as a model name, matched against the __name__ attribute:
class Room(models.Model):
players = models.ListField('Player', required = True)
This neatly bypasses the import problem altogether.
From the ListField docstring:
target_type -- can be a Python object or a redisco model class.
If target_type is not a redisco model class, the target_type should
also a callable that casts the (string) value of a list element into
target_type. E.g. str, unicode, int, float.
ListField also accepts a string that refers to a redisco model.
The code to resolve the name uses the function get_model_from_key() to resolve the string, which simply searches through all subclasses of models.Model, matching on __name__.
It'll resolve the name when validating new values or when retrieving existing values for the first time, by which time the Player subclass has already been imported.
If they are in the same .py file then you don't need imports. If they are in different files in the same directory just use 'import room' (assuming its in a file called 'room.py'). If they are in separate files in separate directories you will need to use the imp module.
import imp
roomClass = imp.load_source('roomClass', [Path to room.py])
Then it can be called with something like:
aRoom = roomClass.Room(Model)
First of all if you want to import a module to your program, they must be in the same directory.
Follow this for importing modules ;
from filename import classname
Which is the filename= your .py file that you want to import class
class is the specific class that you want to use.
Or;
from filename import *
Which is going to add all functions and classes.
Also check this topic. program is running fine but cannot being import with IndexError
You can reference to this problem.
Pythonic way to resolve circular import statements?
Using import rather than from [...] import [...]

Django: Refactoring models into sub-modules

After the models.py in one app grew quite large, I've tried to move some of the classes into subpackages.
The old structure was something like this:
# File: assets/models.py
class Asset(...):
# lots of irrelevant code
# File: widgets/models.py
from assets.models import Asset
class Video(...):
asset = models.ForeignKey(Asset)
This worked without a problem, so I'm not going into further details about the structure.
What I've tried to do now is move the Asset class into a submodule. The structure is now as follows:
# File: assets/models/__init__.py (of course I deleted the old models.py)
from .assets import Asset
# File: assets/models/assets.py
class Asset(...):
# lots of irrelevant code
# File: widgets/models.py
from assets.models.assets import Asset
class Video(...):
asset = models.ForeignKey(Asset)
Somehow this doesn't work, and I can't figure out what actually causes the trouble. The error I'm getting is this:
widgets.video: 'asset' has a relation with model , which has either not been installed or
is abstract
It appears that Django can't reliably detect which app a model belongs to if it's in a nested submodule (ie. not directly inside APPNAME.models).
This is a known problem and can be solved by adding the following lines (in this case to the Asset class), thus defining explicitly which app a model belongs to:
class Asset(models.Model):
...
class Meta:
app_label = 'assets'
...
References:
https://groups.google.com/forum/#!topic/django-users/MmaiKvbDlDc
https://code.djangoproject.com/ticket/14007
You should import from models as before:
from assets.models import Asset
This allows you to always import from models but organise the models separately within the models directory. It also means, conceptually, that Asset is still in models as your ForeignKey refers to a assets.models.Asset object, not assets.models.assets.Asset.

Categories