klen / mixer

Mixer -- Is a fixtures replacement. Supported Django, Flask, SqlAlchemy and custom python objects.
Other
939 stars 96 forks source link

Django mixer.blend fails to populate model that has (custom) SearchVectorField #126

Closed osvill closed 4 years ago

osvill commented 4 years ago

Hi,

I'm encountering fails with models that have (custom) SearchVectorField model fields.

Example models:

# models.py
from django.db import models

from .fields import PostgresSearchVectorConfigField, PostgresSearchVectorField

class Article(models.Model):
    content = models.TextField(null=True, blank=True)
    search_config = PostgresSearchVectorConfigField()
    search_content = PostgresSearchVectorField(config_column_name='search_config', input_columns=['content'], null=True)
# fields.py

from django.contrib.postgres.search import SearchVectorField
from django.db import models

class PostgresSearchVectorField(SearchVectorField):

    def __init__(self, config_column_name: str, input_columns: List[str], *args, **kwargs):
        self.config_column_name = config_column_name
        self.input_columns = input_columns
        super().__init__(*args, **kwargs)

    def db_type(self, connection):
        _str = " || ' ' || ".join([f"coalesce({col}, '')" for col in self.input_columns])
        return f'tsvector GENERATED ALWAYS AS (to_tsvector({self.config_column_name}, {_str})) STORED'

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        kwargs['config_column_name'] = self.config_column_name
        kwargs['input_columns'] = self.input_columns
        return name, path, args, kwargs

class PostgresSearchVectorConfigField(models.CharField):
    DANISH = 'danish'
    DUTCH = 'dutch'
    ENGLISH = 'english'
    FINNISH = 'finnish'
    FRENCH = 'french'
    GERMAN = 'german'
    HUNGARIAN = 'hungarian'
    ITALIAN = 'italian'
    NORWEGIAN = 'norwegian'
    PORTUGUESE = 'portuguese'
    ROMANIAN = 'romanian'
    RUSSIAN = 'russian'
    SIMPLE = 'simple'
    SPANISH = 'spanish'
    SWEDISH = 'swedish'
    TURKISH = 'turkish'

    SEARCH_CONFIG_CHOICES = (
        (DANISH, 'Danish'), (DUTCH, 'Dutch'), (ENGLISH, 'English'), (FINNISH, 'Finnish'), (FRENCH, 'French'),
        (GERMAN, 'German'), (HUNGARIAN, 'Hungarian'), (ITALIAN, 'Italian'), (NORWEGIAN, 'Norwegian'),
        (PORTUGUESE, 'Portuguese'), (ROMANIAN, 'Romanian'), (RUSSIAN, 'Russian'), (SIMPLE, 'simple'),
        (SPANISH, 'Spanish'), (SWEDISH, 'Swedish'), (TURKISH, 'Turkish'))

    def __init__(self, default: str = 'simple',  *args, **kwargs):
        kwargs['choices'] = self.SEARCH_CONFIG_CHOICES
        kwargs['default'] = default
        kwargs['max_length'] = 25
        kwargs['null'] = False
        kwargs['blank'] = False
        super().__init__(*args, **kwargs)

    def db_type(self, connection):
        return 'regconfig'

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        del kwargs['choices']
        del kwargs['default']
        del kwargs['max_length']
        return name, path, args, kwargs

and following test:

# test_models.py

class TestArticle:

    def test_model(self):
        obj = mixer.blend(Article, pk=1)
        assert obj.pk == 1, "Should save an Article instance"

I receive following error:


self = <app.tests.test_models.TestArticle object at 0x7f0de6697d10>

    def test_model(self):
>       obj = mixer.blend(Article, pk=1)

apps/core/tests/test_models.py:523:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.7/site-packages/mixer/main.py:568: in blend
    return type_mixer.blend(**values)
/usr/local/lib/python3.7/site-packages/mixer/main.py:116: in blend
    for name, value in defaults.items()
/usr/local/lib/python3.7/site-packages/mixer/main.py:116: in <genexpr>
    for name, value in defaults.items()
/usr/local/lib/python3.7/site-packages/mixer/mix_types.py:222: in gen_value
    return type_mixer.gen_field(field)
/usr/local/lib/python3.7/site-packages/mixer/backend/django.py:270: in gen_field
    return super(TypeMixer, self).gen_field(field)
/usr/local/lib/python3.7/site-packages/mixer/main.py:193: in gen_field
    return self.gen_value(field.name, field, unique=unique)
/usr/local/lib/python3.7/site-packages/mixer/main.py:238: in gen_value
    fab = self.get_fabric(field, field_name, fake=fake)
/usr/local/lib/python3.7/site-packages/mixer/main.py:282: in get_fabric
    self.__fabrics[key] = self.make_fabric(field.scheme, field_name, fake)
/usr/local/lib/python3.7/site-packages/mixer/backend/django.py:327: in make_fabric
    fcls, field_name=fname, fake=fake, kwargs=kwargs)
/usr/local/lib/python3.7/site-packages/mixer/main.py:301: in make_fabric
    factory=self.__factory).blend, **kwargs)
/usr/local/lib/python3.7/site-packages/mixer/main.py:55: in __call__
    cls_type, mixer=mixer, factory=factory, fake=fake)
/usr/local/lib/python3.7/site-packages/mixer/main.py:88: in __init__
    self.__fields = _.OrderedDict(self.__load_fields())
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <TypeMixer <class 'apps.common.fields.PostgresSearchVectorField'>>

    def __load_fields(self):
>       private_fields = getattr(self.__scheme._meta, 'private_fields', [])
E       AttributeError: Mixer (core.Article): type object 'PostgresSearchVectorField' has no attribute '_meta'

/usr/local/lib/python3.7/site-packages/mixer/backend/django.py:386: AttributeError

I use following versions:

Django==3.0.4
psycopg2-binary==2.8.4
mixer==6.1.3

I know mixer is currently only compatible with Django 2.1 as mentioned in the requirements. But I'm still able to use mixer in other tests with Django 3.0.4 except when using SearchVectorField. According to django's documentation SearchVectorField has been available since Django 1.10.

Is there a possibility to add a functionality for SearchVectorField?

Thanks

osvill commented 4 years ago

After some further research I noticed, this isn't an issue of mixer. It's the fact, that the custom field will create a generated column at the database and django tries to insert a value and can't skip this field.

In case of mixer you can use the approach from the docs. But for Django you have to override the model's save method (exclude all SearchVectorField before saving) and maybe create a custom manager for the bulk_create method.