Factory Boy Circular Import not working as desired - python

I have 2 models, Account and AccountUser. An Account has many AccountUsers. I'm trying to create this using Factory Boy's circular imports but have been unsuccessful so far with various changes. There's not much on SO about factory boy, and not enough on Google to help me. Can anyone help me make this work without errors?
I put a pdb at the bottom of stub_account.py and do acct = AccountFactory() but I get *** NameError: name 'AccountFactory' is not defined. If I do acct = AccountFactory().build() or .create() I get the same error. If I define those variables inside the script instead of while in pdb I get the same error.
project_root/app/tests/scripts/stubs/stub_account.py
from app.models import Account
from faker import Faker
import factory
fake = Faker()
class AccountFactory(factory.Factory):
class Meta:
model = Account
billing_contact = factory.SubFactory(
"app.tests.scripts.stubs.stub_account_user.AccountUserFactory")
project_root/app/tests/scripts/stubs/stub_account_user.py
from app.models import AccountUser
from faker import Faker
import factory
fake = Faker()
class AccountUserFactory(factory.Factory):
class Meta:
model = AccountUser
_parent = factory.SubFactory(AccountFactory)

For reverse foreign key you have to use factory.RelatedFactory

Related

Using Factory Boy with GeoDjango PointFields

I'm working on writing tests for a new GeoDjango project I've started. Normally I used Factory Boy and Faker to create model instances for testing. However it's not clear to me how you can mock GeoDjango PointField fields. When looking at the record in Spacialite it appears as a binary blob.
I'm totally new to GIS stuff, and a little confused as to how I can create factories for PointFields in Django.
# models.py
from django.contrib.gis.db import models
class Place(models.Model):
name = models.CharField(max_length=255)
location = models.PointField(blank=True, null=True)
objects = models.GeoManager()
def __str__(self):
return "%s" % self.name
# factories.py
import factory
from faker import Factory as FakerFactory
from . import models
faker = FakerFactory.create()
class PlaceFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Place
name = factory.LazyAttribute(lambda x: faker.name())
#location = What do I do?
I believe you need to create a custom fuzzy attribute for point instances. Can you try this? Right now I don't have the setup to run it all through.
import random
from django.contrib.gis.geos import Point
from factory.fuzzy import BaseFuzzyAttribute
class FuzzyPoint(BaseFuzzyAttribute):
def fuzz(self):
return Point(random.uniform(-180.0, 180.0),
random.uniform(-90.0, 90.0))
class PlaceFactory(FakerFactory):
name = factory.LazyAttribute(lambda x: faker.name())
location = FuzzyPoint()
class Meta:
model = models.Place
Fussy is about to be deprecated as Factory Boy documentation says.
Now that FactoryBoy includes the factory.Faker class, most of these built-in fuzzers are deprecated in favor of their Faker equivalents.
As #Steven B said, you must create your own provider. I did some changes to his code in order to make the provider as generic as possible.
class DjangoGeoPointProvider(BaseProvider):
def geo_point(self, **kwargs):
kwargs['coords_only'] = True
# # generate() is not working in later Faker versions
# faker = factory.Faker('local_latlng', **kwargs)
# coords = faker.generate()
faker = factory.faker.faker.Faker()
coords = faker.local_latlng(**kwargs)
return Point(x=float(coords[1]), y=float(coords[0]), srid=4326)
Note: coords_only must be always true because we just need lat and long values, without any extra metadata.
Note 2: generate() is deprecated, see related answer.
Finally, it is like using the local_latlng provider or any of the built-in providers. Here is a full example:
class TargetFactory(factory.django.DjangoModelFactory):
factory.Faker.add_provider(DjangoGeoPointProvider)
class Meta:
model = Target
radius = factory.Faker('random_int', min=4500, max=90000)
location = factory.Faker('geo_point', country_code='US')
Note: 'US' is the default country code, it could be omitted in this example, but you could use any of the other specified countries code in Faker doc.
Faker already has some nice geo features. You can create you own provider to make it work for Django, for example:
import factory
from faker.providers import BaseProvider
from django.contrib.gis.geos import Point
class DjangoGeoLocationProvider(BaseProvider):
countries = ['NL', 'DE', 'FR', 'BE']
# uses faker.providers.geo
def geolocation(self, country=None):
country_code = country or factory.Faker('random_element', elements=self.countries).generate()
faker = factory.Faker('local_latlng', country_code=country_code, coords_only=True)
coords = faker.generate()
return Point(x=float(coords[1]), y=float(coords[0]), srid=4326)
Which will generate Django compatible Points for the given countries.
After registering it:
factory.Faker.add_provider(DjangoGeoLocationProvider)
You can use it in your DjangoModelFactory like any of the built-in providers:
from factory.django import DjangoModelFactory as Factory
class PlaceFactory(Factory):
class Meta:
model = Place
location = factory.Faker('geolocation')
# or
another_location = factory.Faker('geolocation', country='NL')
You can also set the value directly.
import json
from factory import LazyAttribute
from factory.django import DjangoModelFactory
class PlaceFactory(DjangoModelFactory):
static_location = "{'type': 'Point', 'coordinates': [1,1]}"
random_location = LazyAttribute(lambda x: json.dumps({
'type': 'Point',
'coordinates': [random.uniform(-180.0, 180.0), random.uniform(-180.0, 180.0)]}
))

