Initial commit

This commit is contained in:
2024-08-27 20:33:44 +02:00
commit 1f1832267d
14794 changed files with 1599592 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
from django.test import TestCase
from wagtail.documents import get_document_model
from wagtail.documents.blocks import DocumentChooserBlock
from .utils import get_test_document_file
class TestDocumentChooserBlock(TestCase):
def test_deconstruct(self):
block = DocumentChooserBlock(required=False)
path, args, kwargs = block.deconstruct()
self.assertEqual(path, "wagtail.documents.blocks.DocumentChooserBlock")
self.assertEqual(args, ())
self.assertEqual(kwargs, {"required": False})
def test_extract_references(self):
Document = get_document_model()
document = Document.objects.create(
title="Test document", file=get_test_document_file()
)
block = DocumentChooserBlock()
self.assertListEqual(
list(block.extract_references(document)),
[(Document, str(document.id), "", "")],
)
# None should not yield any references
self.assertListEqual(list(block.extract_references(None)), [])

View File

@@ -0,0 +1,83 @@
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
from wagtail.documents import get_document_model
from wagtail.test.utils import WagtailTestUtils
Document = get_document_model()
def get_tag_list(document):
return [tag.name for tag in document.tags.all()]
class TestBulkAddTags(WagtailTestUtils, TestCase):
def setUp(self):
self.user = self.login()
self.new_tags = ["first", "second"]
self.documents = [
Document.objects.create(title=f"Test document - {i}") for i in range(1, 6)
]
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtaildocs",
"document",
"add_tags",
),
)
+ "?"
)
for document in self.documents:
self.url += f"id={document.id}&"
self.post_data = {"tags": ",".join(self.new_tags)}
def test_add_tags_with_limited_permissions(self):
self.user.is_superuser = False
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
)
)
self.user.save()
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
html = response.content.decode()
self.assertInHTML(
"<p>You don't have permission to add tags to these documents</p>", html
)
for document in self.documents:
self.assertInHTML(f"<li>{document.title}</li>", html)
self.client.post(self.url, self.post_data)
# New tags should not be added to the documents
for document in self.documents:
self.assertCountEqual(
get_tag_list(Document.objects.get(id=document.id)), []
)
def test_simple(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtaildocs/bulk_actions/confirm_bulk_add_tags.html"
)
def test_add_tags(self):
# Make post request
response = self.client.post(self.url, self.post_data)
# User should be redirected back to the index
self.assertEqual(response.status_code, 302)
# New tags should not be added to the documents
for document in self.documents:
self.assertCountEqual(
get_tag_list(Document.objects.get(id=document.id)), self.new_tags
)

View File

@@ -0,0 +1,84 @@
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
from wagtail.documents import get_document_model
from wagtail.models import Collection
from wagtail.test.utils import WagtailTestUtils
Document = get_document_model()
class TestBulkAddDocumentsToCollection(WagtailTestUtils, TestCase):
def setUp(self):
self.user = self.login()
self.root_collection = Collection.get_first_root_node()
self.dest_collection = self.root_collection.add_child(name="Destination")
self.documents = [
Document.objects.create(title=f"Test document - {i}") for i in range(1, 6)
]
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtaildocs",
"document",
"add_to_collection",
),
)
+ "?"
)
for document in self.documents:
self.url += f"id={document.id}&"
self.post_data = {"collection": str(self.dest_collection.id)}
def test_add_to_collection_with_limited_permissions(self):
self.user.is_superuser = False
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
)
)
self.user.save()
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
html = response.content.decode()
self.assertInHTML(
"<p>You don't have permission to add these documents to a collection</p>",
html,
)
for document in self.documents:
self.assertInHTML(f"<li>{document.title}</li>", html)
self.client.post(self.url, self.post_data)
# Documents should not be moved to new collection
for document in self.documents:
self.assertEqual(
Document.objects.get(id=document.id).collection_id,
self.root_collection.id,
)
def test_simple(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtaildocs/bulk_actions/confirm_bulk_add_to_collection.html"
)
def test_add_to_collection(self):
# Make post request
response = self.client.post(self.url, self.post_data)
# User should be redirected back to the index
self.assertEqual(response.status_code, 302)
# Documents should be moved to new collection
for document in self.documents:
self.assertEqual(
Document.objects.get(id=document.id).collection_id,
self.dest_collection.id,
)

View File

@@ -0,0 +1,88 @@
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
from wagtail.documents import get_document_model
from wagtail.test.utils import WagtailTestUtils
Document = get_document_model()
class TestDocumentBulkDeleteView(WagtailTestUtils, TestCase):
def setUp(self):
self.user = self.login()
# Create documents to delete
self.documents = [
Document.objects.create(title=f"Test document - {i}") for i in range(1, 6)
]
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtaildocs",
"document",
"delete",
),
)
+ "?"
)
for document in self.documents:
self.url += f"id={document.id}&"
def test_delete_with_limited_permissions(self):
self.user.is_superuser = False
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
)
)
self.user.save()
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
html = response.content.decode()
self.assertInHTML(
"<p>You don't have permission to delete these documents</p>", html
)
for document in self.documents:
self.assertInHTML(f"<li>{document.title}</li>", html)
response = self.client.post(self.url)
# User should be redirected back to the index
self.assertEqual(response.status_code, 302)
# Documents should not be deleted
for document in self.documents:
self.assertTrue(Document.objects.filter(id=document.id).exists())
def test_simple(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtaildocs/bulk_actions/confirm_bulk_delete.html"
)
def test_delete(self):
# Make post request
response = self.client.post(self.url)
# User should be redirected back to the index
self.assertEqual(response.status_code, 302)
# Documents should be deleted
for document in self.documents:
self.assertFalse(Document.objects.filter(id=document.id).exists())
def test_usage_link(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtaildocs/bulk_actions/confirm_bulk_delete.html"
)
for document in self.documents:
self.assertContains(response, document.usage_url)
# usage count should be printed for each document
self.assertContains(response, "Used 0 times", count=5)

View File

