"""
Tagulous test: Tag Trees

Modules tested:
    tagulous.models.tree
"""
from django.db import IntegrityError
from django.test import TestCase, TransactionTestCase

import tagulous.settings as tagulous_settings
from tagulous import models as tag_models
from tests.lib import TagTestManager
from tests.tagulous_tests_app import models as test_models


class TagTreeTestManager(TagTestManager):
    """
    Manage tag tree tests
    """

    def assertTreeTag(
        self,
        tag,
        name=None,
        label=None,
        slug=None,
        path=None,
        parent=None,
        count=None,
        protected=None,
        level=None,
    ):
        "Check tag attributes match those specified"
        if name is not None:
            self.assertEqual(tag.name, name)
        if label is not None:
            self.assertEqual(tag.label, label)
        if slug is not None:
            self.assertEqual(tag.slug, slug)
        if path is not None:
            self.assertEqual(tag.path, path)

        if parent is not None:
            self.assertEqual(tag.parent, parent)
        if count is not None:
            self.assertEqual(tag.count, count)
        if protected is not None:
            self.assertEqual(tag.protected, protected)
        if level is not None:
            self.assertEqual(tag.level, level)


# ##############################################################################
# ###### TagTreeModel basics
# ##############################################################################


class TagTreeModelTest(TagTreeTestManager, TestCase):
    """
    Test TagTreeModel basics - that the models are created correctly, and tag
    instances can be created directly with parents and fields correct.

    These tests refer to levels; the higher the level, the deeper in the tree,
    eg: level1/level2/level3
    """

    manage_models = [test_models.TreeTest]

    def setUpExtra(self):
        self.test_model = test_models.TreeTest
        self.singletag_field = test_models.TreeTest.singletag
        self.singletag_model = test_models.TreeTest.singletag.tag_model
        self.tag_field = test_models.TreeTest.tags
        self.tag_model = test_models.TreeTest.tags.tag_model

    def test_label_field_length(self):
        """
        Value is initialized in setup.py runtests() settings
        """
        self.assertEqual(
            self.tag_model._meta.get_field("label").max_length,
            tagulous_settings.LABEL_MAX_LENGTH,
        )

    def test_singletag_model(self):
        "Check tag tree model is used as a base when SingleTagField tree=True"
        self.assertTrue(issubclass(self.singletag_model, tag_models.TagTreeModel))

    def test_tag_model(self):
        "Check tag tree model is used as a base when TagField tree=True"
        self.assertTrue(issubclass(self.tag_model, tag_models.TagTreeModel))

    def test_empty_name_raises_integrity_error(self):
        with self.assertRaises(IntegrityError) as cm:
            self.tag_model.objects.create(name=None)
        self.assertEqual(
            str(cm.exception),
            "NOT NULL constraint failed: tagulous_tests_app_tagulous_treetest_tags.name",
        )

    def test_level_1(self):
        "Check level 1 node created correctly"
        t1 = self.tag_model.objects.create(name="One")
        self.assertTreeTag(t1, name="One", label="One", slug="one", path="one", level=1)

    def test_level_1_single_character(self):
        "Check level 1 node created correctly if only one character"
        t1 = self.tag_model.objects.create(name="O")
        self.assertTreeTag(
            t1, name="O", label="O", slug="o", path="o", level=1,
        )

    def test_level_2_existing_l1(self):
        "Check level 2 node created with existing level 1"
        t1 = self.tag_model.objects.create(name="One")
        t2 = self.tag_model.objects.create(name="One/Two")
        self.assertTreeTag(
            t2,
            name="One/Two",
            label="Two",
            slug="two",
            path="one/two",
            parent=t1,
            level=2,
        )

    def test_level_2_missing_l1(self):
        "Check level 2 node creates missing level 1"
        t2 = self.tag_model.objects.create(name="One/Two")
        t1 = self.tag_model.objects.get(name="One")
        self.assertTreeTag(t1, name="One", label="One", slug="one", path="one", level=1)
        self.assertTreeTag(
            t2,
            name="One/Two",
            label="Two",
            slug="two",
            path="one/two",
            parent=t1,
            level=2,
        )

    def test_level_3_existing_l1_l2(self):
        "Check level 3 node created with existing level 1 and 2"
        self.tag_model.objects.create(name="One")
        t2 = self.tag_model.objects.create(name="One/Two")
        t3 = self.tag_model.objects.create(name="One/Two/Three")
        self.assertTreeTag(
            t3,
            name="One/Two/Three",
            label="Three",
            slug="three",
            path="one/two/three",
            parent=t2,
            level=3,
        )

    def test_level_3_existing_l1_missing_l2(self):
        "Check level 3 node created with existing level 1 but missing level 2"
        t1 = self.tag_model.objects.create(name="One")
        t3 = self.tag_model.objects.create(name="One/Two/Three")
        t2 = self.tag_model.objects.get(name="One/Two")
        self.assertTreeTag(
            t2,
            name="One/Two",
            label="Two",
            slug="two",
            path="one/two",
            parent=t1,
            level=2,
        )
        self.assertTreeTag(
            t3,
            name="One/Two/Three",
            label="Three",
            slug="three",
            path="one/two/three",
            parent=t2,
            level=3,
        )

    def test_level_3_missing_l1_l2(self):
        "Check level 3 node created with missing level 1 and 2"
        t3 = self.tag_model.objects.create(name="One/Two/Three")
        t1 = self.tag_model.objects.get(name="One")
        t2 = self.tag_model.objects.get(name="One/Two")
        self.assertTreeTag(t1, name="One", label="One", slug="one", path="one", level=1)
        self.assertTreeTag(
            t2,
            name="One/Two",
            label="Two",
            slug="two",
            path="one/two",
            parent=t1,
            level=2,
        )
        self.assertTreeTag(
            t3,
            name="One/Two/Three",
            label="Three",
            slug="three",
            path="one/two/three",
            parent=t2,
            level=3,
        )

    def test_level_1_renames_l2_l3(self):
        "Check renaming level 1 node renames l2 and l3"
        t3 = self.tag_model.objects.create(name="One/Two/Three")
        t1 = self.tag_model.objects.get(name="One")
        self.assertTagModel(
            self.tag_model, {"One": 0, "One/Two": 0, "One/Two/Three": 0}
        )
        t1.name = "Uno"
        t1.slug = None  # Force slug to be recreated
        t1.save()

        # Reload
        self.assertTagModel(
            self.tag_model, {"Uno": 0, "Uno/Two": 0, "Uno/Two/Three": 0}
        )
        t1 = self.tag_model.objects.get(name="Uno")
        t2 = self.tag_model.objects.get(name="Uno/Two")
        t3 = self.tag_model.objects.get(name="Uno/Two/Three")

        self.assertTreeTag(t1, name="Uno", label="Uno", slug="uno", path="uno", level=1)
        self.assertTreeTag(
            t2,
            name="Uno/Two",
            label="Two",
            slug="two",
            path="uno/two",
            parent=t1,
            level=2,
        )
        self.assertTreeTag(
            t3,
            name="Uno/Two/Three",
            label="Three",
            slug="three",
            path="uno/two/three",
            parent=t2,
            level=3,
        )

    def test_rebuild(self):
        "Check rebuild updates all slugs"
        # Break slugs
        t3 = self.tag_model.objects.create(name="One/Two/Three")
        for tag in self.tag_model.objects.all():
            tag.slug = str(tag.pk)
            tag.save()

        # Load
        t1 = self.tag_model.objects.get(name="One")
        t2 = self.tag_model.objects.get(name="One/Two")
        t3 = self.tag_model.objects.get(name="One/Two/Three")
        t1_slug = str(t1.pk)
        t2_slug = str(t2.pk)
        t3_slug = str(t3.pk)
        self.assertRegex(t1_slug, r"^\d+$")
        self.assertRegex(t2_slug, r"^\d+$")
        self.assertRegex(t3_slug, r"^\d+$")
        self.assertTreeTag(t1, name="One", slug=t1_slug, path=t1_slug)
        self.assertTreeTag(
            t2,
            name="One/Two",
            slug=t2_slug,
            path="{}/{}".format(t1_slug, t2_slug),
            parent=t1,
        )
        self.assertTreeTag(
            t3,
            name="One/Two/Three",
            slug=t3_slug,
            path="{}/{}/{}".format(t1_slug, t2_slug, t3_slug),
            parent=t2,
        )

        # Rebuild and re-test
        self.tag_model.objects.rebuild()
        t1 = self.tag_model.objects.get(name="One")
        t2 = self.tag_model.objects.get(name="One/Two")
        t3 = self.tag_model.objects.get(name="One/Two/Three")
        self.assertTreeTag(t1, name="One", slug="one", path="one")
        self.assertTreeTag(t2, name="One/Two", slug="two", path="one/two", parent=t1)
        self.assertTreeTag(
            t3, name="One/Two/Three", slug="three", path="one/two/three", parent=t2
        )


# ##############################################################################
# ###### TagTreeModel tree merging
# ##############################################################################


class TagTreeModelMergeTest(TagTreeTestManager, TestCase):
    """
    Test merging TagTreeModel nodes
    """

    manage_models = [test_models.TreeTest]

    def setUpExtra(self):
        self.test_model = test_models.TreeTest
        self.tag_field = test_models.TreeTest.tags
        self.tag_model = test_models.TreeTest.tags.tag_model

        # Normal Animal leaf nodes
        self.test_model.objects.create(name="cat1", tags="Animal/Mammal/Cat")
        self.test_model.objects.create(name="dog1", tags="Animal/Mammal/Dog")
        self.test_model.objects.create(name="bee1", tags="Animal/Insect/Bee")

        # Pet tree has Cat leaf and extra L2 not in Animal
        self.test_model.objects.create(name="cat2", tags="Pet/Mammal/Cat")
        self.test_model.objects.create(name="robin1", tags="Pet/Bird/Robin")

        # Food tree has Cat leaf and extra L3 not in Animal
        self.test_model.objects.create(name="cat3", tags="Food/Mammal/Cat")
        self.test_model.objects.create(name="pig1", tags="Food/Mammal/Pig")

    def test_setup(self):
        "Check test setup is correct"
        # There should be 6 tags
        self.assertTagModel(
            self.tag_model,
            {
                "Animal": 0,
                "Animal/Insect": 0,
                "Animal/Insect/Bee": 1,
                "Animal/Mammal": 0,
                "Animal/Mammal/Cat": 1,
                "Animal/Mammal/Dog": 1,
                "Pet": 0,
                "Pet/Mammal": 0,
                "Pet/Mammal/Cat": 1,
                "Pet/Bird": 0,
                "Pet/Bird/Robin": 1,
                "Food": 0,
                "Food/Mammal": 0,
                "Food/Mammal/Cat": 1,
                "Food/Mammal/Pig": 1,
            },
        )

    def test_merge_l1(self):
        """
        Test that merging an l1 without merging children reassigns tagged
        items, but leaves self and children
        """
        # Tag something with 'Pet'
        t1 = self.test_model.objects.create(name="pet1", tags="Pet")
        self.assertEqual(self.tag_model.objects.get(name="Pet").count, 1)

        # Now merge
        self.tag_model.objects.get(name="Animal").merge_tags(
            "Pet, Food", children=False
        )
        self.assertTagModel(
            self.tag_model,
            {
                "Animal": 1,
                "Animal/Insect": 0,
                "Animal/Insect/Bee": 1,
                "Animal/Mammal": 0,
                "Animal/Mammal/Cat": 1,
                "Animal/Mammal/Dog": 1,
                "Pet": 0,
                "Pet/Mammal": 0,
                "Pet/Mammal/Cat": 1,
                "Pet/Bird": 0,
                "Pet/Bird/Robin": 1,
                "Food": 0,
                "Food/Mammal": 0,
                "Food/Mammal/Cat": 1,
                "Food/Mammal/Pig": 1,
            },
        )
        self.assertInstanceEqual(t1, name="pet1", tags="Animal")

    def test_merge_l1_with_children(self):
        """
        Test that merging an l1 and its children merges the full subtrees and
        cleans empty parents
        """
        # Now merge
        self.tag_model.objects.get(name="Animal").merge_tags("Pet, Food", children=True)
        self.assertTagModel(
            self.tag_model,
            {
                "Animal": 0,
                "Animal/Insect": 0,
                "Animal/Insect/Bee": 1,
                "Animal/Mammal": 0,
                "Animal/Mammal/Cat": 3,
                "Animal/Mammal/Dog": 1,
                "Animal/Mammal/Pig": 1,
                "Animal/Bird": 0,
                "Animal/Bird/Robin": 1,
            },
        )

    def test_merge_l2(self):
        """
        Test that merging an l2 without merging children reassigns tagged
        items, but leaves self and children
        """
        # Tag something with 'Pet/Mammal'
        t1 = self.test_model.objects.create(name="mammal1", tags="Pet/Mammal")
        self.assertEqual(self.tag_model.objects.get(name="Pet/Mammal").count, 1)

        # Now merge
        self.tag_model.objects.get(name="Animal/Mammal").merge_tags(
            "Pet/Mammal, Food/Mammal", children=False
        )
        self.assertTagModel(
            self.tag_model,
            {
                "Animal": 0,
                "Animal/Insect": 0,
                "Animal/Insect/Bee": 1,
                "Animal/Mammal": 1,
                "Animal/Mammal/Cat": 1,
                "Animal/Mammal/Dog": 1,
                "Pet": 0,
                "Pet/Mammal": 0,
                "Pet/Mammal/Cat": 1,
                "Pet/Bird": 0,
                "Pet/Bird/Robin": 1,
                "Food": 0,
                "Food/Mammal": 0,
                "Food/Mammal/Cat": 1,
                "Food/Mammal/Pig": 1,
            },
        )
        self.assertInstanceEqual(t1, name="mammal1", tags="Animal/Mammal")

    def test_merge_l2_with_children(self):
        """
        Test that merging an l2 and its children merges the full subtrees and
        cleans empty parents
        """
        # Now merge
        self.tag_model.objects.get(name="Animal/Mammal").merge_tags(
            "Pet/Mammal, Food/Mammal", children=True
        )
        self.assertTagModel(
            self.tag_model,
            {
                "Animal": 0,
                "Animal/Insect": 0,
                "Animal/Insect/Bee": 1,
                "Animal/Mammal": 0,
                "Animal/Mammal/Cat": 3,
                "Animal/Mammal/Dog": 1,
                "Animal/Mammal/Pig": 1,
                "Pet": 0,
                "Pet/Bird": 0,
                "Pet/Bird/Robin": 1,
            },
        )

    def test_merge_l3(self):
        "Test merging an l3 cleans empty parents"
        # No need to test children=True - there are no children
        self.tag_model.objects.get(name="Animal/Mammal/Cat").merge_tags(
            "Pet/Mammal/Cat, Food/Mammal/Cat", children=False
        )
        self.assertTagModel(
            self.tag_model,
            {
                "Animal": 0,
                "Animal/Insect": 0,
                "Animal/Insect/Bee": 1,
                "Animal/Mammal": 0,
                "Animal/Mammal/Cat": 3,
                "Animal/Mammal/Dog": 1,
                "Pet": 0,
                # Pet/Mammal now empty
                "Pet/Bird": 0,
                "Pet/Bird/Robin": 1,
                "Food": 0,
                "Food/Mammal": 0,
                "Food/Mammal/Pig": 1,
            },
        )