How to import a class from ndb.Model?

I'm working with Google App Engine and I want to use my ndb model in another .py file but I couldn't import it.
Here is my main.py;
from google.appengine.ext import ndb
class User(ndb.Model):
username = ndb.StringProperty()
created_date = ndb.DateTimeProperty(auto_now=True)
follower_list = ndb.StringProperty(repeated=True)
And this is some code from my cron.py file:
from google.appengine.ext import ndb
save_user = User.query().filter(User.username == username)
But I'm getting:
ImportError: No module named User
How can I import the User class?
When you create the model you're just instantiating a class and assigning it to the variable named User. In python those variables are bound to the module they were declared in, and there are no implicit globals, so if you want to use it in another module you would need to import it:
from google.appengine.ext import ndb
import main
save_user = main.User.query().filter(main.User.username == username)
However the best practice would be to create the models in a models.py file, and import that anytime you need them.
BTW, your error hints that you're trying to import User earlier in your cron file, is that so? Either way I think you should get the idea now :)

How to Lazy Load a model in a managers to stop circular imports?

In Django you can create managers for your models. I do this by added a new file called managers.py and in my model objects = MyManager().
To stop circular imports I do self.model. However, if I need to reference a different model in my manager i.e.
from models import SecondModel
second= SecondModel(name=test).save()
self.model(second=second)
I get the following error: ImportError: cannot import name SecondModel
So is there a way in Django to lazy load a model?
The currently accepted answer is deprecated as of Django 1.7; from this answer, you can adapt your code like this.
from django.apps import apps
class SomeModelManager(...):
...
def some_function(self):
model = apps.get_model(app_label='your_app', model_name='YourModel')
You have a few options:
1. Import by name
Django has a utility function for importing by string name so you don't need to import yourself. There are several methods available for this (see this question: Django: Get model from string?)
from django.db.models.loading import get_model
class SomeModelManager(...):
...
def some_function(self):
model = get_model('your_app', 'YourModel')
object = model()
2. Imports at the bottom
Add the import at the bottom of the managers.py file and make sure to simply import the module and not the models themselves.
So...
models.py:
import managers
class SomeModel(models.Model):
...
objects = managers.SomeModelManager()
managers.py
class SomeModelManager(...):
...
def some_function(self):
object = models.SomeOtherModel()
import models

Issues with facebook profile model

I am using facebook_connect with my app.
I want to import user profile model to my app.....But it gives me an error of
from facebook_connect.models import FacebookProfileModel
ImportError: cannot import name FacebookProfileModel
my model.py is as:-
from facebook_connect.models import FacebookProfileModel
class MyCustomProfile(FacebookProfileModel):
user = models.OneToOneField('auth.User')
I am using this functionality first time and I am using this doc for reference. so any body can help me where I am wrong or I have to add something in my model.
Thanks
I'm pretty sure your import statement is incorrect, and should be django_facebook not facebook_connect.
From the docs:
from django_facebook.models import FacebookProfileModel
class MyCustomProfile(FacebookProfileModel):
user = models.OneToOneField('auth.User')
....

Python import problem with Django management commands

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?
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.)
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.
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.

Categories