@@ -0,0 +1,189 @@
from django.contrib.auth.models import Group
from django.core.files.base import ContentFile
from django.test import TestCase, override_settings
from django.urls import reverse
from wagtail.documents.models import Document
from wagtail.models import Collection, CollectionViewRestriction
from wagtail.test.utils import WagtailTestUtils
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
class TestCollectionPrivacyDocument(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def setUp(self):
self.fake_file = ContentFile(b"A boring example document")
self.fake_file.name = "test.txt"
self.collection = Collection.objects.get(id=2)
self.password_collection = Collection.objects.get(name="Password protected")
self.login_collection = Collection.objects.get(name="Login protected")
self.group_collection = Collection.objects.get(name="Group protected")
self.view_restriction = CollectionViewRestriction.objects.get(
collection=self.password_collection
)
self.event_editors_group = Group.objects.get(name="Event editors")
def get_document(self, collection):
secret_document = Document.objects.create(
title="Test document",
file=self.fake_file,
collection=collection,
)
url = reverse(
"wagtaildocs_serve", args=(secret_document.id, secret_document.filename)
)
response = self.client.get(url)
return response, quote(url)
def test_anonymous_user_must_authenticate(self):
secret_document = Document.objects.create(
title="Test document",
file=self.fake_file,
collection=self.password_collection,
)
doc_url = reverse(
"wagtaildocs_serve", args=(secret_document.id, secret_document.filename)
)
response = self.client.get(doc_url)
self.assertEqual(
response.templates[0].name, "wagtaildocs/password_required.html"
)
submit_url = reverse(
"wagtaildocs_authenticate_with_password", args=[self.view_restriction.id]
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '<form action="%s"' % submit_url)
self.assertContains(
response,
'<input id="id_return_url" name="return_url" type="hidden" value="{}" />'.format(
doc_url
),
html=True,
)
# posting the wrong password should redisplay the password page
response = self.client.post(
submit_url,
{
"password": "wrongpassword",
"return_url": doc_url,
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.templates[0].name, "wagtaildocs/password_required.html"
)
self.assertContains(response, '<form action="%s"' % submit_url)
# posting the correct password should redirect back to return_url
response = self.client.post(
submit_url,
{
"password": "swordfish",
"return_url": doc_url,
},
)
self.assertRedirects(response, doc_url)
# now requests to the documents url should pass authentication
self.client.get(doc_url)
self.client.logout()
# posting an invalid return_url will redirect to default login redirect
with self.settings(LOGIN_REDIRECT_URL="/"):
response = self.client.post(
submit_url,
{
"password": "swordfish",
"return_url": "https://invaliddomain.com",
},
)
self.assertRedirects(response, "/")
@override_settings(
WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE="tests/custom_docs_password_required.html"
)
def test_anonymous_user_must_authenticate_with_custom_password_required_template(
self
):
secret_document = Document.objects.create(
title="Test document",
file=self.fake_file,
collection=self.password_collection,
)
doc_url = reverse(
"wagtaildocs_serve", args=(secret_document.id, secret_document.filename)
)
response = self.client.get(doc_url)
self.assertNotEqual(
response.templates[0].name, "wagtaildocs/password_required.html"
)
self.assertEqual(
response.templates[0].name, "tests/custom_docs_password_required.html"
)
def test_group_restriction_with_anonymous_user(self):
response, url = self.get_document(self.group_collection)
self.assertRedirects(response, f"/_util/login/?next={url}")
def test_group_restriction_with_unpermitted_user(self):
self.login(username="eventmoderator", password="password")
response, url = self.get_document(self.group_collection)
self.assertRedirects(response, f"/_util/login/?next={url}")
def test_group_restriction_with_permitted_user(self):
self.login(username="eventeditor", password="password")
response, url = self.get_document(self.group_collection)
self.assertEqual(response.status_code, 200)
def test_group_restriction_with_superuser(self):
self.login(username="superuser", password="password")
response, url = self.get_document(self.group_collection)
self.assertEqual(response.status_code, 200)
def test_login_restriction_with_anonymous_user(self):
response, url = self.get_document(self.login_collection)
self.assertRedirects(response, f"/_util/login/?next={url}")
def test_login_restriction_with_logged_in_user(self):
self.login(username="eventmoderator", password="password")
response, url = self.get_document(self.login_collection)
self.assertEqual(response.status_code, 200)
def test_set_shared_password_with_logged_in_user(self):
self.login()
response = self.client.get(
reverse("wagtailadmin_collections:set_privacy", args=(self.collection.id,)),
)
input_el = self.get_soup(response.content).select_one("[data-field-input]")
self.assertEqual(response.status_code, 200)
# check that input option for password is visible
self.assertIn("password", response.context["form"].fields)
# check that the option for password is visible
self.assertIsNotNone(input_el)
@override_settings(
WAGTAILDOCS_PRIVATE_COLLECTION_OPTIONS={"SHARED_PASSWORD": False}
)
def test_unset_shared_password_with_logged_in_user(self):
self.login()
response = self.client.get(
reverse("wagtailadmin_collections:set_privacy", args=(self.collection.id,)),
)
self.assertEqual(response.status_code, 200)
self.assertNotIn("password", response.context["form"].fields)
self.assertFalse(
response.context["form"]
.fields["restriction_type"]
.valid_value(CollectionViewRestriction.PASSWORD)
)

View File

@@ -0,0 +1,280 @@
from django.contrib.auth.models import Group, Permission
from django.test import TestCase
from django.urls import reverse
from wagtail.documents.models import Document
from wagtail.models import (
Collection,
GroupCollectionPermission,
Page,
get_root_collection_id,
)
from wagtail.test.utils import WagtailTestUtils
class TestChooser(WagtailTestUtils, TestCase):
"""Test chooser panel rendered by `wagtaildocs_chooser:choose` view"""
_NO_DOCS_TEXT = "You haven't uploaded any documents."
_NO_COLLECTION_DOCS_TEXT = "You haven't uploaded any documents in this collection."
_UPLOAD_ONE_TEXT = "upload one now" # text from the link that opens upload form
def setUp(self):
self.root_page = Page.objects.get(id=2)
def login_as_superuser(self):
self.login()
def login_as_editor(self):
# Create group with access to admin
editors_group = Group.objects.create(name="The Editors")
access_admin_perm = Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
)
editors_group.permissions.add(access_admin_perm)
# Grant "choose" permission to the Editors group on the Root Collection.
choose_document_permission = Permission.objects.get(
content_type__app_label="wagtaildocs", codename="choose_document"
)
GroupCollectionPermission.objects.create(
group=editors_group,
collection=Collection.objects.get(depth=1),
permission=choose_document_permission,
)
# Create a non-superuser editor
user = self.create_user(username="editor", password="password")
user.groups.add(editors_group)
# Log in as a non-superuser editor
self.login(user)
def login_as_baker(self):
# Create group with access to admin and Chooser permission on one Collection, but not another.
bakers_group = Group.objects.create(name="Bakers")
access_admin_perm = Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
)
bakers_group.permissions.add(access_admin_perm)
# Create the "Bakery" Collection and grant "choose" permission to the Bakers group.
root = Collection.objects.get(id=get_root_collection_id())
bakery_collection = root.add_child(instance=Collection(name="Bakery"))
GroupCollectionPermission.objects.create(
group=bakers_group,
collection=bakery_collection,
permission=Permission.objects.get(
content_type__app_label="wagtaildocs", codename="choose_document"
),
)
# Create the "Office" Collection and _don't_ grant any permissions to the Bakers group.
root.add_child(instance=Collection(name="Office"))
# Create a Baker user.
user = self.create_user(username="baker", password="password")
user.groups.add(bakers_group)
# Log in as the baker.
self.login(user)
def get(self, params=None):
return self.client.get(reverse("wagtaildocs_chooser:choose"), params or {})
def test_chooser_docs_exist(self):
# given an editor with access to admin panel
self.login_as_editor()
# and a document in the database
doc_title = "document.pdf"
Document.objects.create(title=doc_title)
# when opening chooser
response = self.get()
# then chooser template is used
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/chooser/chooser.html")
# and document is displayed
self.assertContains(response, doc_title)
# and no hints are displayed
self.assertNotContains(response, self._NO_DOCS_TEXT)
self.assertNotContains(response, self._NO_COLLECTION_DOCS_TEXT)
self.assertNotContains(response, self._UPLOAD_ONE_TEXT)
def test_chooser_only_docs_in_chooseable_collection_appear(self):
# Log in as a baker, who has choose permission on the Bakery but not the Office.
self.login_as_baker()
# And a document to the Bakery and to the Office.
bun_recipe_title = "bun_recipe.pdf"
Document.objects.create(
title=bun_recipe_title, collection=Collection.objects.get(name="Bakery")
)
payroll_title = "payroll.xlsx"
Document.objects.create(
title=payroll_title, collection=Collection.objects.get(name="Office")
)
# Open the doc chooser.
response = self.get()
# Confirm that the chooser opened successfully.
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/chooser/chooser.html")
# Ensure that the bun recipe is visible, but the payroll is not.
self.assertContains(response, bun_recipe_title)
self.assertNotContains(response, payroll_title)
def test_chooser_collection_selector_appears_only_if_multiple_collections_are_choosable(
self,
):
# Log in as a baker, who has choose permission on the Bakery but not the Office.
self.login_as_baker()
# Open the doc chooser.
response = self.get()
# Confirm that the chooser opened successfully.
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/chooser/chooser.html")
# Ensure that the Collection chooser is not visible, because the Baker cannot
# choose from multiple Collections.
self.assertNotContains(response, "Collection")
# Let the Baker choose from the Office Collection.
GroupCollectionPermission.objects.create(
group=Group.objects.get(name="Bakers"),
collection=Collection.objects.get(name="Office"),
permission=Permission.objects.get(
content_type__app_label="wagtaildocs", codename="choose_document"
),
)
# Open the doc chooser again.
response = self.get()
# Confirm that the chooser opened successfully.
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/chooser/chooser.html")
# Ensure that the Collection chooser IS visible, because the Baker can now
# choose from multiple Collections.
self.assertContains(response, "Collection")
def test_chooser_no_docs_upload_allowed(self):
# given a superuser and no documents in the database
self.login_as_superuser()
# when opening chooser
response = self.get()
# then chooser template is used
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/chooser/chooser.html")
# and hint "You haven't uploaded any documents. Why not upload one now?" is displayed
self.assertContains(response, self._NO_DOCS_TEXT)
self.assertContains(response, self._UPLOAD_ONE_TEXT)
def test_chooser_no_docs_upload_forbidden(self):
# given an editor with access to admin panel
# and no documents in the database
self.login_as_editor()
# when opening chooser
response = self.get()
# then chooser template is used
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/chooser/chooser.html")
# and the following hint is displayed:
# "You haven't uploaded any documents in this collection. Why not upload one now?"
self.assertContains(response, self._NO_DOCS_TEXT)
self.assertNotContains(response, self._UPLOAD_ONE_TEXT)
def test_results_docs_exist(self):
# given a superuser
self.login_as_superuser()
# and a document in the database
doc_title = "document.pdf"
Document.objects.create(title=doc_title)
# when searching for any documents at chooser panel
response = self.get({"q": ""})
# then results template is used
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtaildocs/chooser/results.html")
# and document is displayed
self.assertContains(response, doc_title)
# and no hints are displayed
self.assertNotContains(response, self._NO_DOCS_TEXT)
self.assertNotContains(response, self._NO_COLLECTION_DOCS_TEXT)
self.assertNotContains(response, self._UPLOAD_ONE_TEXT)
def test_results_no_docs_upload_allowed(self):
# given a superuser and no documents in the database
self.login_as_superuser()
# when searching for any documents at chooser panel
response = self.get({"q": ""})
# then results template is used
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtaildocs/chooser/results.html")
# and hint "You haven't uploaded any documents. Why not upload one now?" is displayed
self.assertContains(response, self._NO_DOCS_TEXT)
self.assertContains(response, self._UPLOAD_ONE_TEXT)
def test_results_no_docs_upload_forbidden(self):
# given an editor with access to admin panel
# and no documents in the database
self.login_as_editor()
# when searching for any documents at chooser panel
response = self.get({"q": ""})
# then results template is used
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtaildocs/chooser/results.html")
# and hint "You haven't uploaded any documents." is displayed
self.assertContains(response, self._NO_DOCS_TEXT)
self.assertNotContains(response, self._UPLOAD_ONE_TEXT)
def test_results_no_collection_docs_upload_allowed(self):
# given a superuser
self.login_as_superuser()
# and a document in a collection
root_id = get_root_collection_id()
root = Collection.objects.get(id=root_id)
empty_collection = Collection(name="Nothing to see here")
root.add_child(instance=empty_collection)
doc_title = "document.pdf"
Document.objects.create(title=doc_title, collection=root)
# when searching for documents in another collection at chooser panel
response = self.get({"q": "", "collection_id": empty_collection.id})
# then results template is used
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtaildocs/chooser/results.html")
# and the following hint is displayed:
# "You haven't uploaded any documents in this collection. Why not upload one now?"
self.assertContains(response, self._NO_COLLECTION_DOCS_TEXT)
self.assertContains(response, self._UPLOAD_ONE_TEXT)
def test_results_no_collection_docs_upload_forbidden(self):
# given an editor with access to admin panel
self.login_as_editor()
# and a document in a collection
root_id = get_root_collection_id()
root = Collection.objects.get(id=root_id)
empty_collection = Collection(name="Nothing to see here")
root.add_child(instance=empty_collection)
Document.objects.create(collection=root)
# when searching for documents in another collection at chooser panel
response = self.get({"q": "", "collection_id": empty_collection.id})
# then results template is used
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtaildocs/chooser/results.html")
# and hint "You haven't uploaded any documents in this collection." is displayed
self.assertContains(response, self._NO_COLLECTION_DOCS_TEXT)
self.assertNotContains(response, self._UPLOAD_ONE_TEXT)