# ##############################################################################
# ###### Custom TagTreeModel basics
# ##############################################################################


class TagTreeModelCustomTest(TagTreeTestManager, TransactionTestCase):
    """
    Test TagTreeModel basics when using a custom model

    Use a TransactionTestCase so the apps will be reset
    """

    manage_models = [test_models.CustomTreeTest]

    def setUpExtra(self):
        self.test_model = test_models.CustomTreeTest
        self.tag_model = test_models.CustomTagTree
        self.singletag_field = test_models.CustomTreeTest.singletag
        self.tag_field = test_models.CustomTreeTest.tags

    def test_tag_model(self):
        "Check tag tree model is used as a base"
        self.assertTrue(issubclass(self.tag_model, tag_models.TagTreeModel))

    def test_tree_option(self):
        "Check tag tree model forces tree=True when missing"
        self.assertTrue(self.tag_model.tag_options.tree)
        self.assertTrue(self.singletag_field.tag_options.tree)
        self.assertTrue(self.tag_field.tag_options.tree)

    def test_tree_option_tagmodel_fail(self):
        "Check tree option is not allowed in TagMeta"
        with self.assertRaises(ValueError) as cm:

            class FailModel_tree_tagmeta(tag_models.TagModel):
                class TagMeta:
                    tree = True

        self.assertEqual(str(cm.exception), "Cannot set tree option in TagMeta")


