Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?
like image 422
Tom Avatar asked Sep 28 '15 17:09

Tom


3 Answers

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
like image 96
djangonaut Avatar answered Nov 18 '22 01:11

djangonaut


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.

like image 11
mathias.lantean Avatar answered Nov 18 '22 02:11

mathias.lantean


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')
like image 2
Steven B Avatar answered Nov 18 '22 02:11

Steven B