View File

@@ -0,0 +1,95 @@
from django import forms
from django.test import TestCase, override_settings
from taggit import models as taggit_models
from wagtail.admin import widgets
from wagtail.admin.widgets import AdminDateTimeInput
from wagtail.documents import models
from wagtail.documents.forms import (
BaseDocumentForm,
get_document_base_form,
get_document_form,
get_document_multi_form,
)
from wagtail.test.testapp.media_forms import AlternateDocumentForm, OverriddenWidget
from wagtail.test.testapp.models import CustomRestaurantDocument, RestaurantTag
class TestDocumentFormOverride(TestCase):
def test_get_document_base_form(self):
self.assertIs(get_document_base_form(), BaseDocumentForm)
def test_get_document_form(self):
bases = get_document_form(models.Document).__bases__
self.assertIn(BaseDocumentForm, bases)
self.assertNotIn(AlternateDocumentForm, bases)
def test_get_document_form_widgets(self):
form_cls = get_document_form(models.Document)
form = form_cls()
self.assertIsInstance(form.fields["tags"].widget, widgets.AdminTagWidget)
self.assertEqual(form.fields["tags"].widget.tag_model, taggit_models.Tag)
self.assertIsInstance(form.fields["file"].widget, forms.FileInput)
def test_tags_widget_with_custom_tag_model(self):
form_cls = get_document_form(CustomRestaurantDocument)
form = form_cls()
self.assertIsInstance(form.fields["tags"].widget, widgets.AdminTagWidget)
self.assertEqual(form.fields["tags"].widget.tag_model, RestaurantTag)
def test_tags_longer_than_max_characters(self):
long_value = "longtag" * 20
form_data = {
"title": "Test Document",
"file": OverriddenWidget,
"tags": [long_value],
}
form_cls = get_document_form(models.Document)
form = form_cls(form_data)
self.assertFalse(form.is_valid())
self.assertIn("tags", form.errors)
self.assertEqual(
form.errors["tags"][0],
"Tag(s) ['{val}'] are over {max_tag_length} characters".format(
val=long_value,
max_tag_length=taggit_models.TagBase._meta.get_field("name").max_length,
),
)
@override_settings(
WAGTAILDOCS_DOCUMENT_FORM_BASE="wagtail.test.testapp.media_forms.AlternateDocumentForm"
)
def test_overridden_base_form(self):
self.assertIs(get_document_base_form(), AlternateDocumentForm)
@override_settings(
WAGTAILDOCS_DOCUMENT_FORM_BASE="wagtail.test.testapp.media_forms.AlternateDocumentForm"
)
def test_get_overridden_document_form(self):
bases = get_document_form(models.Document).__bases__
self.assertNotIn(BaseDocumentForm, bases)
self.assertIn(AlternateDocumentForm, bases)
@override_settings(
WAGTAILDOCS_DOCUMENT_FORM_BASE="wagtail.test.testapp.media_forms.AlternateDocumentForm"
)
def test_get_overridden_document_multi_form(self):
bases = get_document_multi_form(models.Document).__bases__
self.assertNotIn(BaseDocumentForm, bases)
self.assertIn(AlternateDocumentForm, bases)
@override_settings(
WAGTAILDOCS_DOCUMENT_FORM_BASE="wagtail.test.testapp.media_forms.AlternateDocumentForm"
)
def test_get_overridden_document_form_widgets(self):
form_cls = get_document_form(models.Document)
form = form_cls()
self.assertIsInstance(form.fields["tags"].widget, OverriddenWidget)
self.assertIsInstance(form.fields["file"].widget, OverriddenWidget)
self.assertIn("form_only_field", form.fields)
self.assertIs(form.Meta.widgets["form_only_field"], AdminDateTimeInput)

View File

@@ -0,0 +1,310 @@
from django.conf import settings
from django.contrib.auth.models import Group, Permission
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.files.base import ContentFile
from django.db import transaction
from django.test import TestCase, TransactionTestCase
from django.test.utils import override_settings
from wagtail.documents import (
get_document_model,
get_document_model_string,
models,
signal_handlers,
)
from wagtail.images.tests.utils import get_test_image_file
from wagtail.models import Collection, GroupCollectionPermission
from wagtail.test.testapp.models import CustomDocument, ReimportedDocumentModel
from wagtail.test.utils import WagtailTestUtils
class TestDocumentQuerySet(TransactionTestCase):
fixtures = ["test_empty.json"]
def test_search_method(self):
# Make a test document
document = models.Document.objects.create(title="Test document")
# Search for it
results = models.Document.objects.search("Test")
self.assertEqual(list(results), [document])
def test_operators(self):
aaa_document = models.Document.objects.create(title="AAA Test document")
zzz_document = models.Document.objects.create(title="ZZZ Test document")
results = models.Document.objects.search("aaa test", operator="and")
self.assertEqual(list(results), [aaa_document])
results = models.Document.objects.search("aaa test", operator="or")
sorted_results = sorted(results, key=lambda doc: doc.title)
self.assertEqual(sorted_results, [aaa_document, zzz_document])
def test_custom_ordering(self):
aaa_document = models.Document.objects.create(title="AAA Test document")
zzz_document = models.Document.objects.create(title="ZZZ Test document")
results = models.Document.objects.order_by("title").search(
"Test", order_by_relevance=False
)
self.assertEqual(list(results), [aaa_document, zzz_document])
results = models.Document.objects.order_by("-title").search(
"Test", order_by_relevance=False
)
self.assertEqual(list(results), [zzz_document, aaa_document])
class TestDocumentPermissions(WagtailTestUtils, TestCase):
def setUp(self):
# Create some user accounts for testing permissions
self.user = self.create_user(
username="user", email="user@email.com", password="password"
)
self.owner = self.create_user(
username="owner", email="owner@email.com", password="password"
)
self.editor = self.create_user(
username="editor", email="editor@email.com", password="password"
)
self.editor.groups.add(Group.objects.get(name="Editors"))
self.administrator = self.create_superuser(
username="administrator",
email="administrator@email.com",
password="password",
)
# Owner user must have the add_document permission
self.adders_group = Group.objects.create(name="Document adders")
GroupCollectionPermission.objects.create(
group=self.adders_group,
collection=Collection.get_first_root_node(),
permission=Permission.objects.get(codename="add_document"),
)
self.owner.groups.add(self.adders_group)
# Create a document for running tests on
self.document = models.Document.objects.create(
title="Test document", uploaded_by_user=self.owner
)
def test_administrator_can_edit(self):
self.assertTrue(self.document.is_editable_by_user(self.administrator))
def test_editor_can_edit(self):
self.assertTrue(self.document.is_editable_by_user(self.editor))
def test_owner_can_edit(self):
self.assertTrue(self.document.is_editable_by_user(self.owner))
def test_user_cant_edit(self):
self.assertFalse(self.document.is_editable_by_user(self.user))
class TestDocumentFilenameProperties(TestCase):
def setUp(self):
self.document = models.Document(title="Test document")
self.document.file.save(
"sample_name.doc",
ContentFile("A boring example document"),
)
self.pdf_document = models.Document(title="Test document")
self.pdf_document.file.save(
"sample_name.pdf",
ContentFile("A boring example document"),
)
self.extensionless_document = models.Document(title="Test document")
self.extensionless_document.file.save(
"sample_name",
ContentFile("A boring example document"),
)
def test_filename(self):
self.assertEqual("sample_name.doc", self.document.filename)
self.assertEqual("sample_name.pdf", self.pdf_document.filename)
self.assertEqual("sample_name", self.extensionless_document.filename)
def test_file_extension(self):
self.assertEqual("doc", self.document.file_extension)
self.assertEqual("pdf", self.pdf_document.file_extension)
self.assertEqual("", self.extensionless_document.file_extension)
def test_content_type(self):
self.assertEqual("application/msword", self.document.content_type)
self.assertEqual("application/pdf", self.pdf_document.content_type)
self.assertEqual(
"application/octet-stream", self.extensionless_document.content_type
)
def test_file_hash(self):
self.assertEqual(
self.document.get_file_hash(), "7d8c4778b182e4f3bd442408c64a6e22a4b0ed85"
)
self.assertEqual(
self.pdf_document.get_file_hash(),
"7d8c4778b182e4f3bd442408c64a6e22a4b0ed85",
)
self.assertEqual(
self.extensionless_document.get_file_hash(),
"7d8c4778b182e4f3bd442408c64a6e22a4b0ed85",
)
def test_content_disposition(self):
self.assertEqual(
"""attachment; filename=sample_name.doc; filename*=UTF-8''sample_name.doc""",
self.document.content_disposition,
)
self.assertEqual("inline", self.pdf_document.content_disposition)
self.assertEqual(
"""attachment; filename=sample_name; filename*=UTF-8''sample_name""",
self.extensionless_document.content_disposition,
)
def tearDown(self):
# delete the FieldFile directly because the TestCase does not commit
# transactions to trigger transaction.on_commit() in the signal handler
self.document.file.delete()
self.pdf_document.file.delete()
self.extensionless_document.file.delete()
class TestFilesDeletedForDefaultModels(TransactionTestCase):
"""
Because we expect file deletion to only happen once a transaction is
successfully committed, we must run these tests using TransactionTestCase
per the following documentation:
Django's TestCase class wraps each test in a transaction and rolls back that
transaction after each test, in order to provide test isolation. This means
that no transaction is ever actually committed, thus your on_commit()
callbacks will never be run. If you need to test the results of an
on_commit() callback, use a TransactionTestCase instead.
https://docs.djangoproject.com/en/1.10/topics/db/transactions/#use-in-tests
"""
fixtures = ["test_empty.json"]
def test_document_file_deleted_oncommit(self):
with transaction.atomic():
document = get_document_model().objects.create(
title="Test Image", file=get_test_image_file()
)
filename = document.file.name
self.assertTrue(document.file.storage.exists(filename))
document.delete()
self.assertTrue(document.file.storage.exists(filename))
self.assertFalse(document.file.storage.exists(filename))
@override_settings(WAGTAILDOCS_EXTENSIONS=["pdf"])
class TestDocumentValidateExtensions(TestCase):
def setUp(self):
self.document_invalid = models.Document.objects.create(
title="Test document", file="test.doc"
)
self.document_valid = models.Document.objects.create(
title="Test document", file="test.pdf"
)
def test_create_doc_invalid_extension(self):
"""
Checks if the uploaded document has the expected extensions
mentioned in settings.WAGTAILDOCS_EXTENSIONS
This is caught in form.error and should be raised during model
creation when called full_clean. This specific testcase invalid
file extension is passed
"""
with self.assertRaises(ValidationError) as e:
self.document_invalid.full_clean()
expected_message = (
"File extension “doc” is not allowed. Allowed extensions are: pdf."
)
self.assertEqual(e.exception.message_dict["file"][0], expected_message)
def test_create_doc_valid_extension(self):
"""
Checks if the uploaded document has the expected extensions
mentioned in settings.WAGTAILDOCS_EXTENSIONS
This is caught in form.error and should be raised during
model creation when called full_clean. In this specific
testcase invalid file extension is passed.
"""
try:
self.document_valid.full_clean()
except ValidationError:
self.fail("Validation error is raised even when valid file name is passed")
def tearDown(self):
self.document_invalid.file.delete()
self.document_valid.file.delete()
@override_settings(WAGTAILDOCS_DOCUMENT_MODEL="tests.CustomDocument")
class TestFilesDeletedForCustomModels(TestFilesDeletedForDefaultModels):
def setUp(self):
# Required to create root collection because the TransactionTestCase
# does not make initial data loaded in migrations available and
# serialized_rollback=True causes other problems in the test suite.
# ref: https://docs.djangoproject.com/en/1.10/topics/testing/overview/#rollback-emulation
Collection.objects.get_or_create(
name="Root",
path="0001",
depth=1,
numchild=0,
)
#: Sadly signal receivers only get connected when starting django.
#: We will re-attach them here to mimic the django startup behaviour
#: and get the signals connected to our custom model..
signal_handlers.register_signal_handlers()
def test_document_model(self):
cls = get_document_model()
self.assertEqual(
f"{cls._meta.app_label}.{cls.__name__}", "tests.CustomDocument"
)
class TestGetDocumentModel(WagtailTestUtils, TestCase):
@override_settings(WAGTAILDOCS_DOCUMENT_MODEL="tests.CustomDocument")
def test_custom_get_document_model(self):
"""Test get_document_model with a custom document model"""
self.assertIs(get_document_model(), CustomDocument)
def test_get_document_model_at_import_time(self):
self.assertEqual(ReimportedDocumentModel, models.Document)
@override_settings(WAGTAILDOCS_DOCUMENT_MODEL="tests.CustomDocument")
def test_custom_get_document_model_string(self):
"""Test get_document_model_string with a custom document model"""
self.assertEqual(get_document_model_string(), "tests.CustomDocument")
@override_settings()
def test_standard_get_document_model(self):
"""Test get_document_model with no WAGTAILDOCS_DOCUMENT_MODEL"""
del settings.WAGTAILDOCS_DOCUMENT_MODEL
from wagtail.documents.models import Document
self.assertIs(get_document_model(), Document)
@override_settings()
def test_standard_get_document_model_string(self):
"""Test get_document_model_string with no WAGTAILDOCS_DOCUMENT_MODEL"""
del settings.WAGTAILDOCS_DOCUMENT_MODEL
self.assertEqual(get_document_model_string(), "wagtaildocs.Document")
@override_settings(WAGTAILDOCS_DOCUMENT_MODEL="tests.UnknownModel")
def test_unknown_get_document_model(self):
"""Test get_document_model with an unknown model"""
with self.assertRaises(ImproperlyConfigured):
get_document_model()
@override_settings(WAGTAILDOCS_DOCUMENT_MODEL="invalid-string")
def test_invalid_get_document_model(self):
"""Test get_document_model with an invalid model string"""
with self.assertRaises(ImproperlyConfigured):
get_document_model()

View File

@@ -0,0 +1,76 @@
from django.test import TestCase
from django.urls import reverse_lazy
from wagtail.documents import get_document_model
from wagtail.documents.rich_text import (
DocumentLinkHandler as FrontendDocumentLinkHandler,
)
from wagtail.documents.rich_text.editor_html import (
DocumentLinkHandler as EditorHtmlDocumentLinkHandler,
)
from wagtail.fields import RichTextField
from wagtail.rich_text.feature_registry import FeatureRegistry
from wagtail.test.utils import WagtailTestUtils
class TestEditorHtmlDocumentLinkHandler(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def test_get_db_attributes(self):
soup = self.get_soup('<a data-id="test-id">foo</a>')
tag = soup.a
result = EditorHtmlDocumentLinkHandler.get_db_attributes(tag)
self.assertEqual(result, {"id": "test-id"})
def test_expand_db_attributes_for_editor(self):
result = EditorHtmlDocumentLinkHandler.expand_db_attributes({"id": 1})
self.assertEqual(
result,
'<a data-linktype="document" data-id="1" href="/documents/1/test.pdf">',
)
def test_expand_db_attributes_for_editor_preserves_id_of_nonexistent_document(self):
result = EditorHtmlDocumentLinkHandler.expand_db_attributes({"id": 0})
self.assertEqual(result, '<a data-linktype="document" data-id="0">')
def test_expand_db_attributes_for_editor_with_missing_id(self):
result = EditorHtmlDocumentLinkHandler.expand_db_attributes({})
self.assertEqual(result, '<a data-linktype="document">')
class TestFrontendDocumentLinkHandler(TestCase):
fixtures = ["test.json"]
def test_expand_db_attributes_for_frontend(self):
result = FrontendDocumentLinkHandler.expand_db_attributes({"id": 1})
self.assertEqual(result, '<a href="/documents/1/test.pdf">')
def test_expand_db_attributes_document_does_not_exist(self):
result = FrontendDocumentLinkHandler.expand_db_attributes({"id": 0})
self.assertEqual(result, "<a>")
def test_expand_db_attributes_with_missing_id(self):
result = FrontendDocumentLinkHandler.expand_db_attributes({})
self.assertEqual(result, "<a>")
def test_extract_references(self):
self.assertEqual(
list(
RichTextField().extract_references(
'<a linktype="document" id="1">Link to a document</a>'
)
),
[(get_document_model(), "1", "", "")],
)
class TestEntityFeatureChooserUrls(TestCase):
def test_chooser_urls_exist(self):
features = FeatureRegistry()
document = features.get_editor_plugin("draftail", "document-link")
self.assertIsNotNone(document.data.get("chooserUrls"))
self.assertEqual(
document.data["chooserUrls"]["documentChooser"],
reverse_lazy("wagtaildocs_chooser:choose"),
)

View File

@@ -0,0 +1,109 @@
import unittest
from django.core.files.base import ContentFile
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
from wagtail.documents import models
from wagtail.test.utils import WagtailTestUtils
@override_settings(_WAGTAILSEARCH_FORCE_AUTO_UPDATE=["elasticsearch"])
class TestIssue613(WagtailTestUtils, TestCase):
def get_elasticsearch_backend(self):
from django.conf import settings
from wagtail.search.backends import get_search_backend
if "elasticsearch" not in settings.WAGTAILSEARCH_BACKENDS:
raise unittest.SkipTest("No elasticsearch backend active")
return get_search_backend("elasticsearch")
def setUp(self):
self.search_backend = self.get_elasticsearch_backend()
self.login()
def add_document(self, **params):
# Build a fake file
fake_file = ContentFile(b"A boring example document")
fake_file.name = "test.txt"
# Submit
post_data = {
"title": "Test document",
"file": fake_file,
}
post_data.update(params)
response = self.client.post(reverse("wagtaildocs:add"), post_data)
# User should be redirected back to the index
self.assertRedirects(response, reverse("wagtaildocs:index"))
# Document should be created
doc = models.Document.objects.filter(title=post_data["title"])
self.assertTrue(doc.exists())
return doc.first()
def edit_document(self, **params):
# Build a fake file
fake_file = ContentFile(b"A boring example document")
fake_file.name = "test.txt"
# Create a document without tags to edit
document = models.Document.objects.create(title="Test document", file=fake_file)
# Build another fake file
another_fake_file = ContentFile(b"A boring example document")
another_fake_file.name = "test.txt"
# Submit
post_data = {
"title": "Test document changed!",
"file": another_fake_file,
}
post_data.update(params)
response = self.client.post(
reverse("wagtaildocs:edit", args=(document.id,)), post_data
)
# User should be redirected back to the index
self.assertRedirects(response, reverse("wagtaildocs:index"))
# Document should be changed
doc = models.Document.objects.filter(title=post_data["title"])
self.assertTrue(doc.exists())
return doc.first()
def test_issue_613_on_add(self):
# Reset the search index
self.search_backend.reset_index()
self.search_backend.add_type(models.Document)
# Add a document with some tags
document = self.add_document(tags="hello")
self.search_backend.refresh_index()
# Search for it by tag
results = self.search_backend.search("hello", models.Document)
# Check
self.assertEqual(len(results), 1)
self.assertEqual(results[0].id, document.id)
def test_issue_613_on_edit(self):
# Reset the search index
self.search_backend.reset_index()
self.search_backend.add_type(models.Document)
# Add a document with some tags
document = self.edit_document(tags="hello")
self.search_backend.refresh_index()
# Search for it by tag
results = self.search_backend.search("hello", models.Document)
# Check
self.assertEqual(len(results), 1)
self.assertEqual(results[0].id, document.id)

View File

@@ -0,0 +1,75 @@
from django.conf import settings
from django.core.files.base import ContentFile
from django.test import TestCase, override_settings
from django.urls import reverse
from wagtail.documents import models
class TestCorrectDownloadUrlSerialization(TestCase):
"""Test asserts that in case of both `redirect` and `direct`
WAGTAILDOCS_SERVE_METHOD settings `download_url` field
is correctly serialized by DocumentDownloadUrlField."""
def setUp(self):
self.document = models.Document(title="Test document", file_hash="123456")
self.document.file.save(
"serialization.doc",
ContentFile("A boring example document"),
)
def tearDown(self):
# delete the FieldFile directly because the TestCase does not commit
# transactions to trigger transaction.on_commit() in the signal handler
self.document.file.delete()
def get_response(self, document_id, **params):
return self.client.get(
reverse("wagtailapi_v2:documents:detail", args=(document_id,)), params
)
@override_settings(
WAGTAILDOCS_SERVE_METHOD="redirect",
STORAGES={
**settings.STORAGES,
"default": {
"BACKEND": "wagtail.test.dummy_external_storage.DummyExternalStorage"
},
},
WAGTAILAPI_BASE_URL="http://example.com/",
)
def test_serializer_wagtaildocs_serve_redirect(self):
response = self.get_response(self.document.id)
data = response.json()
self.assertIn("meta", data)
meta = data["meta"]
self.assertIn("download_url", meta)
download_url = meta["download_url"]
expected_url = (
f"http://example.com/documents/{self.document.pk}/serialization.doc"
)
self.assertEqual(download_url, expected_url)
@override_settings(
WAGTAILDOCS_SERVE_METHOD="direct",
STORAGES={
**settings.STORAGES,
"default": {
"BACKEND": "wagtail.test.dummy_external_storage.DummyExternalStorage"
},
},
MEDIA_URL="http://remotestorage.com/media/",
WAGTAILAPI_BASE_URL="http://example.com/",
)
def test_serializer_wagtaildocs_serve_direct(self):
response = self.get_response(self.document.id)
data = response.json()
self.assertIn("meta", data)
meta = data["meta"]
self.assertIn("download_url", meta)
download_url = meta["download_url"]
self.assertEqual(
download_url,
"http://remotestorage.com/media/documents/serialization.doc",
)

View File

@@ -0,0 +1,132 @@
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.urls import reverse
from wagtail.documents import get_document_model
from wagtail.documents.wagtail_hooks import DocumentsSummaryItem
from wagtail.models import Collection, GroupCollectionPermission, Site
from wagtail.test.utils import WagtailTestUtils
class TestDocumentsSummary(WagtailTestUtils, TestCase):
@classmethod
def setUpTestData(self):
Document = get_document_model()
# Permissions
doc_content_type = ContentType.objects.get_for_model(Document)
add_doc_permission = Permission.objects.get(
content_type=doc_content_type, codename="add_document"
)
change_doc_permission = Permission.objects.get(
content_type=doc_content_type, codename="change_document"
)
choose_doc_permission = Permission.objects.get(
content_type=doc_content_type, codename="choose_document"
)
# Collections
self.root_collection = Collection.get_first_root_node()
self.reports_collection = self.root_collection.add_child(name="Birds")
# Groups
doc_changers_group = Group.objects.create(name="Document changers")
GroupCollectionPermission.objects.create(
group=doc_changers_group,
collection=self.root_collection,
permission=change_doc_permission,
)
report_adders_group = Group.objects.create(name="Bird adders")
GroupCollectionPermission.objects.create(
group=report_adders_group,
collection=self.reports_collection,
permission=add_doc_permission,
)
report_choosers_group = Group.objects.create(name="Bird choosers")
GroupCollectionPermission.objects.create(
group=report_choosers_group,
collection=self.reports_collection,
permission=choose_doc_permission,
)
# Users
self.superuser = self.create_superuser(
"superuser", "superuser@example.com", "password"
)
# a user with add_doc permission on reports via the report_adders group
self.report_adder = self.create_user(
"reportadder", "reportadder@example.com", "password"
)
self.report_adder.groups.add(report_adders_group)
# a user with choose_doc permission on reports via the report_choosers group
self.report_chooser = self.create_user(
"reportchooser", "reportchooser@example.com", "password"
)
self.report_chooser.groups.add(report_choosers_group)
# Documents
# an doc in the root owned by 'reportadder'
self.changer_doc = Document.objects.create(
title="reportadder's doc",
collection=self.root_collection,
uploaded_by_user=self.report_adder,
)
# an doc in reports owned by 'reportadder'
self.changer_report = Document.objects.create(
title="reportadder's report",
collection=self.reports_collection,
uploaded_by_user=self.report_adder,
)
# an doc in reports owned by 'reportadder'
self.adder_report = Document.objects.create(
title="reportadder's report",
collection=self.reports_collection,
uploaded_by_user=self.report_adder,
)
def setUp(self):
self.login(self.superuser)
def get_request(self):
return self.client.get(reverse("wagtailadmin_home")).wsgi_request
def assertSummaryContains(self, content):
summary = DocumentsSummaryItem(self.get_request()).render_html()
self.assertIn(content, summary)
def test_site_name_is_shown(self):
self.assertEqual(Site.objects.count(), 1)
site = Site.objects.first()
self.assertSummaryContains(site.site_name)
def test_user_with_permissions_is_shown_panel(self):
self.assertTrue(DocumentsSummaryItem(self.get_request()).is_shown())
def test_user_with_no_permissions_is_not_shown_panel(self):
self.superuser.is_superuser = False
self.superuser.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
)
)
self.superuser.save()
self.assertFalse(DocumentsSummaryItem(self.get_request()).is_shown())
def test_user_sees_proper_doc_count(self):
cases = (
(self.superuser, "<span>3</span> Documents"),
(self.report_adder, "<span>2</span> Documents"),
(self.report_chooser, "<span>2</span> Documents"),
)
for user, content in cases:
with self.subTest(user=user):
self.login(user)
self.assertSummaryContains(content)