# ##############################################################################
# ###### TagTreeModel tree navigation methods
# ##############################################################################


class TagTreeModelNavTest(TagTreeTestManager, TestCase):
    """
    Test navigation through TagTreeModel - get_ancestors etc
    """

    manage_models = [test_models.TreeTest]

    def setUpExtra(self):
        self.tag_field = test_models.TreeTest.tags
        self.tag_model = test_models.TreeTest.tags.tag_model

        self.cat = self.tag_model.objects.create(name="Animal/Mammal/Cat")
        self.dog = self.tag_model.objects.create(name="Animal/Mammal/Dog")
        self.bee = self.tag_model.objects.create(name="Animal/Insect/Bee")

    def test_setup(self):
        "Check test setup is correct"
        # There should be 6 tags
        self.assertTagModel(
            self.tag_model,
            {
                "Animal": 0,
                "Animal/Insect": 0,
                "Animal/Insect/Bee": 0,
                "Animal/Mammal": 0,
                "Animal/Mammal/Cat": 0,
                "Animal/Mammal/Dog": 0,
            },
        )

    def test_ancestors(self):
        "Check l1 and l2 ancestors found from l3"
        # Add in some more extras to make sure they're not picked up
        self.tag_model.objects.create(name="Fail")
        self.tag_model.objects.create(name="Animal/Fail")
        self.tag_model.objects.create(name="Animal/Mammal/Cat/Fail")

        # Look up from Cat
        t1 = self.tag_model.objects.get(name="Animal/Mammal/Cat")
        anc = t1.get_ancestors()
        self.assertEqual(len(anc), 2)
        self.assertTreeTag(anc[0], name="Animal", level=1)
        self.assertTreeTag(anc[1], name="Animal/Mammal", level=2)

    def test_no_ancestors(self):
        "Check no ancestors found from l1"
        t1 = self.tag_model.objects.get(name="Animal")
        self.assertEqual(len(t1.get_ancestors()), 0)

    def test_descendants_l1(self):
        "Check l2 and l3 descendants found from l1"
        # Add in some extras to make sure they're not picked up
        self.tag_model.objects.create(name="Fail")

        # Look down from Animal
        t1 = self.tag_model.objects.get(name="Animal")
        self.assertTreeTag(t1, name="Animal", level=1)
        dec = t1.get_descendants()
        self.assertEqual(len(dec), 5)
        self.assertTreeTag(dec[0], name="Animal/Insect", level=2)
        self.assertTreeTag(dec[1], name="Animal/Insect/Bee", level=3)
        self.assertTreeTag(dec[2], name="Animal/Mammal", level=2)
        self.assertTreeTag(dec[3], name="Animal/Mammal/Cat", level=3)
        self.assertTreeTag(dec[4], name="Animal/Mammal/Dog", level=3)

    def test_descendants_l3_from_l2(self):
        "Check l3 descendants found from l2"
        # Look down from Animal
        t1 = self.tag_model.objects.get(name="Animal/Mammal")
        self.assertTreeTag(t1, name="Animal/Mammal", level=2)
        dec = t1.get_descendants()
        self.assertEqual(len(dec), 2)
        self.assertTreeTag(dec[0], name="Animal/Mammal/Cat", level=3)
        self.assertTreeTag(dec[1], name="Animal/Mammal/Dog", level=3)

    def test_no_descendants_from_l3(self):
        "Check no descendants found from l3"
        # Look down from Animal
        t1 = self.tag_model.objects.get(name="Animal/Insect/Bee")
        self.assertTreeTag(t1, name="Animal/Insect/Bee", level=3)
        dec = t1.get_descendants()
        self.assertEqual(len(dec), 0)

    def test_siblings_l1(self):
        "Find level 1 siblings"
        # Add another level 1 tag to find
        self.tag_model.objects.create(name="Vegetable")

        t1 = self.tag_model.objects.get(name="Animal")
        sibs = t1.get_siblings()
        self.assertEqual(len(sibs), 2)
        self.assertTreeTag(sibs[0], name="Animal", level=1)
        self.assertTreeTag(sibs[1], name="Vegetable", level=1)

    def test_siblings_l2(self):
        "Find level 2 siblings"
        t1 = self.tag_model.objects.get(name="Animal/Insect")
        sibs = t1.get_siblings()
        self.assertEqual(len(sibs), 2)
        self.assertTreeTag(sibs[0], name="Animal/Insect", level=2)
        self.assertTreeTag(sibs[1], name="Animal/Mammal", level=2)


