Beispiel #1
0
def test_article_admin_modelchoice_change_labels(db, admin_client):
    """
    Admin change form should have language names in model choices fields.
    """
    # Create new objects
    CategoryFactory(title="garlic", language="en")
    CategoryFactory(title="ail", language="fr")

    ArticleFactory(title="egg", language="en")
    obj_fr = ArticleFactory(title="baguette", language="fr")
    ArticleFactory(title="omelette", language="fr")

    # Build form and get its simple HTML representation to parse it
    f = ArticleAdminForm({}, instance=obj_fr)
    content = f.as_p()
    dom = html_pyquery(content)

    originals = dom.find("#id_original option")
    assert [item.text for item in originals] == [
        f.fields["original"].empty_label,
        "egg [English]",
    ]

    relateds = dom.find("#id_related option")
    assert [item.text for item in relateds] == [
        "omelette [Français]",
    ]

    categories = dom.find("#id_categories option")
    assert [item.text for item in categories] == [
        "ail [Français]",
    ]
Beispiel #2
0
def test_article_creation(db):
    """
    Factory should correctly create a new object without any errors.
    """
    ping = CategoryFactory(slug="ping")
    pong = CategoryFactory(slug="pong")

    article = ArticleFactory(
        slug="foo",
        fill_categories=[ping, pong],
    )
    assert article.slug == "foo"

    # Check related categories
    results = queryset_values(article.categories.all())

    assert results == [
        {
            "slug": "ping",
            "language": "en"
        },
        {
            "slug": "pong",
            "language": "en"
        },
    ]

    # Ensure no random categories are created when specifically required
    article = ArticleFactory(slug="bar", fill_categories=False)
    assert article.categories.count() == 0
Beispiel #3
0
def test_multilingual_article(db):
    """
    Factory helper should create an original article with its required
    translations.
    """
    ping = CategoryFactory(slug="ping")
    pong = CategoryFactory(slug="pong")

    # Create an article with a FR and DE translations. Also try to create
    # Deutsch translations twice, but "multilingual_article" is safe on unique
    # language.
    created = multilingual_article(
        slug="cheese",
        langs=["fr", "de", "de"],
        fill_categories=[ping, pong],
        contents={"fr": {
            "slug": "fromage",
            "fill_categories": [ping],
        }},
    )

    # Original slug is correct
    assert created["original"].slug == "cheese"

    # There is two related translations
    assert (len(created["translations"]) == 2) is True

    # Required translations have been create
    assert ("fr" in created["translations"]) is True
    assert ("de" in created["translations"]) is True

    # French translation have its own slug
    assert created["translations"]["fr"].slug == "fromage"
    # deutsch translation inherit from original slug
    assert created["translations"]["de"].slug == "cheese"

    # Check original categories
    original_categories = queryset_values(created["original"].categories.all())

    assert original_categories == [
        {
            "slug": "ping",
            "language": "en"
        },
        {
            "slug": "pong",
            "language": "en"
        },
    ]

    # Check french translation categories
    fr_categories = queryset_values(
        created["translations"]["fr"].categories.all())

    assert fr_categories == [
        {
            "slug": "ping",
            "language": "en"
        },
    ]
Beispiel #4
0
def test_category_index_basic(db, client):
    """
    Category index view should list every category.
    """
    category1 = CategoryFactory(title="category1")
    category2 = CategoryFactory(title="category2")

    response = client.get("/")

    assert response.status_code == 200

    dom = html_pyquery(response)
    items = dom.find(".category-list li")

    expected_titles = [
        "category1",
        "category2",
    ]

    expected_urls = [
        "/{category_pk}/".format(category_pk=category1.id),
        "/{category_pk}/".format(category_pk=category2.id),
    ]

    assert expected_titles == [item.find("a").text for item in items]

    assert expected_urls == [item.find("a").get("href") for item in items]
