Update collections and attachment uploads

This commit is contained in:
rucki
2026-09-03 13:59:02 +02:00
parent 28e17a4b21
commit d97244de40
9 changed files with 337 additions and 24 deletions
+146 -1
View File
@@ -1,5 +1,10 @@
import shutil
import tempfile
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings
from django.urls import reverse
from .models import ApiKey, Collection, CollectionSection, Item, Tag, Team, TeamMembership, UserPreference
@@ -103,6 +108,20 @@ class SlashMenuContextTests(TestCase):
self.assertIn('/public', content)
self.assertIn('/private', content)
@patch('core.views.fetch_page_title', return_value='Example page')
def test_url_on_first_line_becomes_link_with_title_and_extra_text(self, _fetch_title):
kind, _, content, url = parse_quick_content('https://example.com/page\nEigener Text #tag')
self.assertEqual(kind, Item.Kind.LINK)
self.assertEqual(url, 'https://example.com/page')
self.assertEqual(content, 'Example page\nEigener Text #tag')
@patch('core.views.fetch_page_title', return_value='Example page')
def test_explicit_link_command_uses_first_line_url_title(self, _fetch_title):
kind, _, content, url = parse_quick_content('/link https://example.com/page\n#tag')
self.assertEqual(kind, Item.Kind.LINK)
self.assertEqual(url, 'https://example.com/page')
self.assertEqual(content, 'Example page\n#tag')
class MarkdownHeadingTests(TestCase):
def test_tag_at_line_start_is_not_a_heading(self):
@@ -171,6 +190,31 @@ class NavigationAndLayoutTests(TestCase):
self.assertLess(toolbar_position, file_position)
class AttachmentEditTests(TestCase):
def setUp(self):
self.media_root = tempfile.mkdtemp()
self.override = override_settings(MEDIA_ROOT=self.media_root)
self.override.enable()
self.user = get_user_model().objects.create_user(username='attachment-editor', password='secret')
self.client.force_login(self.user)
def tearDown(self):
self.override.disable()
shutil.rmtree(self.media_root, ignore_errors=True)
def test_uploaded_file_is_visible_after_first_inline_edit_save(self):
item = Item.objects.create(owner=self.user, content='Original')
uploaded = SimpleUploadedFile('hello.txt', b'hello', content_type='text/plain')
response = self.client.post(reverse('edit_item', args=[item.id]), {
'content': 'Original', 'comment': '', 'visibility': Item.Visibility.PRIVATE, 'url': '', 'files': uploaded,
}, HTTP_HX_REQUEST='true')
self.assertEqual(response.status_code, 200)
self.assertEqual(item.attachments.count(), 1)
self.assertContains(response, 'hello.txt')
class TeamItemEditTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(username='team-editor', password='secret')
@@ -301,6 +345,107 @@ class CollectionTests(TestCase):
self.assertEqual(list(form.fields['team'].queryset), [team])
self.assertEqual(list(form.fields['filter_tags'].queryset.values_list('name', flat=True)), ['ordinary'])
def test_tutorial_is_listed_but_chapter_is_hidden_from_collection_overview(self):
tutorial = Collection.objects.create(owner=self.user, title='my2dos Tutorial', mode=Collection.Mode.TUTORIAL)
chapter = Collection.objects.create(owner=self.user, title='my2dos Typen', mode=Collection.Mode.CHAPTER)
response = self.client.get(reverse('collection_list'))
self.assertContains(response, tutorial.title)
self.assertNotIn(chapter, [entry['collection'] for entry in response.context['entries']])
def test_collection_list_can_filter_collection_types(self):
Collection.objects.create(owner=self.user, title='Manual', mode=Collection.Mode.MANUAL)
Collection.objects.create(owner=self.user, title='Tutorial', mode=Collection.Mode.TUTORIAL)
Collection.objects.create(owner=self.user, title='Tagged', mode=Collection.Mode.TAGS)
Collection.objects.create(owner=self.user, title='Chapter', mode=Collection.Mode.CHAPTER)
response = self.client.get(f'{reverse("collection_list")}?mode=chapter')
self.assertEqual([entry['collection'].title for entry in response.context['entries']], ['Chapter'])
def test_orphan_chapters_are_shown_as_warning(self):
Collection.objects.create(owner=self.user, title='Lonely chapter', mode=Collection.Mode.CHAPTER)
response = self.client.get(reverse('collection_list'))
self.assertContains(response, 'Verwaiste Kapitel')
self.assertContains(response, 'Lonely chapter')
def test_tutorial_can_include_chapter_collection(self):
tutorial = Collection.objects.create(owner=self.user, title='my2dos Tutorial', mode=Collection.Mode.TUTORIAL)
chapter = Collection.objects.create(owner=self.user, title='my2dos Typen', mode=Collection.Mode.CHAPTER)
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='## Todos')
CollectionSection.objects.create(collection=chapter, item=note, position=0)
manage_url = reverse('collection_manage', args=[tutorial.id])
response = self.client.post(manage_url, {'action': 'add_collection', 'collection_id': chapter.id})
self.assertRedirects(response, manage_url)
self.assertTrue(tutorial.sections.filter(sub_collection=chapter).exists())
response = self.client.get(tutorial.get_absolute_url())
self.assertContains(response, '<h2>my2dos Typen</h2>', html=True)
self.assertContains(response, '<h3>Todos</h3>', html=True)
def test_collection_overview_is_available_on_mobile(self):
response = self.client.get(reverse('collection_list'))
self.assertNotContains(response, 'Collections are currently available on desktop only.')
self.assertContains(response, 'Sammlungen')
def test_collection_manage_links_to_note_editor_with_return_target(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.get(manage_url)
self.assertContains(response, f'{reverse("new_item")}?kind=note&collection={collection.id}&next={manage_url}')
def test_create_note_for_collection_in_editor_adds_it_and_returns(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.post(f'{reverse("new_item")}?kind=note&collection={collection.id}&next={manage_url}', {
'collection': collection.id, 'next': manage_url, 'content': '## Fresh note #new',
})
note = Item.objects.get(content='## Fresh note #new')
self.assertRedirects(response, manage_url)
self.assertTrue(collection.sections.filter(item=note).exists())
self.assertEqual(note.visibility, collection.visibility)
self.assertEqual(list(note.tags.values_list('name', flat=True)), ['new'])
def test_create_chapter_from_tutorial_manage_adds_and_opens_it(self):
tutorial = Collection.objects.create(owner=self.user, title='my2dos Tutorial', mode=Collection.Mode.TUTORIAL)
response = self.client.post(reverse('collection_manage', args=[tutorial.id]), {'action': 'create_chapter', 'title': 'Tags'})
chapter = Collection.objects.get(title='Tags')
self.assertEqual(chapter.mode, Collection.Mode.CHAPTER)
self.assertTrue(tutorial.sections.filter(sub_collection=chapter).exists())
self.assertRedirects(response, reverse('collection_manage', args=[chapter.id]))
def test_collection_sections_can_be_reordered_by_position(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
first = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='First')
second = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Second')
first_section = CollectionSection.objects.create(collection=collection, item=first, position=0)
second_section = CollectionSection.objects.create(collection=collection, item=second, position=1)
response = self.client.post(reverse('collection_manage', args=[collection.id]), {
'action': 'move_to', 'section_id': second_section.id, 'position': 0,
})
self.assertRedirects(response, reverse('collection_manage', args=[collection.id]))
self.assertEqual(list(collection.sections.order_by('position').values_list('id', flat=True)), [second_section.id, first_section.id])
def test_collection_detail_is_available_on_mobile(self):
collection = Collection.objects.create(owner=self.user, title='Mobile reading')
response = self.client.get(collection.get_absolute_url())
self.assertNotContains(response, 'Collections are currently available on desktop only.')
self.assertContains(response, 'collection-document')
def test_tag_collection_is_dynamic_and_sorted_by_creation(self):
first = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='First #guide #basics')
first.sync_metadata()