from __future__ import unicode_literals

from django import forms
from django.contrib.contenttypes.forms import generic_inlineformset_factory
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldError
from django.db.models import Q
from django.test import TestCase
from django.utils import six

from .models import (TaggedItem, ValuableTaggedItem, Comparison, Animal,
                     Vegetable, Mineral, Gecko, Rock, ManualPK,
                     ForProxyModelModel, ForConcreteModelModel,
                     ProxyRelatedModel, ConcreteRelatedModel, AllowsNullGFK)


class GenericRelationsTests(TestCase):
    def setUp(self):
        self.lion = Animal.objects.create(
            common_name="Lion", latin_name="Panthera leo")
        self.platypus = Animal.objects.create(
            common_name="Platypus", latin_name="Ornithorhynchus anatinus")
        Vegetable.objects.create(name="Eggplant", is_yucky=True)
        self.bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
        self.quartz = Mineral.objects.create(name="Quartz", hardness=7)

        # Tagging stuff.
        self.bacon.tags.create(tag="fatty")
        self.bacon.tags.create(tag="salty")
        self.lion.tags.create(tag="yellow")
        self.lion.tags.create(tag="hairy")

        # Original list of tags:
        self.comp_func = lambda obj: (
            obj.tag, obj.content_type.model_class(), obj.object_id
        )

    def test_generic_update_or_create_when_created(self):
        """
        Should be able to use update_or_create from the generic related manager
        to create a tag. Refs #23611.
        """
        count = self.bacon.tags.count()
        tag, created = self.bacon.tags.update_or_create(tag='stinky')
        self.assertTrue(created)
        self.assertEqual(count + 1, self.bacon.tags.count())

    def test_generic_update_or_create_when_updated(self):
        """
        Should be able to use update_or_create from the generic related manager
        to update a tag. Refs #23611.
        """
        count = self.bacon.tags.count()
        tag = self.bacon.tags.create(tag='stinky')
        self.assertEqual(count + 1, self.bacon.tags.count())
        tag, created = self.bacon.tags.update_or_create(defaults={'tag': 'juicy'}, id=tag.id)
        self.assertFalse(created)
        self.assertEqual(count + 1, self.bacon.tags.count())
        self.assertEqual(tag.tag, 'juicy')

    def test_generic_get_or_create_when_created(self):
        """
        Should be able to use get_or_create from the generic related manager
        to create a tag. Refs #23611.
        """
        count = self.bacon.tags.count()
        tag, created = self.bacon.tags.get_or_create(tag='stinky')
        self.assertTrue(created)
        self.assertEqual(count + 1, self.bacon.tags.count())

    def test_generic_get_or_create_when_exists(self):
        """
        Should be able to use get_or_create from the generic related manager
        to get a tag. Refs #23611.
        """
        count = self.bacon.tags.count()
        tag = self.bacon.tags.create(tag="stinky")
        self.assertEqual(count + 1, self.bacon.tags.count())
        tag, created = self.bacon.tags.get_or_create(id=tag.id, defaults={'tag': 'juicy'})
        self.assertFalse(created)
        self.assertEqual(count + 1, self.bacon.tags.count())
        # shouldn't had chan
ged the ta
g
        self.assertEqual(tag.tag, 'stinky')

    def test_generic_relations_m2m_mimic(self):
        """
        Objects with declared GenericRelations can be tagged directly -- the
        API mimics the many-to-many API.
        """
        self.assertQuerysetEqual(self.lion.tags.all(), [
            "<TaggedItem: hairy>",
            "<TaggedItem: yellow>"
        ])
        self.assertQuerysetEqual(self.bacon.tags.all(), [
            "<TaggedItem: fatty>",
            "<TaggedItem: salty>"
        ])

    def test_access_content_object(self):
        """
        Test accessing the content object like a foreign key.
        """
        tagged_item = TaggedItem.objects.get(tag="salty")
        self.assertEqual(tagged_item.content_object, self.bacon)

    def test_query_content_object(self):
        qs = TaggedItem.objects.filter(
            animal__isnull=False).order_by('animal__common_name', 'tag')
        self.assertQuerysetEqual(
            qs, ["<TaggedItem: hairy>", "<TaggedItem: yellow>"]
        )

        mpk = ManualPK.objects.create(id=1)
        mpk.tags.create(tag='mpk')
        qs = TaggedItem.objects.filter(
            Q(animal__isnull=False) | Q(manualpk__id=1)).order_by('tag')
        self.assertQuerysetEqual(
            qs, ["hairy", "mpk", "yellow"], lambda x: x.tag)

    def test_exclude_generic_relations(self):
        """
        Test lookups over an object without GenericRelations.
        """
        # Recall that the Mineral class doesn't have an explicit GenericRelation
        # defined. That's OK, because you can create TaggedItems explicitly.
        # However, excluding GenericRelations means your lookups have to be a
        # bit more explicit.
        TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
        TaggedItem.objects.create(content_object=self.quartz, tag="clearish")

        ctype = ContentType.objects.get_for_model(self.quartz)
        q = TaggedItem.objects.filter(
            content_type__pk=ctype.id, object_id=self.quartz.id
        )
        self.assertQuerysetEqual(q, [
            "<TaggedItem: clearish>",
            "<TaggedItem: shiny>"
        ])

    def test_access_via_content_type(self):
        """
        Test lookups through content type.
        """
        self.lion.delete()
        self.platypus.tags.create(tag="fatty")

        ctype = ContentType.objects.get_for_model(self.platypus)

        self.assertQuerysetEqual(
            Animal.objects.filter(tags__content_type=ctype),
            ["<Animal: Platypus>"])

    def test_set_foreign_key(self):
        """
        You can set a generic foreign key in the way you'd expect.
        """
        tag1 = TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
        tag1.content_object = self.platypus
        tag1.save()

        self.assertQuerysetEqual(
            self.platypus.tags.all(),
            ["<TaggedItem: shiny>"])

    def test_queries_across_generic_relations(self):
        """
        Queries across generic relations respect the content types. Even though
        there are two TaggedItems with a tag of "fatty", this query only pulls
        out the one with the content type related to Animals.
        """
        self.assertQuerysetEqual(Animal.objects.order_by('common_name'), [
            "<Animal: Lion>",
            "<Animal: Platypus>"
        ])

    def test_queries_content_type_restriction(self):
        """
        Create another fatty tagged instance with different PK to ensure there
        is a content type restriction in the generated queries below.
        """
        mpk = ManualPK.objects.create(id=self.lion.pk)
        mpk.tags.create(tag="fatty")
        self.platypus.tags.create(tag="fatty")

        self.assertQuerysetEqual(
            Animal.objects.filter(tags__tag='fatty'), ["<Animal: Platypus>"])
        self.assertQuerysetEqual(
            Animal.objects.exclude(tags__tag='fatty'), ["<Animal: Lion>"])

    def test_object_deletion_with_generic_relation(self):
        """
        If you delete an object with an explicit Generic relation, the related
        objects are deleted when the source object is deleted.
        """
        self.assertQuerysetEqual(TaggedItem.objects.all(), [
            ('fatty', Vegetable, self.bacon.pk),
            ('hairy', Animal, self.lion.pk),
            ('salty', Vegetable, self.bacon.pk),
            ('yellow', Animal, self.lion.pk)
        ],
            self.comp_func
        )
        self.lion.delete()

        self.assertQuerysetEqual(TaggedItem.objects.all(), [
            ('fatty', Vegetable, self.bacon.pk),
            ('salty', Vegetable, self.bacon.pk),
        ],
            self.comp_func
        )

    def test_object_deletion_wi