Beispiel #5
0
def test_category_admin_original_validation(db, admin_client):
    """
    Changing language should not allow to trick constraint on original relation
    which must be in different language.
    """
    # Create new object without a cover file
    obj_fr = CategoryFactory(language="fr", original=None)
    obj_en = CategoryFactory(language="en", original=None)

    # Build initial POST data
    ignore = ["id", "category", "articles"]
    data = build_post_data_from_object(Category, obj_fr, ignore=ignore)

    # 1) Edit to set original on 'obj_en', everything is ok
    data["original"] = obj_en
    f = CategoryAdminForm(data, instance=obj_fr)

    # No validation errors
    assert f.is_valid() is True
    assert compact_form_errors(f) == {}
    assert obj_fr.original.language != obj_fr.language

    obj_fr = f.save()

    # 2) Switch language to 'EN' should not allow to keep the original relation
    # on 'obj_en' in 'EN' language
    data["language"] = "en"
    f = CategoryAdminForm(data, instance=obj_fr)

    # Validation is raised on language field
    assert f.is_valid() is False
    assert compact_form_errors(f) == {
        "language": ["invalid"],
        "original": ["invalid"],
    }
Beispiel #6
0
def test_article_admin_original_choices(db, admin_client):
    """
    Choices should be limited to some constraints:

    * 'original' field should not list items in same language and not the
      article itself;
    * 'related' field should not list items in different language and not the
      article itself;
    * 'categories' field should not list items in different language;
    """
    # Create new object to check
    obj = ArticleFactory(language="en")
    # Create some objects in same language
    fillers_en = [
        ArticleFactory(language="en"),
        ArticleFactory(language="en"),
    ]
    # Create some other objects in various other languages, these are the only
    # elligible articles for original field choices
    fillers_langs = [
        ArticleFactory(language="fr"),
        ArticleFactory(language="fr"),
        ArticleFactory(language="de"),
    ]
    # Create some categories
    cat_en = CategoryFactory(language="en")
    CategoryFactory(language="fr")

    # Get the obj detail page
    url = get_admin_change_url(obj)
    response = admin_client.get(url)

    assert response.status_code == 200

    dom = html_pyquery(response)

    # Get available 'original' choice ids from their values
    options = dom.find("#id_original option")
    option_ids = [
        int(item.get("value")) for item in options if item.get("value")
    ]

    assert sorted(option_ids) == sorted([item.id for item in fillers_langs])

    # Get available 'related' choice ids from their values
    options = dom.find("#id_related option")
    option_ids = [
        int(item.get("value")) for item in options if item.get("value")
    ]

    assert sorted(option_ids) == sorted([item.id for item in fillers_en])

    # Get available 'categories' choice ids from their values
    options = dom.find("#id_categories option")
    option_ids = [cat_en.id]
Beispiel #7
0
def test_article_creation(db):
    """
    Factory should correctly create a new object without any errors.
    """
    ping = CategoryFactory(slug="ping")
    pong = CategoryFactory(slug="pong")

    # Just a dummy article
    dummy = ArticleFactory(slug="dummy", language="fr")

    # Article with some relation
    article = ArticleFactory(
        slug="foo",
        fill_categories=[ping, pong],
        fill_related=[dummy],
    )
    assert article.slug == "foo"

    # Check related categories
    results = queryset_values(
        article.categories.all()
    )

    assert results == [
        {"slug": "ping", "language": "en"},
        {"slug": "pong", "language": "en"},
    ]

    # Check related articles
    results = queryset_values(
        article.related.all()
    )

    assert results == [
        {"slug": "dummy", "language": "fr"},
    ]

    # Check for reverse relation
    results = queryset_values(
        dummy.relations.all()
    )

    assert results == [
        {"slug": "foo", "language": "en"},
    ]

    # Ensure 'related' is not symmetrical
    assert dummy.related.count() == 0

    # Ensure no random relations are created when not specifically
    # required from "fill_****" methods
    article = ArticleFactory(slug="bar")
    assert article.authors.count() == 0
    assert article.categories.count() == 0
    assert article.related.count() == 0
