Esempio n. 1
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
Esempio n. 2
0
def test_article_admin_original_add_validation(db, admin_client):
    """
    Just add an original in different language should work.
    """
    # Create new objects
    obj_fr = ArticleFactory(language="fr")
    obj_en = ArticleFactory(language="en")

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

    data["original"] = obj_en

    f = ArticleAdminForm(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()
Esempio n. 3
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]",
    ]
Esempio n. 4
0
def test_article_admin_original_change_validation(db, admin_client):
    """
    Changing language should not allow to trick constraint on original relation
    which must be in different language.
    """
    # Create new objects
    obj_en = ArticleFactory(language="en")
    obj_fr = ArticleFactory(language="fr", original=obj_en)

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

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

    assert f.is_valid() is False
    assert compact_form_errors(f) == {
        "language": ["invalid"],
        "original": ["invalid"],
    }
Esempio n. 5
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
Esempio n. 6
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
Esempio n. 7
0
    def create_articles(self, authors=[], categories=[]):
        """
        Create Article objects required length from factory.
        """
        created = []

        self.stdout.write(
            self.style.SUCCESS("* Creating {} articles".format(self.article_length))
        )

        for i in range(1, self.article_length+1):
            title = self.faker.unique.sentence(nb_words=5)
            slug = slugify(title)

            obj = ArticleFactory(
                title=title,
                slug=slug,
                fill_authors=random.sample(
                    authors,
                    random.randint(1, self.author_length),
                ),
                fill_categories=random.sample(
                    categories,
                    random.randint(1, self.category_length),
                ),
                fill_related=random.sample(
                    created,
                    random.randint(0, len(created)),
                ),
            )

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

        return created
Esempio n. 8
0
def test_article_view_detail_draft(db, admin_client, client):
    """
    Draft article is only reachable for admin in 'admin mode'.
    """
    user = AuthorFactory()
    instance = ArticleFactory(status=STATUS_DRAFT)

    # With default behavior a draft is not available no matter it's for an admin or not
    response = client.get(instance.get_absolute_url())
    assert response.status_code == 404

    response = admin_client.get(instance.get_absolute_url())
    assert response.status_code == 404

    # Admin mode behavior do not work for non admin users
    response = client.get(instance.get_absolute_url(), {'admin': 1})
    assert response.status_code == 404

    client.force_login(user)
    response = client.get(instance.get_absolute_url(), {'admin': 1})
    assert response.status_code == 404

    # Admin mode behavior only work for admin users
    response = admin_client.get(instance.get_absolute_url(), {'admin': 1})
    assert response.status_code == 200
Esempio n. 9
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"],
    }
Esempio n. 10
0
def test_article_model_file_management(db):
    """
    Article 'cover' and 'image' field file management 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 = ArticleFactory(
        cover=create_image_file(filename="machin.png"),
        image=create_image_file(filename="ping_image.png"),
        fill_categories=False,
    )
    pong = ArticleFactory(
        cover=create_image_file(filename="machin.png"),
        image=create_image_file(filename="pong_image.png"),
        fill_categories=False,
    )

    # Memorize some data to use after deletion
    ping_cover_path = ping.cover.path
    ping_image_path = ping.image.path
    pong_cover_path = pong.cover.path
    pong_image_path = pong.image.path

    # Delete object
    ping.delete()

    # Files are deleted along their object
    assert os.path.exists(ping_cover_path) is False
    assert os.path.exists(ping_image_path) is False
    # Paranoiac mode: other existing similar filename (as uploaded) is conserved
    # (since Django rename file with a unique hash if filename alread exist)
    assert os.path.exists(pong_cover_path) is True

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

    # During pre save signal, old file is removed from FS and new one is left
    # untouched
    assert os.path.exists(pong_cover_path) is False
    assert os.path.exists(pong_image_path) is False
    assert os.path.exists(pong.cover.path) is True
    assert os.path.exists(pong.image.path) is True
Esempio n. 11
0
def test_article_admin_detail(db, admin_client):
    """
    Article model admin detail view should not raise error on GET request.
    """
    obj = ArticleFactory()

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

    assert response.status_code == 200
Esempio n. 12
0
def test_article_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_a = ArticleFactory(language="fr")
    obj_b = ArticleFactory(language="en")

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

    # 1) Edit to set original on 'obj_b', everything is ok
    data["original"] = obj_b

    f = ArticleAdminForm(data, instance=obj_a)

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

    obj_a = f.save()

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

    # Validation is raised on language field
    assert f.is_valid() is False
    assert compact_form_errors(f) == {
        "language": ["invalid"],
        "original": ["invalid"],
    }
Esempio n. 13
0
def test_article_admin_related_create_validation(db, admin_client):
    """
    Admin create form should not allow to set related article with a different
    language.
    """
    # Create new objects
    obj_en = ArticleFactory(language="en")
    obj_fr = ArticleFactory(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 related article in different language, raise error
    data["related"] = [obj_en.id]

    f = ArticleAdminForm(data)

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

    # 2) Correctly add related article with same language, should work
    data["related"] = [obj_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.related.all().count() == 1
Esempio n. 14
0
def test_article_detail_404(db, client):
    """
    Try to reach unexisting article should return a 404 response.
    """
    article = ArticleFactory(title="article1")

    url = "/{article_pk}/1/".format(article_pk=article.id)

    response = client.get(url)

    assert response.status_code == 404
Esempio n. 15
0
def test_article_view_detail_published(db, admin_client, client):
    """
    Published article is reachable from anyone.
    """
    instance = ArticleFactory()

    response = client.get(instance.get_absolute_url())
    assert response.status_code == 200

    response = admin_client.get(instance.get_absolute_url())
    assert response.status_code == 200
Esempio n. 16
0
def test_article_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 = ArticleFactory(
        cover=None,
        image=None,
        fill_categories=True,
        fill_authors=True,
    )

    # Fields we don't want to post anything
    ignored_fields = ["id", "relations", "article"]

    # Build POST data from object field values
    data = {}
    fields = [
        f.name for f in Article._meta.get_fields()
        if f.name not in ignored_fields
    ]
    for name in fields:
        value = getattr(obj, name)
        # M2M are special ones since form expect only a list of IDs
        if name in ("categories", "authors", "related"):
            data[name] = value.values_list("id", flat=True)
        else:
            data[name] = value

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

    f = ArticleAdminForm(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
    assert os.path.exists(obj.image.path) is True
Esempio n. 17
0
def test_article_view_detail_private(db, client):
    """
    Private article is reachable only for authenticated users.
    """
    user = AuthorFactory()
    instance = ArticleFactory(private=True)

    response = client.get(instance.get_absolute_url())
    assert response.status_code == 404

    client.force_login(user)
    response = client.get(instance.get_absolute_url())
    assert response.status_code == 200
Esempio n. 18
0
def test_article_view_detail_publication(db, admin_client, client):
    """
    Publication criteria should be respected to view an Article, excepted for admin
    mode.
    """
    now = timezone.now()
    past_hour = now - datetime.timedelta(hours=1)

    instance = ArticleFactory(publish_end=past_hour)

    response = client.get(instance.get_absolute_url())
    assert response.status_code == 404

    response = admin_client.get(instance.get_absolute_url(), {'admin': 1})
    assert response.status_code == 200
Esempio n. 19
0
def test_article_last_update(db):
    """
    Model should auto update "last_update" value on each save.
    """
    article = ArticleFactory(
        slug="foo",
    )
    assert article.last_update is not None

    # Save date for usage after change
    last_update = article.last_update

    # Trigger save for auto update
    article.save()

    assert article.last_update > last_update
Esempio n. 20
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]
Esempio n. 21
0
def test_article_detail_content(db, client):
    """
    Article content should be displayed correctly.
    """
    article = ArticleFactory()

    url = "/{article_pk}/".format(article_pk=article.id, )

    response = client.get(url)

    assert response.status_code == 200

    dom = html_pyquery(response)
    article_title = dom.find(".article-detail h2")
    article_content = dom.find(".article-detail div.content")

    assert article_title.text() == article.title
    # Avoid text() method to remove white spaces since content may contain some
    # line breaks
    assert article_content.text(squash_space=False) == article.content
Esempio n. 22
0
def test_article_managers(db):
    """
    Article manager should be able to correctly filter on language and
    publication.
    """
    now = timezone.now()
    yesterday = now - datetime.timedelta(days=1)
    tomorrow = now + datetime.timedelta(days=1)
    # Today 5min sooner to avoid shifting with pytest and factory delays
    today = now - datetime.timedelta(minutes=5)

    # Single language only
    common_kwargs = {
        "publish_date": today.date(),
        "publish_time": today.time(),
        "fill_authors": False,
        "fill_categories": False,
    }
    ArticleFactory(slug="english", language="en", **common_kwargs)
    ArticleFactory(slug="french", language="fr", **common_kwargs)
    ArticleFactory(slug="deutsch", language="de", **common_kwargs)

    # Explicitely non published
    ArticleFactory(slug="niet", status=STATUS_DRAFT, **common_kwargs)

    # English and French
    multilingual_article(
        slug="banana",
        langs=["fr"],
        publish_date=today.date(),
        publish_time=today.time(),
        fill_authors=False,
        fill_categories=False,
    )

    # English and Deutsch translation
    multilingual_article(
        slug="burger",
        langs=["de"],
        publish_date=today.date(),
        publish_time=today.time(),
        fill_authors=False,
        fill_categories=False,
    )

    # Original Deutsch and French translation
    multilingual_article(
        slug="wurst",
        language="de",
        langs=["fr"],
        publish_date=today.date(),
        publish_time=today.time(),
        fill_authors=False,
        fill_categories=False,
    )

    # All languages and available for publication
    multilingual_article(
        slug="cheese",
        langs=["fr", "de"],
        publish_date=today.date(),
        publish_time=today.time(),
        fill_authors=False,
        fill_categories=False,
    )
    multilingual_article(
        slug="yesterday",
        langs=["fr", "de"],
        publish_date=yesterday.date(),
        publish_time=yesterday.time(),
        fill_authors=False,
        fill_categories=False,
    )
    # All lang and publish ends tomorrow, still available for publication
    multilingual_article(
        slug="shortlife-today",
        langs=["fr", "de"],
        publish_date=today.date(),
        publish_time=today.time(),
        publish_end=tomorrow,
        fill_authors=False,
        fill_categories=False,
    )
    # All lang but not available for publication
    multilingual_article(
        slug="tomorrow",
        langs=["fr", "de"],
        publish_date=tomorrow.date(),
        publish_time=tomorrow.time(),
        fill_authors=False,
        fill_categories=False,
    )
    multilingual_article(
        slug="invalid-yesterday",
        langs=["fr", "de"],
        publish_date=today.date(),
        publish_time=today.time(),
        publish_end=yesterday,
        fill_authors=False,
        fill_categories=False,
    )

    # Check all english articles
    assert Article.objects.get_for_lang().count() == 9

    # Check all french articles
    assert Article.objects.get_for_lang("fr").count() == 8

    # Check all french articles
    assert Article.objects.get_for_lang("de").count() == 8

    # Check all published
    assert Article.objects.get_published().count() == 18

    # Check all unpublished
    assert Article.objects.get_unpublished().count() == 7

    # Check all english published
    q_en_published = Article.objects.get_for_lang().get_published()
    assert queryset_values(q_en_published) == [
        {
            "slug": "banana",
            "language": "en"
        },
        {
            "slug": "burger",
            "language": "en"
        },
        {
            "slug": "cheese",
            "language": "en"
        },
        {
            "slug": "english",
            "language": "en"
        },
        {
            "slug": "shortlife-today",
            "language": "en"
        },
        {
            "slug": "yesterday",
            "language": "en"
        },
    ]

    # Check all french published
    q_fr_published = Article.objects.get_for_lang("fr").get_published()
    assert queryset_values(q_fr_published) == [
        {
            "slug": "banana",
            "language": "fr"
        },
        {
            "slug": "cheese",
            "language": "fr"
        },
        {
            "slug": "french",
            "language": "fr"
        },
        {
            "slug": "shortlife-today",
            "language": "fr"
        },
        {
            "slug": "wurst",
            "language": "fr"
        },
        {
            "slug": "yesterday",
            "language": "fr"
        },
    ]

    # Check all deutsch published
    q_de_published = Article.objects.get_for_lang("de").get_published()
    assert queryset_values(q_de_published) == [
        {
            "slug": "burger",
            "language": "de"
        },
        {
            "slug": "cheese",
            "language": "de"
        },
        {
            "slug": "deutsch",
            "language": "de"
        },
        {
            "slug": "shortlife-today",
            "language": "de"
        },
        {
            "slug": "wurst",
            "language": "de"
        },
        {
            "slug": "yesterday",
            "language": "de"
        },
    ]
Esempio n. 23
0
def test_article_constraints_model(db):
    """
    Article contraints should be respected at model validation level.

    We use factory build strategy to avoid automatic creation so we can test
    "full_clean" raise a validation error (to ensure constraint is not only
    enforced at DB level)
    """
    now = timezone.now()

    # Get a same date from "now" but shifted from an hour,
    # default to +1 hour but if it leads to a different date (like when
    # executing tests at 23:00, date will be the next day), shift to -1 hour
    shifted_time = now + datetime.timedelta(hours=1)
    if shifted_time.date() > now.date():
        shifted_time = now - datetime.timedelta(hours=1)

    # Base original objects
    bar = ArticleFactory(
        slug="bar",
        publish_date=now.date(),
        publish_time=now.time(),
        fill_authors=False,
        fill_categories=False,
    )
    # A translation
    ArticleFactory(
        slug="bar",
        language="fr",
        original=bar,
        fill_authors=False,
        fill_categories=False,
    )

    # Constraint unique combo date+slug+lang
    builded = ArticleFactory.build(
        slug="bar",
        publish_date=now.date(),
        publish_time=now.time(),
        fill_authors=False,
        fill_categories=False,
    )
    with pytest.raises(ValidationError) as excinfo:
        builded.full_clean()

    assert excinfo.value.message_dict == {
        "__all__": [
            "Article with this Publication date, Slug and Language already "
            "exists."
        ],
    }

    # Constraint unique combo original+lang
    builded = ArticleFactory.build(
        slug="zap",
        language="fr",
        original=bar,
        publish_date=now.date(),
        publish_time=now.time(),
        fill_authors=False,
        fill_categories=False,
    )
    with pytest.raises(ValidationError) as excinfo:
        builded.full_clean()

    assert excinfo.value.message_dict == {
        "__all__": ["Article with this Original and Language already exists."],
    }

    # Combination of all constraints
    builded = ArticleFactory.build(
        slug="bar",
        language="fr",
        original=bar,
        publish_date=now.date(),
        publish_time=now.time(),
        fill_authors=False,
        fill_categories=False,
    )
    with pytest.raises(ValidationError) as excinfo:
        builded.full_clean()

    assert excinfo.value.message_dict == {
        "__all__": [
            "Article with this Publication date, Slug and Language already "
            "exists.",
            "Article with this Original and Language already exists.",
        ],
    }
Esempio n. 24
0
def test_article_constraints_db(db):
    """
    Article contraints should be respected at database level.
    """
    now = timezone.now()
    tomorrow = now + datetime.timedelta(days=1)

    # Get a same date from "now" but shifted from an hour,
    # default to +1 hour but if it leads to a different date (like when
    # executing tests at 23:00, date will be the next day), shift to -1 hour
    shifted_time = now + datetime.timedelta(hours=1)
    if shifted_time.date() > now.date():
        shifted_time = now - datetime.timedelta(hours=1)

    # Base original objects
    bar = ArticleFactory(
        slug="bar",
        publish_date=now.date(),
        publish_time=now.time(),
        fill_authors=False,
        fill_categories=False,
    )
    ArticleFactory(
        slug="pong",
        publish_date=now.date(),
        publish_time=now.time(),
        fill_authors=False,
        fill_categories=False,
    )

    # We can have an identical slug on the same date for a different
    # language.
    # Note than original is just a marker to define an object as a translation
    # of "original" relation object.
    ArticleFactory(
        slug="bar",
        language="fr",
        original=bar,
        publish_date=now.date(),
        publish_time=now.time(),
        fill_authors=False,
        fill_categories=False,
    )

    # But 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:
            ArticleFactory(
                slug="zap",
                language="fr",
                original=bar,
                publish_date=now.date(),
                publish_time=now.time(),
                fill_authors=False,
                fill_categories=False,
            )
        assert str(excinfo.value) == (
            "UNIQUE constraint failed: "
            "lotus_article.original_id, lotus_article.language")

    # Can't have an identical slug and language on the same date
    with transaction.atomic():
        with pytest.raises(IntegrityError) as excinfo:
            ArticleFactory(
                slug="bar",
                publish_date=now.date(),
                publish_time=now.time(),
                fill_authors=False,
                fill_categories=False,
            )
        assert str(excinfo.value) == (
            "UNIQUE constraint failed: "
            "lotus_article.publish_date, lotus_article.slug, "
            "lotus_article.language")

    # But we can have an identical slug and language on different date
    ArticleFactory(
        slug="bar",
        publish_date=tomorrow.date(),
        publish_time=tomorrow.time(),
        fill_authors=False,
        fill_categories=False,
    )

    # Or identical slug+date+original on different language
    ArticleFactory(
        slug="bar",
        language="de",
        original=bar,
        publish_date=now.date(),
        publish_time=now.time(),
        fill_authors=False,
        fill_categories=False,
    )

    # Combination of constraints (date+slug+lang & original+lang)
    with transaction.atomic():
        with pytest.raises(IntegrityError) as excinfo:
            ArticleFactory(
                slug="bar",
                language="fr",
                original=bar,
                publish_date=now.date(),
                publish_time=now.time(),
                fill_authors=False,
                fill_categories=False,
            )
        # Only the original+language constraint is returned since it raises
        # first and stop other db validation
        assert str(excinfo.value) == (
            "UNIQUE constraint failed: "
            "lotus_article.original_id, lotus_article.language")
Esempio n. 25
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"],
    }
Esempio n. 26
0
def test_article_view_list_publication(db, admin_client, client, user_kind,
                                       client_kwargs, expected):
    """
    View list should respect publication criterias (dates and state, private article and
    order.

    Tested again profiles:

    * non authenticated;
    * non authenticated trying to user admin mode;
    * authenticated user lambda;
    * authenticated user lambda trying to user admin mode;
    * admin without admin mode;
    * admin with admin mode;
    """
    # Available Django clients as a dict to be able to switch on
    client_for = {
        "anonymous": client,
        "user": client,
        "admin": admin_client,
    }

    # Date references
    now = timezone.now()
    yesterday = now - datetime.timedelta(days=1)
    past_hour = now - datetime.timedelta(hours=1)
    tomorrow = now + datetime.timedelta(days=1)
    next_hour = now + datetime.timedelta(hours=1)

    # Create 10 articles (according to pagination limit) with different publication
    # parameters
    # Numerate titles to enforce ordering since articles share the exact same datetimes
    # which would lead to arbitrary order from a session to another
    ArticleFactory(
        title="01. draft yesterday",
        publish_date=yesterday.date(),
        publish_time=yesterday.time(),
        status=STATUS_DRAFT,
    )
    ArticleFactory(
        title="02. published yesterday",
        publish_date=yesterday.date(),
        publish_time=yesterday.time(),
    )
    ArticleFactory(
        title="03. published yesterday, ended one hour ago",
        publish_date=yesterday.date(),
        publish_time=yesterday.time(),
        publish_end=past_hour,
    )
    ArticleFactory(
        title="04. published past hour",
        publish_date=past_hour.date(),
        publish_time=past_hour.time(),
    )
    ArticleFactory(
        title="05. pinned, published past hour",
        publish_date=past_hour.date(),
        publish_time=past_hour.time(),
        pinned=True,
    )
    ArticleFactory(
        title="06. featured, published past hour",
        publish_date=past_hour.date(),
        publish_time=past_hour.time(),
        featured=True,
    )
    ArticleFactory(
        title="07. private, published past hour",
        publish_date=past_hour.date(),
        publish_time=past_hour.time(),
        private=True,
    )
    ArticleFactory(
        title="08. published past hour, end next hour",
        publish_date=past_hour.date(),
        publish_time=past_hour.time(),
        publish_end=next_hour,
    )
    ArticleFactory(
        title="09. publish next hour",
        publish_date=next_hour.date(),
        publish_time=next_hour.time(),
    )
    ArticleFactory(
        title="10. publish next hour, end tomorrow",
        publish_date=next_hour.date(),
        publish_time=next_hour.time(),
        publish_end=tomorrow,
    )

    # Select the right client to use for user kind
    enabled_client = client_for[user_kind]
    # We have to force authenticated user (non admin)
    if user_kind == "user":
        user = AuthorFactory()
        client.force_login(user)

    # Get all available items from HTML page
    urlname = "lotus:article-index"
    response = enabled_client.get(reverse(urlname), client_kwargs)
    assert response.status_code == 200

    # Parse HTML
    dom = html_pyquery(response)
    items = dom.find("#lotus-content .article-list-container .list .item")

    # Get useful content from list items
    content = []
    for item in items:
        title = item.cssselect("h3 > a")[0].text
        # Drop item class since it's useless for test
        classes = [v for v in item.get("class").split() if v != "item"]
        content.append([title, classes])

    assert content == expected
Esempio n. 27
0
def test_author_manager(db):
    """
    Author manager should be able to get all author which have published
    articles and Author method "published_articles" should return all published
    articles for an Author object.
    """
    now = timezone.now()
    tomorrow = now + datetime.timedelta(days=1)
    # Today 5min sooner to avoid shifting with pytest and factory delays
    today = now - datetime.timedelta(minutes=5)

    # Some authors
    picsou = AuthorFactory(username="******")
    donald = AuthorFactory(username="******")
    flairsou = AuthorFactory(username="******")

    # Some articles
    ArticleFactory(
        slug="Klondike",
        publish_date=today.date(),
        publish_time=today.time(),
        fill_authors=[picsou],
    )

    ArticleFactory(
        slug="DuckCity",
        publish_date=today.date(),
        publish_time=today.time(),
        fill_authors=[picsou, donald],
    )

    ArticleFactory(
        slug="Tomorrow",
        publish_date=tomorrow.date(),
        publish_time=tomorrow.time(),
        fill_authors=[donald],
    )

    # Check for author which have published articles
    q_authors_published = Author.lotus_objects.get_published()
    data = queryset_values(q_authors_published,
                           names=["username"],
                           orders=["username"])

    assert data == [{"username": "******"}, {"username": "******"}]

    # Check for published articles for each author
    assert queryset_values(flairsou.articles.get_published()) == []

    assert queryset_values(donald.articles.get_published()) == [
        {
            "language": "en",
            "slug": "DuckCity"
        },
    ]

    assert queryset_values(picsou.articles.get_published()) == [
        {
            "language": "en",
            "slug": "DuckCity"
        },
        {
            "language": "en",
            "slug": "Klondike"
        },
    ]