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')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With