def test_category_model_file_purge(db):
    """
    Category 'cover' field file should follow correct behaviors:

    * When object is deleted, its files should be delete from filesystem too;
    * When changing file from an object, its previous files (if any) should be
      deleted;
    """
    ping = CategoryFactory(cover=create_image_file(filename="machin.png"))
    pong = CategoryFactory(cover=create_image_file(filename="machin.png"))

    # Memorize some data to use after deletion
    ping_path = ping.cover.path
    pong_path = pong.cover.path

    # Delete object
    ping.delete()

    # File is deleted along its object
    assert os.path.exists(ping_path) is False
    # Paranoiac mode: other existing similar filename (as uploaded) are conserved
    # since Django rename file with a unique hash if filename alread exist, they
    # should not be mistaken
    assert os.path.exists(pong_path) is True

    # Change object file to a new one
    pong.cover = create_image_file(filename="new.png")
    pong.save()

    # During pre save signal, old file is removed from FS and new one is left
    # untouched
    assert os.path.exists(pong_path) is False
    assert os.path.exists(pong.cover.path) is True
Beispiel #9
0
def test_article_view_detail_content(db, admin_client):
    """
    Detail view should contain all expected content and relations.

    Note we are requesting with admin mode to be able to see a draft
    article to check for the "draft" CSS class.

    Also, this does not care about textual content (title, lead, content, etc..).
    """
    picsou = AuthorFactory(first_name="Picsou", last_name="McDuck")
    AuthorFactory(first_name="Flairsou", last_name="Cresus")

    cat_1 = CategoryFactory(title="cat_1")
    CategoryFactory(title="cat_2")
    CategoryFactory(title="cat_3")

    ArticleFactory(title="Foo")
    article_2 = ArticleFactory(title="Bar")
    article_3 = ArticleFactory(
        title="Ping",
        fill_categories=[cat_1],
        fill_related=[article_2],
        fill_authors=[picsou],
        status=STATUS_DRAFT,
        pinned=True,
        featured=True,
        private=True,
    )

    # Get detail HTML page
    response = admin_client.get(article_3.get_absolute_url(), {'admin': 1})
    assert response.status_code == 200

    # Parse HTML response to get content and relations
    dom = html_pyquery(response)
    container = dom.find("#lotus-content .article-detail")[0]
    categories = [
        item.text for item in dom.find("#lotus-content .categories li a")
    ]
    authors = [item.text for item in dom.find("#lotus-content .authors li")]
    relateds = [
        item.text for item in dom.find("#lotus-content .relateds li a")
    ]
    cover = dom.find("#lotus-content .cover img")[0].get("src")
    large_img = dom.find("#lotus-content .image img")[0].get("src")
    classes = sorted(
        [v for v in container.get("class").split() if v != "article-detail"])

    assert categories == ["cat_1"]
    assert authors == ["Picsou McDuck"]
    assert relateds == ["Bar"]
    assert classes == ["draft", "featured", "pinned", "private"]
    assert cover == article_3.cover.url
    assert large_img == article_3.image.url
Beispiel #10
0
def test_category_admin_article_relations_validation(db, admin_client):
    """
    Category admin form should not allow to change language if object already
    have related articles in different language.
    """
    build_fr = CategoryFactory.build()

    # Build initial POST data
    ignore = ["id", "category", "articles"]
    data = build_post_data_from_object(Category, build_fr, ignore=ignore)

    # 1) Set category without any article relation yet
    data["language"] = "fr"

    f = CategoryAdminForm(data)
    assert f.is_valid() is True
    assert compact_form_errors(f) == {}

    cat_fr = f.save()

    # Set an article relation
    ArticleFactory(language="fr", fill_categories=[cat_fr])

    # 2) Change language to a different one from related articles
    data["language"] = "en"

    f = CategoryAdminForm(data, instance=cat_fr)
    assert f.is_valid() is False
    assert compact_form_errors(f) == {
        "language": ["invalid-language"],
    }
