I have a django app where I want MyModel instances to be saved using an Enum choice, which I process in the save() method, such as:
# app/models.py
from django.db import models
from app.utils import translate_choice
from enum import Enum
class MyEnum(Enum):
CHOICE_A = 'Choice A'
CHOICE_B = 'Choice B'
class MyModel(models.Model):
...
choice = models.CharField(
max_length=10,
choices=[(tag.name, tag.value) for tag in MyEnum],
)
...
def save(self, *args, **kwargs):
self.choice = translate_choice(self.choice)
super().save(*args, **kwargs)
Whereas on the app/utils.py I have:
from app.models import MyEnum
def translate_choice(value):
...
return MyEnum.CHOICE_A # For example
I keep getting ImportError: cannot import name 'MyEnum' errors on the app/utils.py file when running the application. Is this due to a python circular import error, or am I missing something else? When moving the translate_choice() method to app/models.py it stops happening, but I would like to use this same method in other modules and it is kind of weird having a transforming feature within the models when used in another app.
Thank you in advance
It's probably due to the circular import, as you've guessed yourself. You can try putting the import statement not at the top of the file, but inside the function that uses the imported object:
def translate_choice(value):
from app.models import MyEnum
...
return MyEnum.CHOICE_A # For example
This is, admittedly, not the most elegant solution. See also the answers to this post, where you can find other approaches.
Related
How can I set the default locale in Python's factory_boy for all of my Factories?
In docs says that one should set it with factory.Faker.override_default_locale but that does nothing to my fakers...
import factory
from app.models import Example
from custom_fakers import CustomFakers
# I use custom fakers, this indeed are added
factory.Faker.add_provider(CustomFakers)
# But not default locales
factory.Faker.override_default_locale('es_ES')
class ExampleFactory(factory.django.DjangoModelFactory):
class Meta:
model = Example
name = factory.Faker('first_name')
>>> from example import ExampleFactory
>>> e1 = ExampleFactory()
>>> e1.name
>>> u'Chad'
The Faker.override_default_locale() is a context manager, although it's not very clear from the docs.
As such, to change the default locale for a part of a test:
with factory.Faker.override_default_locale('es_ES'):
ExampleFactory()
For the whole test:
#factory.Faker.override_default_locale('es_ES')
def test_foo(self):
user = ExampleFactory()
For all the tests (Django):
# settings.py
TEST_RUNNER = 'myproject.testing.MyTestRunner'
# myproject/testing.py
import factory
from django.conf import settings
from django.util import translation
import django.test.runner
class MyTestRunner(django.test.runner.DiscoverRunner):
def run_tests(self, test_labels, extra_tests=None, **kwargs):
with factory.Faker.override_default_locale(translation.to_locale(settings.LANGUAGE_CODE)):
return super().run_tests(test_labels, extra_tests=extra_tests, **kwargs)
More on it here.
UPD As I said, this solution is suboptimal:
factory.Faker._DEFAULT_LOCALE is a private field
fake() and faker() use the private interface
fake() doesn't work since factory-boy==3.1.0
if I were to use faker, I'd use it directly, not via factory-boy
You should generally prefer the other answer. Leaving this one for posterity.
Not a good solution, but for now it's as good as it gets. You can change the variable that holds the value:
import factory
factory.Faker._DEFAULT_LOCALE = 'xx_XX'
Moreover, you can create a file like this (app/faker.py):
import factory
from faker.providers import BaseProvider
factory.Faker._DEFAULT_LOCALE = 'xx_XX'
def fake(name):
return factory.Faker(name).generate({})
def faker():
return factory.Faker._get_faker()
class MyProvider(BaseProvider):
def category_name(self):
return self.random_element(category_names)
...
factory.Faker.add_provider(MyProvider)
category_names = [...]
Then, once you import the file, the locale changes. Also, you get your providers and an easy way to use factory_boy's faker outside of the factories:
from app.faker import fake
print(fake('random_int'))
print(faker().random_int())
I'm having same issue as yours. For a temporary solution try passing locale in factory.Faker.
For example:
name = factory.Faker('first_name', locale='es_ES')
With Django, you can simply insert the following lines in <myproject>/settings.py:
import factory
factory.Faker._DEFAULT_LOCALE = 'fr_FR'
Further to #xelnor's answer, if using pytest (instead of Django manage.py test), add a hookwrapper on the pytest_runtestloop hook in your conftest.py to set the default locale for all the tests:
#pytest.hookimpl(hookwrapper=True)
def pytest_runtestloop(session):
with factory.Faker.override_default_locale(translation.to_locale(settings.LANGUAGE_CODE)):
outcome = yield
I was under the understanding that using the syntax:
from foo import y
will import the class y from foo.py.
If this is true how come when I use the following:
models.py
from django.db import models
from .utils import codeGenerator, createShortcode
class KirrUrlManager(models.Manager):
def all(self, *args, **kwargs):
qs_main = super(KirrUrlManager, self).all(*args, **kwargs)
qs = qs_main.filter(active=True)
return qs
def refreshShortCodes(self):
qs = KirrUrl.objects.filter(id__gte=1) #id_gte is primary key
newCodes = 0
for q in qs:
q.shortcode = createShortcode(q)
q.save()
newCodes +=1
return "new codes made: {i} ".format(i=newCodes)
class KirrUrl(models.Model):
url = models.CharField(max_length=220, unique=True)
shortcode = models.CharField(max_length=50, unique=True, blank = True)
timestamp = models.DateTimeField(auto_now=True,)
timestamp = models.DateTimeField(auto_now_add=True,)
active = models.BooleanField(default=True,)
objects = KirrUrlManager()
def save(self, *args, **kwargs):
if self.shortcode is None or self.shortcode =="":
self.shortcode = createShortcode(self)
super(KirrUrl, self).save(*args, **kwargs)
def __str__(self):
return str(self.url)
foo.py
from django.core.management.base import BaseCommand, CommandError
from shortener.models import KirrUrl
class Command(BaseCommand):
help = "refreshes all shortcodes"
def handle(self, *args, **options):
return KirrUrl.objects.refreshShortCodes()
I am unsure why I am able to call the method "refreshShortCodes()" in foo.py. I am only using the import statement "from shortener.models import KirrUrl". Shouldnt this import statement only let me import the KirrUrl class? refreshShortCodes() is not part of the KirrUrl class, however it is the models.py file that is being imported.
Shouldnt this import statement only let me import the KirrUrl class?
Yes, and that's exactly what the import statement is doing. objects.refreshShortCodes() is part of the KirrUrl class. So you have access to the class name, as well as its attributes.
When Python imports a class, it imports the entire class object. That means all of variables and methods defined in the classes namespace can be reached. So Since you created an instance of KirrUrlManager() inside of the KirrUrl class, you can access the refreshShortCodes() method by first getting the KirrUrlManager() instance:
KirrUrl.objects
And then getting the refreshShortCodes() method from the KirrUrlManager() instance:
KirrUrl.objects.refreshShortCodes()
You only need to import the class to have access to its attributes; you can't import an object encapsulated in a class.
Therefore, after importing the class KirrUrl, the model manager objects is accessible via the class, and the method refreshShortCodes is equally accessible via the model manager instance which is composed in the class KirrUrl.
This is one of the ways objects that are not reachable by the import mechanism are accessed; dot referencing.
I am reading many doc, blog, and SE questions about this topic but I cant get it right. I have a circular import between my models.py and tms.py in a same module named maps
models.py
from maps.tms import tileMapConfig
class LayerMapOptions(models.Model):
layer = models.ForeignKey(Shapefile)
basqui_map = models.ForeignKey(BasquiMap)
position = models.IntegerField(max_length=100, blank=True, null=True)
visible = models.BooleanField(default=False)
styles = models.ManyToManyField(LayerStyle, null=True, blank=True)
def save(self, *args, **kwargs):
super(LayerMapOptions, self).save(*args, **kwargs)
self.basqui_map.date_updated = datetime.now()
self.basqui_map.save()
tileMapConfig(self.basqui_map.pk)
tms.py:
from maps.models import LayerMapOptions
result = LayerMapOptions.objects.filter(basqui_map__pk=map_id)
def tileMapConfig(map_id):
...
This result in the following error:
from maps.models import LayerMapOptions ImportError: cannot import name LayerMapOptions
What would be the way to import those submodules without conflict?
Simply put your import into the function that uses it:
def tileMapConfig(map_id):
from maps.models import LayerMapOptions
...
Or
def save(self, *args, **kwargs):
from maps.tms import tileMapConfig
...
Modules are only imported once, so each time it re-enters the function, it will not reimport the whole module, it will only fetch it from the already imported modules, so there is (almost) no performance issue.
One way is to use the full namespace.
import maps.tms
maps.tmstileMapConfig(self.basqui_map.pk)
The other issue is that you have some global code running during the import:
result = LayerMapOptions.objects.filter(basqui_map__pk=map_id)
When importing a module, the code in the module gets executed, so if you can move this line into a function or method, that will delay the execution of that line until after both modules have fully imported.
Here's a better explanation:
http://effbot.org/zone/import-confusion.htm
I'm having issues getting a class method to run in Flask.
In models/User.py:
from mongoengine import *
class User(Document):
first_name = StringField()
last_name = StringField()
...
def __init__(self, arg1, arg2, ...):
self.first_name = arg1
self.last_name = arg2
...
#classmethod
def create(self, arg1, arg2, ...):
#do some things like salting and hashing passwords...
user = self(arg1, arg2, ...)
user.save()
return user
In the main application python file:
from models import User
...
def func():
...
#Throws "AttributeError: type object 'User' has no attribute 'create'"
user = User.create(arg1, arg2, ...)
Shouldn't I be able to call create on the User class without instantiating a User object? I'm using Python 2.7.2, and I also tried the non-decorator syntax of using create = classmethod(create), but that didn't work. Thanks in advance!
EDIT: I found one issue: that the models folder did not contain an __init__.py file, so it wasn't a module, so from models import User was not actually importing the file I wanted it to. It did not give me an error from before because I used to have a models.py module in the same directory as the application python script, but after deleting it I never deleted the corresponding .pyc file. Now, I'm getting the error AttributeError: 'module' object has no attribute 'create' instead of what I had before, but I'm certain it is importing the correct file now.
EDIT2: Solved. I then changed the import to from models.User import User and It's hitting the method now.
The issue was twofold:
The User.py file was in the models/ folder, meaning that my import was actually looking for the User class in the models.py file, which no longer existed but still was being imported without error because the models.pyc file was still around
The import was incorrect for importing within a directory. it should have been from models.User import User, so long as the models/ folder is a module, so all I needed to do then was touch models/__init__.py.
>>> class foo(object):
... def __init__(self):
... pass
... #classmethod
... def classmethod(cls):
... return 0
...
>>> a = foo()
>>> a.classmethod()
0
>>>
I have a Django app with custom form fields, some of which have slow operations in their constructors. I was surprised recently to find out that those constructors were getting called when Django itself was starting up, even before a user does something that requires that form in a view.
Why are they getting instantiated at server start?
Example:
urls.py:
from myapp.views import view1
...
url(r'^test$', view1.test),
views/view1.py:
class MyForm(ModelForm):
class Meta:
model = MyModel
field1 = MyChoiceField()
class MyChoiceField(ChoiceField):
def __init__(self, choices=(), required=True, widget=None, label=None,
initial=None, help_text=None, *args, **kwargs):
super(ChoiceField, self).__init__(required, widget, label, initial,
help_text, *args, **kwargs)
self.choices = [(m.id, m.name) for m in ReallyLargeTableModel.objects.all()]
If I set a break point inside that field constructor, then start up Django, it breaks the first time I request any page, even if the view in question does not need that form or field. The stacktrace leads back to the import line in urls.py.
Is this because I'm importing view1 in urls.py, instead of importing view1.test?
Edit: This isn't Django specific, here is a test case the illustrates the behavior:
class Something():
def __init__(self):
print "Something __init__() called"
class UsesSomething():
field = Something()
If you run this in the interactive terminal, it will print "Something init() called". This was surprising to me because I have not actually instantiated a UsesSomething object.
Because you instantiate the fields in the form definition, which is presumably being imported by one of your views.
The field init is the wrong place to do this sort of dynamic initialization, for this exact reason. You want something that is called when the form is initialized: ie, the form's __init__.
That said, you don't actually want to do this at all - you just need to use forms.ModelChoiceField, which takes a queryset and does the dynamic assignment of choices for you.
class MyForm(ModelForm):
field1 = forms.ModelChoiceField(queryset=ReallyLargeTableModel.objects.all())
In your example:
class UsesSomething():
field = Something()
The line of code field = Something() will execute when you import the containing module as Python processes the class definition. This is just how Python works. You can actually put arbitrary code inside a class definition.
module: test.py:
class UsesSomething():
print "wow!"
>>> import test
wow!