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"], }
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
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()
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]", ]
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_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
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
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
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
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
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"], }
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
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
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_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
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
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"], }
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
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
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
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]
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
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" }, ]
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.", ], }
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")
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_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" }, ]
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