Beispiel #11
0
def test_category_admin_change_form(db, admin_client):
    """
    Ensure the admin change form is working well (this should cover add form
    also) and ensure image upload is correct.
    """
    # Create new object without a cover file
    obj = CategoryFactory(cover=None)

    # Build initial POST data
    ignore = ["id", "category", "articles"]
    data = build_post_data_from_object(Category, obj, ignore=ignore)

    file_data = {
        "cover": SimpleUploadedFile(
            "small.gif",
            DUMMY_GIF_BYTES,
            content_type="image/gif"
        ),
    }

    f = CategoryAdminForm(data, file_data, instance=obj)

    # No validation errors
    assert f.is_valid() is True
    assert f.errors.as_data() == {}

    updated_obj = f.save()

    # Check everything has been saved
    assert updated_obj.title == obj.title
    assert os.path.exists(obj.cover.path) is True
def test_category_managers(db):
    """
    Category manager should be able to correctly filter on language.
    """
    # Simple category on default language without translations
    CategoryFactory(slug="foobar")

    # Original category on different language than 'settings.LANGUAGE_CODE' and
    # with a translation for 'settings.LANGUAGE_CODE' lang.
    multilingual_category(
        slug="musique",
        language="fr",
        langs=["en"],
        contents={
            "en": {
                "slug": "music",
            }
        },
    )

    # A category with a french translation inheriting original slug
    multilingual_category(
        slug="food",
        langs=["fr"],
    )

    # A category with french translation with its own slug and deutsch
    # translation inheriting original slug
    multilingual_category(
        slug="recipe",
        langs=["fr", "de"],
        contents={
            "fr": {
                "slug": "recette",
            }
        },
    )

    # Use default language as configured in settings
    assert queryset_values(Category.objects.get_for_lang()) == [
        {"slug": "foobar", "language": "en"},
        {"slug": "food", "language": "en"},
        {"slug": "music", "language": "en"},
        {"slug": "recipe", "language": "en"},
    ]

    # For french language
    assert queryset_values(Category.objects.get_for_lang("fr")) == [
        {"slug": "food", "language": "fr"},
        {"slug": "musique", "language": "fr"},
        {"slug": "recette", "language": "fr"},
    ]

    # For deutsch language
    assert queryset_values(Category.objects.get_for_lang("de")) == [
        {"slug": "recipe", "language": "de"},
    ]
Beispiel #13
0
def test_category_index_pagination(settings, db, client):
    """
    Category index view is paginated from setting limit, not every category is listed
    on the same page.
    """
    # Twice the category pagination limit plus one entry so we can expect three
    # result pages
    category_total = (settings.BLOG_PAGINATION * 2) + 1

    CategoryFactory.create_batch(category_total)

    assert category_total == Category.objects.all().count()

    # First result page
    response = client.get("/")
    assert response.status_code == 200

    dom = html_pyquery(response)
    items = dom.find(".category-list li")
    assert settings.BLOG_PAGINATION == len(items)
    # Check pagination is correct
    pages = dom.find(".pagination a")
    assert 3 == len(pages)

    # Second result page
    response = client.get("/?page=2")
    assert response.status_code == 200

    dom = html_pyquery(response)
    items = dom.find(".category-list li")
    assert settings.BLOG_PAGINATION == len(items)
    # Check current page is correct
    active = dom.find(".pagination a.active")
    assert 1 == len(active)
    assert "2" == active.text()

    # Third result page
    response = client.get("/?page=3")

    assert response.status_code == 200

    dom = html_pyquery(response)
    items = dom.find(".category-list li")
    assert 1 == len(items)
Beispiel #14
0
def test_category_admin_detail(db, admin_client):
    """
    Category model admin detail view should not raise error on GET request.
    """
    obj = CategoryFactory()

    url = get_admin_change_url(obj)
    response = admin_client.get(url)

    assert response.status_code == 200
Beispiel #15
0
def test_category_admin_modelchoice_create_labels(db, admin_client):
    """
    Admin create form should have language names in model choices fields.
    """
    # Create new objects
    CategoryFactory(title="egg", language="en")
    CategoryFactory(title="baguette", language="fr")

    # Build form and get its simple HTML representation to parse it
    f = CategoryAdminForm()
    content = f.as_p()
    dom = html_pyquery(content)

    originals = dom.find("#id_original option")
    assert [item.text for item in originals] == [
        f.fields["original"].empty_label,
        "baguette [Français]",
        "egg [English]",
    ]
Beispiel #16
0
def test_article_admin_category_create_validation(db, admin_client):
    """
    Admin create form should not allow to set category with a different
    language.
    """
    cat_en = CategoryFactory(language="en")
    cat_fr = CategoryFactory(language="fr")
    # Build object to create
    build_fr = ArticleFactory.build(language="fr")

    # Build initial POST data
    ignore = [
        "id",
        "relations",
        "article",
        "authors",
        "related",
        "categories",
    ]
    data = build_post_data_from_object(Article, build_fr, ignore=ignore)

    # 1) Try to add category in different language, raise error
    data["categories"] = [cat_en.id]

    f = ArticleAdminForm(data)

    assert f.is_valid() is False
    assert compact_form_errors(f) == {
        "categories": ["invalid-categories"],
    }

    # 2) Correctly add category with same language, should work
    data["categories"] = [cat_fr.id]

    f = ArticleAdminForm(data)

    assert f.is_valid() is True
    assert compact_form_errors(f) == {}

    obj_fr_bis = f.save()

    assert obj_fr_bis.categories.all().count() == 1
Beispiel #17
0
def test_category_admin_original_choices(db, admin_client):
    """
    Choices for 'original' should not list item in same language and not the
    category itself.
    """
    # Create new object to check
    obj = CategoryFactory(language="en")
    # Create some objects in same language
    CategoryFactory(language="en")
    CategoryFactory(language="en")
    # Create some other objects in various other languages, these are the only
    # elligible categories for original field choices
    fillers_langs = [
        CategoryFactory(language="fr"),
        CategoryFactory(language="fr"),
        CategoryFactory(language="de"),
    ]

    # Get the obj detail page
    url = get_admin_change_url(obj)
    response = admin_client.get(url)

    assert response.status_code == 200

    dom = html_pyquery(response)

    # Get available choice ids from their values
    options = dom.find("#id_original option")
    option_ids = [int(item.get("value")) for item in options if item.get("value")]

    assert sorted(option_ids) == sorted([item.id for item in fillers_langs])
Beispiel #18
0
def test_category_detail_article_pagination(settings, db, client):
    """
    Category index detail has a paginated list of article and so not every category
    articles are listed on the same page.
    """
    category1 = CategoryFactory(title="category1")

    category_url = "/{category_pk}/".format(category_pk=category1.id)

    article_total = (settings.ARTICLE_PAGINATION * 2) + 1

    ArticleFactory.create_batch(article_total, category=category1)

    assert article_total == Article.objects.all().count()

    # First result page
    response = client.get(category_url)
    assert response.status_code == 200

    dom = html_pyquery(response)
    items = dom.find(".article-list li")
    assert settings.ARTICLE_PAGINATION == len(items)
    # Check pagination is correct
    pages = dom.find(".pagination a")
    assert 3 == len(pages)

    # Second result page
    response = client.get(category_url + "?page=2")
    assert response.status_code == 200

    dom = html_pyquery(response)
    items = dom.find(".article-list li")
    assert settings.ARTICLE_PAGINATION == len(items)
    # Check current page is correct
    active = dom.find(".pagination a.active")
    assert 1 == len(active)
    assert "2" == active.text()

    # Third result page
    response = client.get(category_url + "?page=3")

    assert response.status_code == 200

    dom = html_pyquery(response)
    items = dom.find(".article-list li")
    assert 1 == len(items)
