diff --git a/accounts/tests.py b/accounts/tests.py index 35d015e..8eb3201 100644 --- a/accounts/tests.py +++ b/accounts/tests.py @@ -546,3 +546,51 @@ class CloudFetchCachingTests(TestCase): with patch('books.webdav.fetch_file', return_value=b'EPUB-BYTES'): resp = self.client.get(f'/books/cloud/{self.source.pk}/fetch/?path=Dune.epub') self.assertEqual(resp['Cache-Control'], 'no-store') + + +class WebDAVEditTests(TestCase): + def setUp(self): + self.user = User.objects.create_user(username='alice', password='pw12345678') + self.other = User.objects.create_user(username='bob', password='pw12345678') + self.source = WebDAVSource.objects.create( + user=self.user, label='cloud', base_url='https://cloud.example.com', + username='alice', password='app-pw', + ) + + def _edit(self, pk, **data): + form = {'label': 'cloud', 'base_url': 'https://cloud.example.com', 'username': 'alice', 'root_path': ''} + form.update(data) + with patch('accounts.views.list_directory', return_value=[]): + return self.client.post(f'/accounts/webdav/{pk}/edit/', form) + + def test_sets_start_folder_and_keeps_password(self): + self.client.force_login(self.user) + resp = self._edit(self.source.pk, root_path='Books', password='') + self.assertEqual(resp.status_code, 302) + self.source.refresh_from_db() + self.assertEqual(self.source.root_path, 'Books') + self.assertEqual(self.source.password, 'app-pw') + + def test_new_password_replaces_the_old_one(self): + self.client.force_login(self.user) + self._edit(self.source.pk, password='new-pw') + self.source.refresh_from_db() + self.assertEqual(self.source.password, 'new-pw') + + def test_name_and_url_are_required(self): + self.client.force_login(self.user) + self._edit(self.source.pk, label='', root_path='Books') + self.source.refresh_from_db() + self.assertEqual(self.source.root_path, '') + + def test_foreign_source_is_not_editable(self): + self.client.force_login(self.other) + self._edit(self.source.pk, root_path='x') + self.source.refresh_from_db() + self.assertEqual(self.source.root_path, '') + + def test_settings_page_does_not_show_the_password(self): + self.client.force_login(self.user) + html = self.client.get('/accounts/settings/').content.decode() + self.assertIn(f'/accounts/webdav/{self.source.pk}/edit/', html) + self.assertNotIn('app-pw', html) diff --git a/accounts/urls.py b/accounts/urls.py index 4bee71f..f569262 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -17,6 +17,7 @@ urlpatterns = [ path('change-password/', views.change_password, name='change_password'), path('api-token/regenerate/', views.regenerate_api_token, name='regenerate_api_token'), path('webdav/add/', views.webdav_add, name='webdav_add'), + path('webdav//edit/', views.webdav_edit, name='webdav_edit'), path('webdav//test/', views.webdav_test, name='webdav_test'), path('webdav//delete/', views.webdav_delete, name='webdav_delete'), ] diff --git a/accounts/views.py b/accounts/views.py index 30878ff..78ee9d7 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -290,6 +290,35 @@ def webdav_add(request): return redirect('settings') +@login_required +@require_http_methods(['POST']) +def webdav_edit(request, pk): + source = WebDAVSource.objects.filter(pk=pk, user=request.user).first() + if not source: + messages.error(request, 'Verbindung nicht gefunden.') + return redirect('settings') + + label = request.POST.get('label', '').strip() + base_url = request.POST.get('base_url', '').strip() + if not label or not base_url: + messages.error(request, 'Name und Server-URL sind erforderlich.') + return redirect('settings') + + source.label = label[:100] + source.base_url = base_url[:500] + source.username = request.POST.get('username', '').strip()[:200] + source.root_path = request.POST.get('root_path', '').strip()[:500] + # The stored password is never sent to the page: empty means keep it. + password = request.POST.get('password', '') + if password: + source.password = password[:500] + source.save() + + level, message = _probe_source(source) + messages.add_message(request, level, message) + return redirect('settings') + + @login_required @require_http_methods(['POST']) def webdav_test(request, pk): diff --git a/static/css/app.css b/static/css/app.css index a2b225f..36e4a4b 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -826,6 +826,15 @@ a:hover { align-self: flex-start; } +/* Editing a saved connection: a full row under its name and buttons */ +.webdav-edit { + flex-basis: 100%; + padding-top: 0.75rem; +} +.webdav-edit[hidden] { + display: none; +} + /* --- Cloud import browser (books tab) --- */ .cloud-browser { diff --git a/static/js/sw.js b/static/js/sw.js index 853f5fb..4d369e7 100644 --- a/static/js/sw.js +++ b/static/js/sw.js @@ -2,7 +2,7 @@ * diora service worker — caches the app shell for offline use. */ -const CACHE = 'diora-v98'; +const CACHE = 'diora-v99'; const SHELL = [ '/static/css/app.css', '/static/js/app.js', diff --git a/templates/accounts/settings.html b/templates/accounts/settings.html index 702cbcf..c2d17f7 100644 --- a/templates/accounts/settings.html +++ b/templates/accounts/settings.html @@ -150,6 +150,8 @@ {{ source.base_url }}{% if source.root_path %} · /{{ source.root_path }}{% endif %}
+
{% csrf_token %} @@ -160,6 +162,31 @@
+ {% endfor %}