Import notes: sync to ws

This commit is contained in:
Michael Wedl 2024-04-29 09:13:00 +02:00
parent 6f75e008b8
commit 2a1bca883e
6 changed files with 76 additions and 6 deletions

View File

@ -2,6 +2,7 @@
## Next
* Collaborative editing in project findings and sections
* Collaborative editing: update notes list when import new notes
* Fix slot data items `.length` property undefined `<list-of-figures>`, `<list-of-tables>` and `<table-of-contents>` components

View File

@ -1,13 +1,15 @@
from .import_export import (
export_notes,
export_project_types,
export_projects,
export_templates,
import_notes,
import_project_types,
import_projects,
import_templates,
)
__all__ = [
'export_project_types', 'export_projects', 'export_templates',
'import_project_types', 'import_projects', 'import_templates',
'export_project_types', 'export_projects', 'export_templates', 'export_notes',
'import_project_types', 'import_projects', 'import_templates', 'import_notes',
]

View File

@ -19,7 +19,10 @@ from reportcreator_api.archive.import_export.serializers import (
PentestProjectExportImportSerializer,
ProjectTypeExportImportSerializer,
)
from reportcreator_api.pentests.consumers import send_collab_event_project, send_collab_event_user
from reportcreator_api.pentests.models import (
CollabEvent,
CollabEventType,
FindingTemplate,
PentestFinding,
PentestProject,
@ -27,8 +30,14 @@ from reportcreator_api.pentests.models import (
ProjectNotebookPage,
ProjectType,
ReportSection,
UserNotebookPage,
)
from reportcreator_api.pentests.serializers.notes import (
ProjectNotebookPageSerializer,
ProjectNotebookPageSortListSerializer,
UserNotebookPageSerializer,
UserNotebookPageSortListSerializer,
)
from reportcreator_api.pentests.models.notes import UserNotebookPage
from reportcreator_api.users.models import PentestUser
from reportcreator_api.utils.history import history_context
@ -265,5 +274,41 @@ def import_projects(archive_file):
def import_notes(archive_file, context):
if not context.get('project') and not context.get('user'):
raise ValueError('Either project or user must be provided')
return import_archive(archive_file, serializer_classes=[NotesExportImportSerializer], context=context)
# Import notes to DB
notes = import_archive(archive_file, serializer_classes=[NotesExportImportSerializer], context=context)
# Send collab events
sender_options = {
'related_object': context['project'],
'serializer': ProjectNotebookPageSerializer,
'serializer_sort': ProjectNotebookPageSortListSerializer,
'send_collab_event': send_collab_event_project,
} if context.get('project') else {
'related_object': context['user'],
'serializer': UserNotebookPageSerializer,
'serializer_sort': UserNotebookPageSortListSerializer,
'send_collab_event': send_collab_event_user,
}
# Create events
events = CollabEvent.objects.bulk_create(
CollabEvent(
related_id=sender_options['related_object'].id,
path=f'notes.{n.note_id}',
type=CollabEventType.CREATE,
created=n.created,
version=n.created.timestamp(),
data={
'value': sender_options['serializer'](instance=n).data,
},
) for n in notes
)
for e in events:
sender_options['send_collab_event'](e)
# Sort event
notes_sorted = sender_options['related_object'].notes.select_related('parent').all()
sender_options['serializer_sort'](instance=notes_sorted, context=context).send_collab_event(notes_sorted)
return notes

View File

@ -38,6 +38,7 @@ from reportcreator_api.archive.import_export import (
import_templates,
)
from reportcreator_api.archive.import_export.import_export import export_notes, import_notes
from reportcreator_api.pentests.consumers import send_collab_event_project
from reportcreator_api.pentests.customfields.predefined_fields import FINDING_FIELDS_PREDEFINED
from reportcreator_api.pentests.customfields.types import field_definition_to_dict
from reportcreator_api.pentests.models import (
@ -61,6 +62,7 @@ from reportcreator_api.pentests.models import (
UploadedTemplateImage,
UserPublicKey,
)
from reportcreator_api.pentests.models.collab import CollabEvent, CollabEventType
from reportcreator_api.pentests.permissions import (
ArchivedProjectKeyPartPermissions,
IsTemplateEditorOrReadOnly,

View File

@ -11,8 +11,12 @@ from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APIClient
from reportcreator_api.archive.import_export import export_project_types, export_projects, export_templates
from reportcreator_api.archive.import_export.import_export import export_notes
from reportcreator_api.archive.import_export import (
export_notes,
export_project_types,
export_projects,
export_templates,
)
from reportcreator_api.notifications.models import NotificationSpec, UserNotification
from reportcreator_api.pentests.models import (
FindingTemplate,

View File

@ -10,10 +10,12 @@ from channels.testing import WebsocketCommunicator
from django.conf import settings
from django.contrib.auth import BACKEND_SESSION_KEY, HASH_SESSION_KEY, SESSION_KEY
from django.contrib.auth.models import AnonymousUser
from django.core.files.base import ContentFile
from django.core.serializers.json import DjangoJSONEncoder
from django.urls import reverse
from django.utils.module_loading import import_string
from reportcreator_api.archive.import_export import export_notes
from reportcreator_api.conf.asgi import application
from reportcreator_api.pentests.customfields.utils import (
ensure_defined_structure,
@ -451,6 +453,20 @@ class TestProjectNotesDbSync:
data=[{'id': self.note.note_id, 'order': 1, 'parent': None}])
await self.assert_event({'type': CollabEventType.SORT, 'path': 'notes', 'client_id': None, 'sort': res.data})
async def test_import_sync(self):
def import_notes():
res = self.api_client1.post(
path=reverse('projectnotebookpage-import', kwargs={'project_pk': self.project.id}),
data={'file': ContentFile(content=b''.join(export_notes(self.project)), name='export.tar.gz')},
format='multipart',
)
return res.data
notes_imported = await sync_to_async(import_notes)()
for n in notes_imported:
await self.assert_event({'type': CollabEventType.CREATE, 'path': f'notes.{n["id"]}', 'value': n, 'client_id': None})
await self.assert_event({'type': CollabEventType.SORT, 'path': 'notes', 'client_id': None})
async def test_member_removed_write(self):
await ProjectMemberInfo.objects.filter(project=self.project, user=self.user1).adelete()