Feature: Share links (#3996)

* Implement share links

Basic implementation of share links

Make certain share link fields not editable, automatically grant permissions on migrate

Updated styling, error messages from expired / deleted links

frontend code linting, reversable sharelink migration

testing coverage

Update translation strings

No links message

* Consolidate file response methods

* improvements to share links on mobile devices

* Refactor share links file_version

* Add docs for share links

* Apply suggestions from code review

* When filtering share links, use the timezone aware now()

* Removes extra call to setup directories for usage in testing

* FIx copied badge display on some browsers

* Move copy to ngx-clipboard library

---------

Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
This commit is contained in:
shamoon
2023-09-14 13:32:43 -07:00
committed by GitHub
parent 3a36d9b1ae
commit 7c9ab8c0b6
35 changed files with 1740 additions and 454 deletions

View File

@@ -1,31 +1,23 @@
import shutil
import os
import tempfile
from datetime import timedelta
from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.test import TestCase
from django.test import override_settings
from django.utils import timezone
from rest_framework import status
from documents.models import Document
from documents.models import ShareLink
from documents.tests.utils import DirectoriesMixin
class TestViews(TestCase):
@classmethod
def setUpClass(cls):
# Provide a dummy static dir to silence whitenoise warnings
cls.static_dir = tempfile.mkdtemp()
cls.override = override_settings(
STATIC_ROOT=cls.static_dir,
)
cls.override.enable()
@classmethod
def tearDownClass(cls):
shutil.rmtree(cls.static_dir, ignore_errors=True)
cls.override.disable()
class TestViews(DirectoriesMixin, TestCase):
def setUp(self) -> None:
self.user = User.objects.create_user("testuser")
super().setUp()
def test_login_redirect(self):
response = self.client.get("/")
@@ -74,3 +66,69 @@ class TestViews(TestCase):
response.context_data["main_js"],
f"frontend/{language_actual}/main.js",
)
def test_share_link_views(self):
"""
GIVEN:
- Share link created
WHEN:
- Valid request for share link is made
- Invalid request for share link is made
- Request for expired share link is made
THEN:
- Document is returned without need for login
- User is redirected to login with error
- User is redirected to login with error
"""
_, filename = tempfile.mkstemp(dir=self.dirs.originals_dir)
content = b"This is a test"
with open(filename, "wb") as f:
f.write(content)
doc = Document.objects.create(
title="none",
filename=os.path.basename(filename),
mime_type="application/pdf",
)
sharelink_permissions = Permission.objects.filter(
codename__contains="sharelink",
)
self.user.user_permissions.add(*sharelink_permissions)
self.user.save()
self.client.force_login(self.user)
self.client.post(
"/api/share_links/",
{
"document": doc.pk,
"file_version": "original",
},
)
sl1 = ShareLink.objects.get(document=doc)
self.client.logout()
# Valid
response = self.client.get(f"/share/{sl1.slug}")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.content, content)
# Invalid
response = self.client.get("/share/123notaslug", follow=True)
response.render()
self.assertEqual(response.request["PATH_INFO"], "/accounts/login/")
self.assertContains(response, b"Share link was not found")
# Expired
sl1.expiration = timezone.now() - timedelta(days=1)
sl1.save()
response = self.client.get(f"/share/{sl1.slug}", follow=True)
response.render()
self.assertEqual(response.request["PATH_INFO"], "/accounts/login/")
self.assertContains(response, b"Share link has expired")