# ##############################################################################
# ###### TagTreeQuerySet
# ##############################################################################


class TagTreeQuerySetTest(TagTreeTestManager, TestCase):
    """
    Test navigation using TagTreeModelQuerySet
    """

    manage_models = [test_models.TreeTest]

    def setUpExtra(self):
        self.tag_field = test_models.TreeTest.tags
        self.tag_model = test_models.TreeTest.tags.tag_model

        self.cat = self.tag_model.objects.create(name="Animal/Mammal/Cat")
        self.dog = self.tag_model.objects.create(name="Animal/Mammal/Dog")
        self.bee = self.tag_model.objects.create(name="Animal/Insect/Bee")
        self.vegetable = self.tag_model.objects.create(name="Vegetable")

    def test_setup(self):
        "Check test setup is correct"
        # There should be 6 tags
        self.assertTagModel(
            self.tag_model,
            {
                "Animal": 0,
                "Animal/Insect": 0,
                "Animal/Insect/Bee": 0,
                "Animal/Mammal": 0,
                "Animal/Mammal/Cat": 0,
                "Animal/Mammal/Dog": 0,
                "Vegetable": 0,
            },
        )

    def test_ancestors_l1(self):
        qs = self.tag_model.objects.filter(level=1)
        self.assertSequenceEqual(
            qs.values_list("label", flat=True), ["Animal", "Vegetable"]
        )

        qs = qs.with_ancestors()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True), ["Animal", "Vegetable"]
        )

    def test_ancestors_l2(self):
        qs = self.tag_model.objects.filter(level=2)
        self.assertSequenceEqual(
            qs.values_list("label", flat=True), ["Insect", "Mammal"]
        )

        qs = qs.with_ancestors()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True),
            ["Animal", "Animal/Insect", "Animal/Mammal"],
        )

    def test_ancestors_l3(self):
        qs = self.tag_model.objects.filter(level=3)
        self.assertSequenceEqual(
            qs.values_list("label", flat=True), ["Bee", "Cat", "Dog"]
        )

        qs = qs.with_ancestors()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True),
            [
                "Animal",
                "Animal/Insect",
                "Animal/Insect/Bee",
                "Animal/Mammal",
                "Animal/Mammal/Cat",
                "Animal/Mammal/Dog",
            ],
        )

    def test_descendants_l1(self):
        qs = self.tag_model.objects.filter(level=1)
        self.assertSequenceEqual(
            qs.values_list("label", flat=True), ["Animal", "Vegetable"]
        )

        qs = qs.with_descendants()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True),
            [
                "Animal",
                "Animal/Insect",
                "Animal/Insect/Bee",
                "Animal/Mammal",
                "Animal/Mammal/Cat",
                "Animal/Mammal/Dog",
                "Vegetable",
            ],
        )

    def test_descendants_l2(self):
        qs = self.tag_model.objects.filter(level=2)
        self.assertSequenceEqual(
            qs.values_list("label", flat=True), ["Insect", "Mammal"]
        )

        qs = qs.with_descendants()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True),
            [
                "Animal/Insect",
                "Animal/Insect/Bee",
                "Animal/Mammal",
                "Animal/Mammal/Cat",
                "Animal/Mammal/Dog",
            ],
        )

    def test_descendants_l3(self):
        qs = self.tag_model.objects.filter(level=3)
        self.assertSequenceEqual(
            qs.values_list("label", flat=True), ["Bee", "Cat", "Dog"]
        )

        qs = qs.with_descendants()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True),
            ["Animal/Insect/Bee", "Animal/Mammal/Cat", "Animal/Mammal/Dog"],
        )

    def test_siblings_l1(self):
        qs = self.tag_model.objects.filter(name="Animal")
        self.assertSequenceEqual(qs.values_list("label", flat=True), ["Animal"])

        qs = qs.with_siblings()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True), ["Animal", "Vegetable"]
        )

    def test_siblings_l2(self):
        # Throw in something else to ensure l2 stays within subtree
        self.tag_model.objects.create(name="Vegetable/Horrible")

        qs = self.tag_model.objects.filter(name="Animal/Insect")
        self.assertSequenceEqual(qs.values_list("label", flat=True), ["Insect"])

        qs = qs.with_siblings()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True), ["Animal/Insect", "Animal/Mammal"]
        )

    def test_siblings_l3(self):
        # Throw in something else to ensure l3 stays within subtree
        self.tag_model.objects.create(name="Vegetable/Horrible/Mushroom")

        qs = self.tag_model.objects.filter(name="Animal/Mammal/Cat")
        self.assertSequenceEqual(qs.values_list("label", flat=True), ["Cat"])

        qs = qs.with_siblings()
        self.assertSequenceEqual(
            qs.values_list("name", flat=True),
            ["Animal/Mammal/Cat", "Animal/Mammal/Dog"],
        )