def test_category_constraints(db):
    """
    Category contraints should be respected.
    """
    # Base original objects
    bar = CategoryFactory(slug="bar", )
    CategoryFactory(slug="pong", )

    # We can have an identical slug for a different language.
    # Note than original is just a marker to define an object as a translation
    # of "original" relation object.
    CategoryFactory(
        slug="bar",
        language="fr",
        original=bar,
    )

    # But not an identical slug on the same language
    with transaction.atomic():
        with pytest.raises(IntegrityError) as excinfo:
            CategoryFactory(
                slug="bar",
                language="en",
            )
        assert str(
            excinfo.value) == ("UNIQUE constraint failed: "
                               "lotus_category.slug, lotus_category.language")

    # And only an unique language for the same original object is allowed since
    # there can't be two translations for the same language.
    with transaction.atomic():
        with pytest.raises(IntegrityError) as excinfo:
            CategoryFactory(
                slug="zap",
                language="fr",
                original=bar,
            )
        assert str(excinfo.value) == (
            "UNIQUE constraint failed: "
            "lotus_category.original_id, lotus_category.language")

    # Combination of constraints (slug+lang & original+lang)
    with transaction.atomic():
        with pytest.raises(IntegrityError) as excinfo:
            CategoryFactory(
                slug="bar",
                language="fr",
                original=bar,
            )
        # This is the original+language constraint which raise first
        assert str(excinfo.value) == (
            "UNIQUE constraint failed: "
            "lotus_category.original_id, lotus_category.language")
Beispiel #20
0
def test_category_detail_no_article(db, client):
    """
    Without any related article, category detail view should just contains its
    content and return the empty text for article list.
    """
    category1 = CategoryFactory(title="category1")

    url = "/{category_pk}/".format(category_pk=category1.id)

    response = client.get(url)

    assert response.status_code == 200

    dom = html_pyquery(response)

    category_title = dom.find(".category-detail h2")
    assert category_title.text() == category1.title

    content = dom.find(".article-list li")[0].text
    assert "No articles yet." == content
Beispiel #21
0
    def create_categories(self):
        """
        Create Category objects required length from factory.
        """
        created = []

        self.stdout.write(
            self.style.SUCCESS("* Creating {} categories".format(self.category_length))
        )

        for i in range(1, self.category_length+1):
            title = self.faker.unique.company()
            slug = slugify(title)

            obj = CategoryFactory(
                title=title,
                slug=slug,
            )

            self.stdout.write("  - Category: {}".format(obj.title))
            created.append(obj)

        return created
def test_category_get_by_lang(db):
    """
    Demonstrate how we can get categories for original language and
    translations.
    """
    created_foobar = CategoryFactory(slug="foobar")

    created_omelette = multilingual_category(
        slug="food",
        langs=["fr"],
    )

    created_cheese = multilingual_category(
        slug="recipe",
        langs=["fr", "de"],
        contents={"fr": {
            "slug": "recette",
        }},
    )

    # Get full total all languages mixed and without any filtering
    assert queryset_values(Category.objects.all()) == [
        {
            "slug": "foobar",
            "language": "en"
        },
        {
            "slug": "food",
            "language": "en"
        },
        {
            "slug": "food",
            "language": "fr"
        },
        {
            "slug": "recette",
            "language": "fr"
        },
        {
            "slug": "recipe",
            "language": "de"
        },
        {
            "slug": "recipe",
            "language": "en"
        },
    ]

    # Get available categories for a required language
    assert Category.objects.filter(language="en").count() == 3
    assert Category.objects.filter(language="fr").count() == 2
    assert Category.objects.filter(language="de").count() == 1

    # Get only originals
    results = Category.objects.filter(original__isnull=True)
    assert queryset_values(results) == [
        {
            "slug": "foobar",
            "language": "en"
        },
        {
            "slug": "food",
            "language": "en"
        },
        {
            "slug": "recipe",
            "language": "en"
        },
    ]

    # Get only translations
    results = Category.objects.filter(original__isnull=False)
    assert queryset_values(results) == [
        {
            "slug": "food",
            "language": "fr"
        },
        {
            "slug": "recette",
            "language": "fr"
        },
        {
            "slug": "recipe",
            "language": "de"
        },
    ]

    # Get translations from original
    assert created_foobar.category_set.all().count() == 0
    assert created_omelette["original"].category_set.all().count() == 1
    assert created_cheese["original"].category_set.all().count() == 2
