class TranslationField(JSONField):
    """
    This model field is used to store the translations in the translated model.

    Arguments:
        fields (iterable): List of model field names to make translatable.
        required_languages (iterable or dict): List of languages required for the model.
            If a dict is supplied, the keys must be translated field names with the value
            containing a list of required languages for that specific field.
        virtual_fields (bool): If `False`, do not add virtual fields to access
            translated values with.
            Set to `True` during migration from django-modeltranslation to prevent
            collisions with it's database fields while having the `i18n` field available.
        fallback_language_field: If not None, this should be the name of the field containing a
            language code to use as the first language in any fallback chain.
            For example: if you have a model instance with 'nl' as language_code, and set
            fallback_language_field='language_code', 'nl' will always be tried after the current
            language before any other language.
    """

    description = "Translation storage for a model"

    def __init__(
        self,
        fields=None,
        required_languages=None,
        virtual_fields=True,
        fallback_language_field=None,
        *args,
        **kwargs,
    ):
        self.fields = fields or ()
        self.required_languages = required_languages or ()
        self.virtual_fields = virtual_fields
        self.fallback_language_field = fallback_language_field

        kwargs["editable"] = False
        kwargs["null"] = True
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()

        del kwargs["editable"]
        del kwargs["null"]
        kwargs["fields"] = self.fields
        kwargs["required_languages"] = self.required_languages
        kwargs["virtual_fields"] = self.virtual_fields

        return name, path, args, kwargs

    def get_translated_fields(self):
        """Return a generator for all translated fields."""
        for field in self.model._meta.get_fields():
            if isinstance(field, TranslatedVirtualField):
                yield field

    def contribute_to_class(self, cls, name):
        if name != "i18n":
            raise ImproperlyConfigured('{} must have name "i18n"'.format(self.__class__.__name__))

        super().contribute_to_class(cls, name) def translate_model(Model):
    i18n_field = get_i18n_field(Model)

    if i18n_field is None:
        return

    if not i18n_field.virtual_fields:
        # This mode is required for the migration process:
        # It needs to have a stage where we do have the TranslationField,
        # but not the virtual fields (which would collide with the
        # django-modeltranslation `<field>_<lang>`-fields), to be able to
        # copy the values from the `<field>_<lang>`-fields into `i18n.<field>_<lang>`.
        return

    validate(Model)

    add_manager(Model)
    fields_to_translate = get_i18n_field_param(Model, i18n_field, "fields")
    required_languages = get_i18n_field_param(Model, i18n_field, "required_languages")
    add_virtual_fields(Model, fields_to_translate, required_languages)
    patch_constructor(Model)

    translate_meta_ordering(Model) class Attribute(models.Model):
    slug = models.SlugField(verbose_name="slug", unique=True)
    name = models.CharField("name", max_length=100, db_index=True)

    i18n = TranslationField(fields=("name",))

    def __str__(self):
        return self.name_i18n class Blog(models.Model):
    title = models.CharField(max_length=255)
    body = models.TextField(null=True)

    category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.CASCADE)
    site = models.ForeignKey(Site, null=True, blank=True, on_delete=models.CASCADE)

    i18n = TranslationField(fields=("title", "body"), required_languages=("nl",))

    def __str__(self):
        return self.title class BlogAttr(createBaseAttr(Blog)):
    pass class Category(models.Model):

    name = models.CharField(max_length=255)
    title = models.CharField(max_length=255)

    i18n = TranslationField(fields=("name", "title"))

    objects = CategoryQueryset.as_manager()

    def __str__(self):
        return self.name class Challenge(models.Model):
    """Model using a custom fallback language per instance/record."""

    title = models.CharField(max_length=255)
    header = models.CharField(max_length=255, null=True, blank=True)

    default_language = models.CharField(
        max_length=2, null=True, blank=True, default=get_default_language()
    )

    i18n = TranslationField(fields=("title", "header"), fallback_language_field="default_language")

    def __str__(self):
        return self.title_i18n class ChallengeContent(models.Model):
    """Model using a custom fallback language on a related record."""

    challenge = models.ForeignKey(Challenge, on_delete=models.CASCADE)
    content = models.TextField()

    i18n = TranslationField(
        fields=("content",), fallback_language_field="challenge__default_language"
    )

    def __str__(self):
        return self.content_i18n class Site(models.Model):
    name = models.CharField(max_length=255)

    objects = MultilingualManager()

    def __str__(self):
        return self.name class CreateTestModel:
    """
    Create the database table for a one or more Models during tests.

    Helpfull if we want to use custom settings which influence the models
    created in our tests, used like this::

        class Test(TestCase):

            @override_settings(LANGUAGE_CODE='nl')
            def test_with_custom_model(self):
                class TestModel(models.Model):
                    title = models.CharField(max_length=10)

                with CreateTestModel(TestModel):
                    m = TestModel.objects.create(title='foo')

                    self.assertEquals(m.title, 'foo')
    """

    def __init__(self, *args, **kwargs):
        self.models = args
        if kwargs.get("translate", False):
            for Model in self.models:
                translate_model(Model)

    def __enter__(self):
        """
        Create the tables in our database
        """
        with connection.schema_editor() as editor:
            for Model in self.models:
                editor.create_model(Model)

    def __exit__(self, *args):
        """
        Remove the tables from our database
        """
        with connection.schema_editor() as editor:
            for Model in reversed(self.models):
                editor.delete_model(Model) def load_wiki():
    wiki = Category.objects.create(name="Wikipedia")
    with open(os.path.join("tests", "fixtures", "fulltextsearch.json")) as infile:
        data = json.load(infile)

        for article in data:
            kwargs = {}
            for item in article:
                lang = "_" + item["lang"]

                kwargs["title" + lang] = item["title"]
                kwargs["body" + lang] = item["body"]

            Blog.objects.create(category=wiki, **kwargs)