# ##############################################################################
# ###### TagTreeModel access via fields
# ##############################################################################


class TagTreeModelFieldTest(TagTreeTestManager, TestCase):
    """
    Test TagTreeModel access via fields - creation and deletion
    """

    manage_models = [test_models.TreeTest]

    def setUpExtra(self):
        self.singletag_field = test_models.TreeTest.singletag
        self.singletag_model = test_models.TreeTest.singletag.tag_model
        self.tag_field = test_models.TreeTest.tags
        self.tag_model = test_models.TreeTest.tags.tag_model

    def test_singletagfield_level_3(self):
        """
        Check level 3 nodes created with missing level 1 and 2 when assigned
        via singletag fields
        """
        test_models.TreeTest.objects.create(name="Test 1", singletag="One/Two/Three")
        t1 = self.singletag_model.objects.get(name="One")
        t2 = self.singletag_model.objects.get(name="One/Two")
        t3 = self.singletag_model.objects.get(name="One/Two/Three")
        self.assertTreeTag(t1, name="One", label="One", slug="one", path="one", level=1)
        self.assertTreeTag(
            t2,
            name="One/Two",
            label="Two",
            slug="two",
            path="one/two",
            parent=t1,
            level=2,
        )
        self.assertTreeTag(
            t3,
            name="One/Two/Three",
            label="Three",
            slug="three",
            path="one/two/three",
            parent=t2,
            count=1,
            level=3,
        )

        self.assertTagModel(
            self.singletag_model, {"One": 0, "One/Two": 0, "One/Two/Three": 1}
        )
        self.assertTagModel(self.tag_model, {})

    def test_tagfield_level_3(self):
        """
        Check level 3 nodes created with missing level 1 and 2 when assigned
        via tag fields
        """
        test_models.TreeTest.objects.create(
            name="Test 1", tags="One/Two/Three, Uno/Dos/Tres"
        )

        self.assertTagModel(self.singletag_model, {})
        self.assertTagModel(
            self.tag_model,
            {
                "One": 0,
                "One/Two": 0,
                "One/Two/Three": 1,
                "Uno": 0,
                "Uno/Dos": 0,
                "Uno/Dos/Tres": 1,
            },
        )

        t1 = self.tag_model.objects.get(name="One")
        t2 = self.tag_model.objects.get(name="One/Two")
        t3 = self.tag_model.objects.get(name="One/Two/Three")
        self.assertTreeTag(t1, name="One", label="One", slug="one", path="one", level=1)
        self.assertTreeTag(
            t2,
            name="One/Two",
            label="Two",
            slug="two",
            path="one/two",
            parent=t1,
            level=2,
        )
        self.assertTreeTag(
            t3,
            name="One/Two/Three",
            label="Three",
            slug="three",
            path="one/two/three",
            parent=t2,
            level=3,
            count=1,
        )

    def test_delete_l2_empty_l1(self):
        """
        Check deleting a level 2 node cleans away empty level 1
        """
        obj1 = test_models.TreeTest.objects.create(name="Test 1", singletag="One/Two")
        self.assertTagModel(self.singletag_model, {"One": 0, "One/Two": 1})

        obj1.singletag = ""
        obj1.save()
        self.assertTagModel(self.singletag_model, {})

    def test_delete_l2_populated_l1(self):
        """
        Check deleting a level 2 node leaves a populated l1
        """
        obj1 = test_models.TreeTest.objects.create(name="Test 1", singletag="Uno/Dos")
        self.singletag_model.objects.create(name="Uno/Due")
        self.assertTagModel(
            self.singletag_model, {"Uno": 0, "Uno/Dos": 1, "Uno/Due": 0}
        )

        obj1.singletag = ""
        obj1.save()
        self.assertTagModel(self.singletag_model, {"Uno": 0, "Uno/Due": 0})

    def test_delete_l3_populated_l2(self):
        """
        Check deleting a level 3 node leaves a populated level 2
        """
        obj1 = test_models.TreeTest.objects.create(
            name="Test 1", singletag="No/Uno/Dos"
        )
        self.singletag_model.objects.create(name="No/Uno/Due")
        self.assertTagModel(
            self.singletag_model,
            {"No": 0, "No/Uno": 0, "No/Uno/Dos": 1, "No/Uno/Due": 0},
        )

        obj1.singletag = ""
        obj1.save()
        self.assertTagModel(
            self.singletag_model, {"No": 0, "No/Uno": 0, "No/Uno/Due": 0}
        )

    def test_delete_l3_empty_l2_populated_l1(self):
        """
        Check deleting a level 3 node cleans empty l2 but leaves populated l1
        """
        obj1 = test_models.TreeTest.objects.create(
            name="Test 1", singletag="Uno/Dos/Tres"
        )
        self.singletag_model.objects.create(name="Uno/Due/Tre")
        self.assertTagModel(
            self.singletag_model,
            {"Uno": 0, "Uno/Dos": 0, "Uno/Dos/Tres": 1, "Uno/Due": 0, "Uno/Due/Tre": 0},
        )

        obj1.singletag = ""
        obj1.save()
        self.assertTagModel(
            self.singletag_model, {"Uno": 0, "Uno/Due": 0, "Uno/Due/Tre": 0}
        )

    def test_delete_l3_used_l2(self):
        """
        Check deleting a level 3 node leaves an empty but used level 2
        """
        test_models.TreeTest.objects.create(name="Test 1", singletag="Uno/Dos")
        obj2 = test_models.TreeTest.objects.create(
            name="Test 2", singletag="Uno/Dos/Tres"
        )
        self.assertTagModel(
            self.singletag_model, {"Uno": 0, "Uno/Dos": 1, "Uno/Dos/Tres": 1}
        )

        obj2.singletag = ""
        obj2.save()
        self.assertTagModel(self.singletag_model, {"Uno": 0, "Uno/Dos": 1})

    def test_delete_l1_used_l2(self):
        "Check deleting a level 1 node with used level 2 leaves both"
        obj1 = test_models.TreeTest.objects.create(name="Test 1", singletag="one")
        test_models.TreeTest.objects.create(name="Test 2", singletag="one/two")

        self.assertTagModel(self.singletag_model, {"one": 1, "one/two": 1})
        obj1.singletag = ""
        obj1.save()
        self.assertTagModel(self.singletag_model, {"one": 0, "one/two": 1})

    def test_get_descendant_count(self):
        "Check the count of the descendants"
        # Make four of everything
        for i in range(4):
            test_models.TreeTest.objects.create(name="Test 1.%d" % i, singletag="One")
            test_models.TreeTest.objects.create(
                name="Test 2.%d" % i, singletag="One/Two"
            )
            test_models.TreeTest.objects.create(
                name="Test 3.%d" % i, singletag="One/Two/Three"
            )
        self.assertTagModel(
            self.singletag_model, {"One": 4, "One/Two": 4, "One/Two/Three": 4}
        )

        # Check the counts
        t1 = self.singletag_model.objects.get(name="One")
        t2 = self.singletag_model.objects.get(name="One/Two")
        t3 = self.singletag_model.objects.get(name="One/Two/Three")
        self.assertEqual(t1.descendant_count, 8)
        self.assertEqual(t2.descendant_count, 4)
        self.assertEqual(t3.descendant_count, 0)

    def test_get_family_count(self):
        "Check the count of the family - node plus descendants"
        # Make four of everything
        for i in range(4):
            test_models.TreeTest.objects.create(name="Test 1.%d" % i, singletag="One")
            test_models.TreeTest.objects.create(
                name="Test 2.%d" % i, singletag="One/Two"
            )
            test_models.TreeTest.objects.create(
                name="Test 3.%d" % i, singletag="One/Two/Three"
            )
        self.assertTagModel(
            self.singletag_model, {"One": 4, "One/Two": 4, "One/Two/Three": 4}
        )

        # Check the counts
        t1 = self.singletag_model.objects.get(name="One")
        t2 = self.singletag_model.objects.get(name="One/Two")
        t3 = self.singletag_model.objects.get(name="One/Two/Three")
        self.assertEqual(t1.family_count, 12)
        self.assertEqual(t2.family_count, 8)
        self.assertEqual(t3.family_count, 4)