View File

@@ -0,0 +1,414 @@
import os.path
import unittest
import urllib
from unittest import mock
from django.conf import settings
from django.core.files.base import ContentFile
from django.test import TestCase, override_settings
from django.urls import reverse
from wagtail.documents import models
@override_settings(WAGTAILDOCS_SERVE_METHOD=None)
class TestServeView(TestCase):
def setUp(self):
self.document = models.Document(title="Test document", file_hash="123456")
self.document.file.save(
"serve_view.doc", ContentFile(b"A boring example document")
)
self.pdf_document = models.Document(title="Test document", file_hash="123456")
self.pdf_document.file.save(
"serve_view.pdf", ContentFile(b"A boring example document")
)
def tearDown(self):
if hasattr(self, "response"):
# Make sure the response is fully read before deleting the document so
# that the file is closed by the view.
# This is required on Windows as the below line that deletes the file
# will crash if the file is still open.
b"".join(self.response.streaming_content)
# delete the FieldFile directly because the TestCase does not commit
# transactions to trigger transaction.on_commit() in the signal handler
self.document.file.delete()
self.pdf_document.file.delete()
def get(self, document=None):
document = document or self.document
self.response = self.client.get(
reverse("wagtaildocs_serve", args=(document.id, document.filename))
)
return self.response
def test_response_code(self):
self.assertEqual(self.get().status_code, 200)
def test_content_disposition_header(self):
self.assertEqual(
self.get(self.document)["Content-Disposition"],
f'attachment; filename="{self.document.filename}"',
)
def test_inline_content_disposition_header(self):
self.assertEqual(
self.get(self.pdf_document)["Content-Disposition"],
f'inline; filename="{self.pdf_document.filename}"',
)
@mock.patch("wagtail.documents.views.serve.hooks")
@mock.patch("wagtail.documents.views.serve.get_object_or_404")
def test_non_local_filesystem_content_disposition_header(
self, mock_get_object_or_404, mock_hooks
):
"""
Tests the 'Content-Disposition' header in a response when using a
storage backend that doesn't expose filesystem paths.
"""
# Create a mock document with no local file to hit the correct code path
mock_doc = mock.Mock()
mock_doc.filename = self.document.filename
mock_doc.content_type = self.document.content_type
mock_doc.content_disposition = self.document.content_disposition
mock_doc.file = ContentFile(b"file-like object" * 10)
mock_doc.file.path = None
mock_doc.file.url = None
mock_get_object_or_404.return_value = mock_doc
# Bypass 'before_serve_document' hooks
mock_hooks.get_hooks.return_value = []
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertEqual(
response["Content-Disposition"],
"attachment; filename={0}; filename*=UTF-8''{0}".format(
urllib.parse.quote(self.document.filename)
),
)
@mock.patch("wagtail.documents.views.serve.hooks")
@mock.patch("wagtail.documents.views.serve.get_object_or_404")
def test_non_local_filesystem_inline_content_disposition_header(
self, mock_get_object_or_404, mock_hooks
):
"""
Tests the 'Content-Disposition' header in a response when using a
storage backend that doesn't expose filesystem paths.
"""
# Create a mock document with no local file to hit the correct code path
mock_doc = mock.Mock()
mock_doc.filename = self.pdf_document.filename
mock_doc.content_type = self.pdf_document.content_type
mock_doc.content_disposition = self.pdf_document.content_disposition
mock_doc.file = ContentFile(b"file-like object" * 10)
mock_doc.file.path = None
mock_doc.file.url = None
mock_get_object_or_404.return_value = mock_doc
# Bypass 'before_serve_document' hooks
mock_hooks.get_hooks.return_value = []
response = self.get(self.pdf_document)
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Disposition"], "inline")
def test_content_length_header(self):
self.assertEqual(self.get()["Content-Length"], "25")
def test_content_type_header(self):
self.assertEqual(self.get()["Content-Type"], "application/msword")
def test_is_streaming_response(self):
self.assertTrue(self.get().streaming)
def test_content(self):
self.assertEqual(
b"".join(self.get().streaming_content), b"A boring example document"
)
def test_document_served_fired(self):
mock_handler = mock.MagicMock()
models.document_served.connect(mock_handler)
try:
self.get()
self.assertEqual(mock_handler.call_count, 1)
self.assertEqual(mock_handler.mock_calls[0][2]["sender"], models.Document)
self.assertEqual(mock_handler.mock_calls[0][2]["instance"], self.document)
finally:
models.document_served.disconnect(mock_handler)
def test_with_nonexistent_document(self):
response = self.client.get(
reverse(
"wagtaildocs_serve",
args=(
1000,
"blahblahblah",
),
)
)
self.assertEqual(response.status_code, 404)
def test_with_incorrect_filename(self):
response = self.client.get(
reverse("wagtaildocs_serve", args=(self.document.id, "incorrectfilename"))
)
self.assertEqual(response.status_code, 404)
def test_has_etag_header(self):
self.assertEqual(self.get()["ETag"], '"123456"')
def clear_sendfile_cache(self):
from wagtail.utils.sendfile import _get_sendfile
_get_sendfile.clear()
@override_settings(WAGTAILDOCS_SERVE_METHOD="redirect")
class TestServeViewWithRedirect(TestCase):
def setUp(self):
self.document = models.Document(title="Test document")
self.document.file.save(
"serve_view_with_redirect.doc",
ContentFile("A boring example document"),
)
self.serve_view_url = reverse(
"wagtaildocs_serve", args=(self.document.id, self.document.filename)
)
def tearDown(self):
self.document.delete()
def get(self):
return self.client.get(
reverse(
"wagtaildocs_serve", args=(self.document.id, self.document.filename)
)
)
def test_document_url_should_point_to_serve_view(self):
self.assertEqual(self.document.url, self.serve_view_url)
def test_redirect(self):
response = self.get()
self.assertRedirects(
response, self.document.file.url, fetch_redirect_response=False
)
@override_settings(WAGTAILDOCS_SERVE_METHOD="direct")
class TestDirectDocumentUrls(TestCase):
def setUp(self):
self.document = models.Document(title="Test document")
self.document.file.save(
"direct_document_urls.doc",
ContentFile("A boring example document"),
)
def tearDown(self):
self.document.delete()
def get(self):
return self.client.get(
reverse(
"wagtaildocs_serve", args=(self.document.id, self.document.filename)
)
)
def test_url_should_point_directly_to_file_storage_url(self):
self.assertEqual(self.document.url, self.document.file.url)
def test_redirect(self):
# The serve view will not normally be linked to in 'direct' mode, but we should ensure it
# still works by redirecting
response = self.get()
self.assertRedirects(
response, self.document.file.url, fetch_redirect_response=False
)
@override_settings(
WAGTAILDOCS_SERVE_METHOD=None,
STORAGES={
**settings.STORAGES,
"default": {
"BACKEND": "wagtail.test.dummy_external_storage.DummyExternalStorage"
},
},
)
class TestServeWithExternalStorage(TestCase):
"""
Test the behaviour of the default serve method when used with a remote storage backend
(i.e. one that throws NotImplementedError for the path() method).
"""
def setUp(self):
self.document = models.Document(title="Test document")
self.document.file.save(
"serve_with_external_storage.doc",
ContentFile("A boring example document"),
)
self.serve_view_url = reverse(
"wagtaildocs_serve", args=(self.document.id, self.document.filename)
)
def tearDown(self):
self.document.delete()
def test_document_url_should_point_to_serve_view(self):
self.assertEqual(self.document.url, self.serve_view_url)
def test_redirect(self):
# serve view should redirect to the remote URL
response = self.client.get(self.serve_view_url)
self.assertRedirects(
response, self.document.file.url, fetch_redirect_response=False
)
@override_settings(WAGTAILDOCS_SERVE_METHOD=None)
class TestServeViewWithSendfile(TestCase):
def setUp(self):
# Import using a try-catch block to prevent crashes if the
# django-sendfile module is not installed
try:
import sendfile # noqa: F401
except ImportError:
raise unittest.SkipTest("django-sendfile not installed")
self.document = models.Document(title="Test document")
self.document.file.save(
"serve_view_with_sendfile.doc",
ContentFile("A boring example document"),
)
def tearDown(self):
# delete the FieldFile directly because the TestCase does not commit
# transactions to trigger transaction.on_commit() in the signal handler
self.document.file.delete()
def get(self):
return self.client.get(
reverse(
"wagtaildocs_serve", args=(self.document.id, self.document.filename)
)
)
def clear_sendfile_cache(self):
from wagtail.utils.sendfile import _get_sendfile
_get_sendfile.clear()
@override_settings(SENDFILE_BACKEND="sendfile.backends.xsendfile")
def test_sendfile_xsendfile_backend(self):
self.clear_sendfile_cache()
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertEqual(response["X-Sendfile"], self.document.file.path)
@override_settings(
SENDFILE_BACKEND="sendfile.backends.mod_wsgi",
SENDFILE_ROOT=settings.MEDIA_ROOT,
SENDFILE_URL=settings.MEDIA_URL[:-1],
)
def test_sendfile_mod_wsgi_backend(self):
self.clear_sendfile_cache()
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertEqual(
response["Location"],
os.path.join(settings.MEDIA_URL, self.document.file.name),
)
@override_settings(
SENDFILE_BACKEND="sendfile.backends.nginx",
SENDFILE_ROOT=settings.MEDIA_ROOT,
SENDFILE_URL=settings.MEDIA_URL[:-1],
)
def test_sendfile_nginx_backend(self):
self.clear_sendfile_cache()
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertEqual(
response["X-Accel-Redirect"],
os.path.join(settings.MEDIA_URL, self.document.file.name),
)
@override_settings(WAGTAILDOCS_SERVE_METHOD=None)
class TestServeWithUnicodeFilename(TestCase):
def setUp(self):
self.document = models.Document(title="Test document")
self.filename = "docs\u0627\u0644\u0643\u0627\u062a\u062f\u0631\u0627"
"\u064a\u064a\u0629_\u0648\u0627\u0644\u0633\u0648\u0642"
try:
self.document.file.save(
self.filename, ContentFile("A boring example document")
)
except UnicodeEncodeError:
raise unittest.SkipTest("Filesystem doesn't support unicode filenames")
def tearDown(self):
# delete the FieldFile directly because the TestCase does not commit
# transactions to trigger transaction.on_commit() in the signal handler
self.document.file.delete()
def test_response_code(self):
response = self.client.get(
reverse("wagtaildocs_serve", args=(self.document.id, self.filename))
)
self.assertEqual(response.status_code, 200)
@mock.patch("wagtail.documents.views.serve.hooks")
@mock.patch("wagtail.documents.views.serve.get_object_or_404")
def test_non_local_filesystem_unicode_content_disposition_header(
self, mock_get_object_or_404, mock_hooks
):
"""
Tests that a unicode 'Content-Disposition' header (for a response using
a storage backend that doesn't expose filesystem paths) doesn't cause an
error if encoded differently.
"""
# Create a mock document to hit the correct code path.
mock_doc = mock.Mock()
mock_doc.filename = "TÈST.doc"
mock_doc.file = ContentFile(b"file-like object" * 10)
mock_doc.file.path = None
mock_doc.file.url = None
mock_get_object_or_404.return_value = mock_doc
# Bypass 'before_serve_document' hooks
mock_hooks.get_hooks.return_value = []
response = self.client.get(
reverse("wagtaildocs_serve", args=(self.document.id, mock_doc.filename))
)
self.assertEqual(response.status_code, 200)
try:
response["Content-Disposition"].encode("ascii")
except UnicodeDecodeError:
self.fail(
"Content-Disposition with unicode characters failed ascii encoding."
)
try:
response["Content-Disposition"].encode("latin-1")
except UnicodeDecodeError:
self.fail(
"Content-Disposition with unicode characters failed latin-1 encoding."
)

View File

@@ -0,0 +1,7 @@
from django.core.files.base import ContentFile
def get_test_document_file():
fake_file = ContentFile(b"A boring example document")
fake_file.name = "test.txt"
return fake_file