import pickle
import django
from unittest import skipIf
from django.db import models
from django.db.models import F, Q
from django.test import TestCase, override_settings
from django.utils.translation import override
from modeltrans.fields import TranslationField
from modeltrans.translator import translate_model
from .app.models import Attribute, Blog, BlogAttr, Category, Challenge, ChallengeContent, Site
from .utils import CreateTestModel, load_wiki
        from django.contrib.postgres.search import SearchVector
        from django.db.models.functions import Lower
        from django.db.models.functions import Lower

        qs = Blog.objects.filter(title_nl=models.functions.Upper(F("title_nl")))
        self.assertEqual({m.title for m in qs}, {"bar", "baz"})

    def test_filter_relations(self):
        mass = Attribute.objects.create(slug="mass", name="Mean Mass")
        length = Attribute.objects.create(slug="length", name="Length", name_nl="Lengte")

        dog = Blog.objects.create(title="Australian Kelpie Dog")
        whale = Blog.objects.create(title="Blue Whale", title_nl="Blauwe vinvis")

        BlogAttr.objects.create(object=dog, attribute=mass, value=17)
        BlogAttr.objects.create(object=dog, attribute=length, value=0.50)
        BlogAttr.objects.create(object=whale, attribute=mass, value=181000)
        BlogAttr.objects.create(object=whale, attribute=length, value=28)

        with override("nl"):
            Attribute.objects.filter(
                blogattr__object_id__in=Blog.objects.filter(title__contains="al")
            )

    def test_filter_subquery(self):
        """
        When in a subquery, the table alias should be used rather than the real
        table name.
        """
        c1 = Category.objects.create(name="Sofa", name_nl="Bank")
        c2 = Category.objects.create(name="modeltrans", name_nl="modeltrans")
        birds = Category.objects.create(name="Birds")

        Blog.objects.create(title="Chesterfield", category=c1)
        Blog.objects.create(title="Why migrate", category=c2)
        Blog.objects.create(title="Initial prototype", category=c2)
        Blog.objects.create(title="Dogfooding", category=c2)

        Blog.objects.create(title="Falcon", category=birds)

        qs = Blog.objects.filter(category__in=Category.objects.filter(name_nl__contains="an"))
        self.assertEqual(
            {m.title_i18n for m in qs},
            {"Chesterfield", "Why migrate", "Initial prototype", "Dogfooding"},
        )

    def test_queryset_related_model(self):
        qs = Blog.objects.filter(category__name_nl="Vogels")
        self.assertEqual({m.title for m in qs}, {"Falcon", "Duck"})

    def test_filter_spanning_relation(self):
        birds = Category.objects.get(name="Birds")
        bird_blogs = Blog.objects.filter(category=birds)

        self.assertEqual(
            {b.title for b in Blog.objects.filter(category__name_nl="Vogels")},
            {b.title for b in bird_blogs},
        )

    def test_filter_spanning_relation_from_non_translatable(self):
        """
        `MultilingualManager` must be set on non-translated models too in order
        to use the rewrite of the fields.
        """
        s = Site.objects.create(name="Testsite")
        Blog.objects.create(title="Strange", title_nl="Vreemd", site=s)

        site_modeltrans = Site.objects.create(name="Modeltrans blog")
        Blog.objects.create(title="Version 0.1.1 of modeltrans released", site=site_modeltrans)

        qs = Site.objects.filter(blog__title="Strange")
        self.assertEqual({m.name for m in qs}, {"Testsite"})

        qs = Site.objects.filter(blog__title_nl="Vreemd")
        self.assertEqual({m.name for m in qs}, {"Testsite"})

        qs = Site.objects.filter(blog__title_i18n__contains="modeltrans")
        self.assertEqual({m.name for m in qs}, {"Modeltrans blog"})


class CustomFallbackTest(TestCase):
    def test_custom_fallback(self):
        instance = Challenge.objects.create(
            default_language="nl", title="Hurray", i18n={"title_nl": "Hoera"}
        )

        with override("de"):
            self.assertCountEqual(Challenge.objects.filter(title_i18n="Hoera"), [instance])
        with override("nl"):
            self.assertCountEqual(Challenge.objects.filter(title_i18n="Hoera"), [instance])
        with override("en"):
            self.assertCountEqual(Challenge.objects.filter(title_i18n="Hoera"), [])

    def test_custom_fallback_null(self):
        instance = Challenge.objects.create(
            default_language=None, title="Hurray", i18n={"title_nl": "Hoera"}
        )
        with override("de"):
            self.assertCountEqual(Challenge.objects.filter(title_i18n="Hoera"), [])
        with override("nl"):
            self.assertCountEqual(Challenge.objects.filter(title_i18n="Hoera"), [instance])
        with override("en"):
            self.assertCountEqual(Challenge.objects.filter(title_i18n="Hoera"), [])

    def test_custom_fallback_follow_relation(self):
        challenge = Challenge.objects.create(default_language="nl", title="Hurray")
        content = ChallengeContent.objects.create(
            challenge=challenge, content="Congratulations", i18n={"content_nl": "Gefeliciteerd"}
        )
        with override("de"):
            self.assertCountEqual(
                ChallengeContent.objects.filter(content_i18n="Gefeliciteerd"), [content]
            )
        with override("nl"):
            self.assertCountEqual(
                ChallengeContent.objects.filter(content_i18n="Gefeliciteerd"), [content]
            )
        with override("en"):
            self.assertCountEqual(ChallengeContent.objects.filter(content_i18n="Gefeliciteerd"), [])


class FulltextSearch(TestCase):
    def test_SearchVector(self):

        load_wiki()