Beispiel #23
0
def test_article_admin_category_change_validation(db, admin_client):
    """
    Admin change form should not allow to set category with a different
    language.
    """
    cat_en = CategoryFactory(language="en")
    cat_fr = CategoryFactory(language="fr")
    obj_fr = ArticleFactory(language="fr")

    # Build initial POST data
    ignore = [
        "id",
        "relations",
        "article",
        "authors",
        "related",
        "categories",
    ]
    data = build_post_data_from_object(Article, obj_fr, ignore=ignore)

    # 1) Try to add category in different language, raise error
    data["categories"] = [cat_en.id]

    f = ArticleAdminForm(data, instance=obj_fr)
    assert f.is_valid() is False
    assert compact_form_errors(f) == {
        "categories": ["invalid_choice"],
    }

    # 2) Try to add category in same language, should be ok
    data["categories"] = [cat_fr.id]

    f = ArticleAdminForm(data, instance=obj_fr)
    assert f.is_valid() is True
    assert compact_form_errors(f) == {}

    # Save it for next test part
    obj_fr = f.save()

    # 3) Try again to add category in different language, keep raising
    # error
    data["categories"] = [cat_fr.id, cat_en.id]

    f = ArticleAdminForm(data, instance=obj_fr)

    assert f.is_valid() is False
    assert compact_form_errors(f) == {
        "categories": ["invalid_choice"],
    }

    # 4) Restore working category object in same language
    data["categories"] = [cat_fr.id]
    f = ArticleAdminForm(data, instance=obj_fr)
    obj_fr = f.save()

    # 5) Try to change language with remaining category object in a different
    # language, raise an error
    data["language"] = "en"

    f = ArticleAdminForm(data, instance=obj_fr)

    assert f.is_valid() is False
    assert compact_form_errors(f) == {
        "categories": ["invalid-categories"],
    }
def test_category_creation(db):
    """
    Factory should correctly create a new object without any errors.
    """
    category = CategoryFactory(title="foo")
    assert category.title == "foo"
def test_category_get_articles(db):
    """
    Demonstrate how to get category related articles.
    """
    ping = CategoryFactory()
    pong = CategoryFactory()

    foo = multilingual_article(
        slug="foo",
        langs=["fr"],
        fill_categories=[ping, pong],
        contents={"fr": {
            "slug": "fou",
            "fill_categories": [ping, pong],
        }},
    )

    bar = multilingual_article(
        slug="bar",
        langs=["fr"],
        fill_categories=[ping],
        contents={"fr": {
            "slug": "barre",
            "fill_categories": [ping, pong],
        }},
    )

    moo = multilingual_article(
        slug="moo",
        fill_categories=[ping],
    )

    yeah = multilingual_article(
        slug="yeah",
        langs=["fr"],
        fill_categories=[pong],
        contents={"fr": {
            "slug": "ouais",
            "fill_categories": [ping],
        }},
    )

    nope = multilingual_article(slug="nope")

    ping_articles = [(item.slug, item.language)
                     for item in ping.get_articles()]

    pong_articles = [(item.slug, item.language)
                     for item in pong.get_articles()]

    # Get unordered queryset then order by slug to avoid arbitrary order
    unordered_ping_articles = [
        (item.slug, item.language)
        for item in ping.get_articles(ordered=False).order_by("slug")
    ]

    assert ping_articles == [
        ("moo", "en"),
        ("bar", "en"),
        ("foo", "en"),
    ]

    assert pong_articles == [
        ("yeah", "en"),
        ("foo", "en"),
    ]

    assert unordered_ping_articles == [
        ("bar", "en"),
        ("foo", "en"),
        ("moo", "en"),
    ]