Einstellungen: Cloud-Verbindung bearbeiten (Name, URL, Benutzer, Unterordner, Passwort); sw v99
Bisher ließ sich eine Verbindung nur anlegen und löschen, der Startordner also nicht nachträglich setzen. Das Passwort wird nicht angezeigt; leer lassen behält das gespeicherte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
53ca3b0002
commit
fda6c01943
6 changed files with 115 additions and 1 deletions
|
|
@ -546,3 +546,51 @@ class CloudFetchCachingTests(TestCase):
|
||||||
with patch('books.webdav.fetch_file', return_value=b'EPUB-BYTES'):
|
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')
|
resp = self.client.get(f'/books/cloud/{self.source.pk}/fetch/?path=Dune.epub')
|
||||||
self.assertEqual(resp['Cache-Control'], 'no-store')
|
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)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ urlpatterns = [
|
||||||
path('change-password/', views.change_password, name='change_password'),
|
path('change-password/', views.change_password, name='change_password'),
|
||||||
path('api-token/regenerate/', views.regenerate_api_token, name='regenerate_api_token'),
|
path('api-token/regenerate/', views.regenerate_api_token, name='regenerate_api_token'),
|
||||||
path('webdav/add/', views.webdav_add, name='webdav_add'),
|
path('webdav/add/', views.webdav_add, name='webdav_add'),
|
||||||
|
path('webdav/<int:pk>/edit/', views.webdav_edit, name='webdav_edit'),
|
||||||
path('webdav/<int:pk>/test/', views.webdav_test, name='webdav_test'),
|
path('webdav/<int:pk>/test/', views.webdav_test, name='webdav_test'),
|
||||||
path('webdav/<int:pk>/delete/', views.webdav_delete, name='webdav_delete'),
|
path('webdav/<int:pk>/delete/', views.webdav_delete, name='webdav_delete'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,35 @@ def webdav_add(request):
|
||||||
return redirect('settings')
|
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
|
@login_required
|
||||||
@require_http_methods(['POST'])
|
@require_http_methods(['POST'])
|
||||||
def webdav_test(request, pk):
|
def webdav_test(request, pk):
|
||||||
|
|
|
||||||
|
|
@ -826,6 +826,15 @@ a:hover {
|
||||||
align-self: flex-start;
|
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 import browser (books tab) --- */
|
||||||
|
|
||||||
.cloud-browser {
|
.cloud-browser {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* diora service worker — caches the app shell for offline use.
|
* diora service worker — caches the app shell for offline use.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE = 'diora-v98';
|
const CACHE = 'diora-v99';
|
||||||
const SHELL = [
|
const SHELL = [
|
||||||
'/static/css/app.css',
|
'/static/css/app.css',
|
||||||
'/static/js/app.js',
|
'/static/js/app.js',
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,8 @@
|
||||||
<span class="muted">{{ source.base_url }}{% if source.root_path %} · /{{ source.root_path }}{% endif %}</span>
|
<span class="muted">{{ source.base_url }}{% if source.root_path %} · /{{ source.root_path }}{% endif %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="webdav-item-actions">
|
<div class="webdav-item-actions">
|
||||||
|
<button type="button" class="btn" aria-expanded="false"
|
||||||
|
onclick="const f = document.getElementById('webdav-edit-{{ source.pk }}'); f.hidden = !f.hidden; this.setAttribute('aria-expanded', String(!f.hidden));">Bearbeiten</button>
|
||||||
<form method="post" action="{% url 'webdav_test' source.pk %}" class="inline-form">
|
<form method="post" action="{% url 'webdav_test' source.pk %}" class="inline-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn">Testen</button>
|
<button type="submit" class="btn">Testen</button>
|
||||||
|
|
@ -160,6 +162,31 @@
|
||||||
<button type="submit" class="btn btn-danger">Entfernen</button>
|
<button type="submit" class="btn btn-danger">Entfernen</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<form method="post" action="{% url 'webdav_edit' source.pk %}" class="webdav-form webdav-edit"
|
||||||
|
id="webdav-edit-{{ source.pk }}" hidden>
|
||||||
|
{% csrf_token %}
|
||||||
|
<label class="webdav-field">
|
||||||
|
<span>Name</span>
|
||||||
|
<input type="text" name="label" required value="{{ source.label }}">
|
||||||
|
</label>
|
||||||
|
<label class="webdav-field">
|
||||||
|
<span>Server-URL</span>
|
||||||
|
<input type="url" name="base_url" required value="{{ source.base_url }}">
|
||||||
|
</label>
|
||||||
|
<label class="webdav-field">
|
||||||
|
<span>Benutzername</span>
|
||||||
|
<input type="text" name="username" autocomplete="off" value="{{ source.username }}">
|
||||||
|
</label>
|
||||||
|
<label class="webdav-field">
|
||||||
|
<span>App-Passwort <span class="muted">(leer lassen = unverändert)</span></span>
|
||||||
|
<input type="password" name="password" autocomplete="new-password">
|
||||||
|
</label>
|
||||||
|
<label class="webdav-field">
|
||||||
|
<span>Unterordner <span class="muted">(optional, Import startet dort)</span></span>
|
||||||
|
<input type="text" name="root_path" value="{{ source.root_path }}" placeholder="Buecher">
|
||||||
|
</label>
|
||||||
|
<button type="submit" class="btn">Speichern</button>
|
||||||
|
</form>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue