djangopostgisfakergeosfactories

How Can I Fake A MultiPolygon Field?


I am creating a Django factory for a model that contains a MultiPolygonField. It is throwing an error when I run the test. Detail below.

I have created a special provider to fake this field. The code is taken from the Django docs:

from django.contrib.gis.geos import (
    Polygon,
    MultiPolygon,
)
import factory
from faker import Faker
from faker.providers import BaseProvider

fake = Faker()


class Provider(BaseProvider):
    def mpoly(self):
        p1 = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) )
        p2 = Polygon( ((1, 1), (1, 2), (2, 2), (1, 1)) )
        mpoly = MultiPolygon(p1, p2)
        return mpoly

fake.add_provider(Provider)


class GeographyFactory(factory.DjangoModelFactory):
    """
    A Factory to generate mock GeographyFactory objects to be used
    in tests.
    """

    class Meta:
        model = 'location.Geography'

    name = factory.Faker('name')
    mpoly = fake.mpoly

The error I get when I run the tests, however, has stumped me.

TypeError: Cannot set Geography SpatialProxy (MULTIPOLYGON) with value of type: <class 'method'>

It seems to suggest that I am not returning the right type, but I can't figure out what it wants instead of the MultiPolygon object I am returning. Why does it think I am returning <class 'method'>?

Any suggestions would be most welcome!


Solution

  • I would suggest defining a custom fuzzy attribute, which would allow some randomness in your tests.

    import factory
    import factory.fuzzy
    from factory import random
    
    
    class FuzzyPolygon(factory.fuzzy.BaseFuzzyAttribute):
        """Yields random polygon"""
        def __init__(self, length=None, **kwargs):
            if length is None:
                length = random.randgen.randrange(3, 20, 1)
            if length < 3:
                raise Exception("Polygon needs to be 3 or greater in length.")
            self.length = length
            super().__init__(**kwargs)
    
        def get_random_coords(self):
            return (
                factory.Faker("latitude").generate({}),
                factory.Faker("longitude").generate({}),
            )
    
        def fuzz(self):
            prefix = suffix = self.get_random_coords()
            coords = [self.get_random_coords() for __ in range(self.length - 1)]
            return Polygon([prefix] + coords + [suffix])
    
    
    class FuzzyMultiPolygon(factory.fuzzy.BaseFuzzyAttribute):
        """Yields random multipolygon"""
        def __init__(self, length=None, **kwargs):
            if length is None:
                length = random.randgen.randrange(2, 20, 1)
            if length < 2:
                raise Exception("MultiPolygon needs to be 2 or greater in length.")
            self.length = length
            super().__init__(**kwargs)
    
        def fuzz(self):
            polygons = [FuzzyPolygon().fuzz() for __ in range(self.length)]
            return MultiPolygon(*polygons)
    

    Then you can use these in your DjangoModelfactory;

    class GeographyFactory(factory.DjangoModelFactory):
        """
        A Factory to generate mock GeographyFactory objects to be used
        in tests.
        """
    
        class Meta:
            model = 'location.Geography'
    
        name = factory.Faker('name')
        mpoly = FuzzyMultiPolygon()