• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fiduswriter / fiduswriter / 10941406873

19 Sep 2024 12:55PM UTC coverage: 87.088% (-0.007%) from 87.095%
10941406873

Pull #1294

github

web-flow
Merge 0ca2fd0ed into b2c563a85
Pull Request #1294: remove JSONPATCH setting

6374 of 7319 relevant lines covered (87.09%)

4.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.03
fiduswriter/document/views.py
1
import time
13✔
2
import os
13✔
3
import bleach
13✔
4
import json
13✔
5
from copy import deepcopy
13✔
6

7
from django.core import serializers
13✔
8
from django.http import HttpResponse, JsonResponse, HttpRequest
13✔
9
from django.contrib.auth.decorators import login_required
13✔
10
from django.views.decorators.http import require_POST
13✔
11
from django.db import transaction
13✔
12
from django.core.files import File
13✔
13
from django.utils.translation import gettext as _
13✔
14
from django.conf import settings
13✔
15
from django.db.models import F, Q
13✔
16
from django.contrib.admin.views.decorators import staff_member_required
13✔
17
from django.contrib.auth import get_user_model
13✔
18

19
from document.models import (
13✔
20
    Document,
21
    AccessRight,
22
    DocumentRevision,
23
    DocumentTemplate,
24
    CAN_UPDATE_DOCUMENT,
25
    CAN_COMMUNICATE,
26
    FW_DOCUMENT_VERSION,
27
)
28
from usermedia.models import DocumentImage, Image
13✔
29
from bibliography.models import Entry
13✔
30
from document.helpers.serializers import PythonWithURLSerializer
13✔
31
from bibliography.views import serializer
13✔
32
from style.models import DocumentStyle, DocumentStyleFile, ExportTemplate
13✔
33
from base.decorators import ajax_required
13✔
34
from base.helpers.host import get_host
13✔
35
from user.models import UserInvite
13✔
36
from . import emails
13✔
37
from user.helpers import Avatars
13✔
38

39

40
@login_required
13✔
41
@ajax_required
13✔
42
@require_POST
13✔
43
def get_documentlist_extra(request):
13✔
44
    response = {}
1✔
45
    status = 200
1✔
46
    ids = request.POST["ids"].split(",")
1✔
47
    docs = (
1✔
48
        Document.objects.filter(
49
            Q(owner=request.user) | Q(accessright__user=request.user)
50
        )
51
        .filter(id__in=ids)
52
        .prefetch_related("documentimage_set")
53
    )
54
    response["documents"] = []
1✔
55
    for doc in docs:
1✔
56
        images = {}
1✔
57
        for image in doc.documentimage_set.all():
1✔
58
            images[image.image.id] = {
1✔
59
                "added": image.image.added,
60
                "checksum": image.image.checksum,
61
                "file_type": image.image.file_type,
62
                "height": image.image.height,
63
                "id": image.image.id,
64
                "image": image.image.image.url,
65
                "title": image.title,
66
                "copyright": image.copyright,
67
                "width": image.image.width,
68
            }
69
            if image.image.thumbnail:
1✔
70
                images[image.image.id]["thumbnail"] = image.image.thumbnail.url
1✔
71
        if "type" not in doc.content:
1✔
72
            doc.content = deepcopy(doc.template.content)
×
73
            if "type" not in doc.content:
×
74
                doc.content["type"] = "doc"
×
75
            if "content" not in doc.content:
×
76
                doc.content["content"] = [{type: "title"}]
×
77
            doc.save()
×
78
        response["documents"].append(
1✔
79
            {
80
                "images": images,
81
                "content": doc.content,
82
                "comments": doc.comments,
83
                "bibliography": doc.bibliography,
84
                "id": doc.id,
85
            }
86
        )
87
    return JsonResponse(response, status=status)
1✔
88

89

90
def documents_list(request):
13✔
91
    avatars = Avatars()
10✔
92
    documents = (
10✔
93
        Document.objects.filter(
94
            Q(owner=request.user) | Q(accessright__user=request.user),
95
            listed=True,
96
        )
97
        .defer("content", "comments", "bibliography", "doc_version", "diffs")
98
        .select_related("owner")
99
        .prefetch_related(
100
            "accessright_set",
101
            "documentrevision_set",
102
        )
103
        .distinct()
104
        .order_by("-updated")
105
    )
106
    output_list = []
10✔
107
    for document in documents:
10✔
108
        if document.owner == request.user:
8✔
109
            access_right = "write"
8✔
110
            path = document.path
8✔
111
        else:
112
            access_object = document.accessright_set.filter(
2✔
113
                user=request.user
114
            ).first()
115
            access_right = access_object.rights
2✔
116
            path = access_object.path
2✔
117
        if (
8✔
118
            request.user.is_staff
119
            or document.owner == request.user
120
            or document.accessright_set.filter(
121
                user=request.user, rights__in=CAN_COMMUNICATE
122
            ).first()
123
        ):
124
            revisions = document.documentrevision_set.all()
8✔
125
            revision_list = []
8✔
126
            for revision in revisions:
8✔
127
                revision_list.append(
1✔
128
                    {
129
                        "date": time.mktime(revision.date.utctimetuple()),
130
                        "note": revision.note,
131
                        "file_name": revision.file_name,
132
                        "pk": revision.pk,
133
                    }
134
                )
135
        else:
136
            revision_list = []
×
137
        added = time.mktime(document.added.utctimetuple())
8✔
138
        updated = time.mktime(document.updated.utctimetuple())
8✔
139
        is_owner = False
8✔
140
        if document.owner == request.user:
8✔
141
            is_owner = True
8✔
142
        output_list.append(
8✔
143
            {
144
                "id": document.id,
145
                "title": document.title,
146
                "path": path,
147
                "is_owner": is_owner,
148
                "owner": {
149
                    "id": document.owner.id,
150
                    "name": document.owner.readable_name,
151
                    "avatar": avatars.get_url(document.owner),
152
                },
153
                "added": added,
154
                "updated": updated,
155
                "rights": access_right,
156
                "revisions": revision_list,
157
            }
158
        )
159
    return output_list
10✔
160

161

162
@login_required
13✔
163
@ajax_required
13✔
164
@require_POST
13✔
165
def get_access_rights(request):
13✔
166
    response = {}
3✔
167
    status = 200
3✔
168
    avatars = Avatars()
3✔
169
    ar_qs = AccessRight.objects.filter(document__owner=request.user)
3✔
170
    doc_ids = request.POST.getlist("document_ids[]")
3✔
171
    if len(doc_ids) > 0:
3✔
172
        ar_qs = ar_qs.filter(document_id__in=doc_ids)
3✔
173
    access_rights = []
3✔
174
    for ar in ar_qs:
3✔
175
        if ar.holder_type.model == "user":
1✔
176
            avatar = avatars.get_url(ar.holder_obj)
1✔
177
        else:
178
            avatar = None
1✔
179
        access_rights.append(
1✔
180
            {
181
                "document_id": ar.document.id,
182
                "rights": ar.rights,
183
                "holder": {
184
                    "id": ar.holder_id,
185
                    "type": ar.holder_type.model,
186
                    "name": ar.holder_obj.readable_name,
187
                    "avatar": avatar,
188
                },
189
            }
190
        )
191
    response["access_rights"] = access_rights
3✔
192
    return JsonResponse(response, status=status)
3✔
193

194

195
@login_required
13✔
196
@ajax_required
13✔
197
@require_POST
13✔
198
@transaction.atomic
13✔
199
def save_access_rights(request):
13✔
200
    User = get_user_model()
3✔
201
    response = {}
3✔
202
    doc_ids = json.loads(request.POST["document_ids"])
3✔
203
    rights = json.loads(request.POST["access_rights"])
3✔
204
    for doc_id in doc_ids:
3✔
205
        doc = Document.objects.filter(pk=doc_id, owner=request.user).first()
3✔
206
        if not doc:
3✔
207
            continue
×
208
        for right in rights:
3✔
209
            holder_selector = "%(type)s__id" % {
3✔
210
                "type": right["holder"]["type"]
211
            }
212
            if right["rights"] == "delete":
3✔
213
                # Status 'delete' means the access right is marked for
214
                # deletion.
215
                AccessRight.objects.filter(
1✔
216
                    **{
217
                        "document_id": doc_id,
218
                        holder_selector: right["holder"]["id"],
219
                    }
220
                ).delete()
221
            else:
222
                owner = request.user.readable_name
3✔
223
                link = HttpRequest.build_absolute_uri(
3✔
224
                    request, doc.get_absolute_url()
225
                )
226
                access_right = AccessRight.objects.filter(
3✔
227
                    **{
228
                        "document_id": doc_id,
229
                        holder_selector: right["holder"]["id"],
230
                    }
231
                ).first()
232
                document_title = doc.title
3✔
233
                if access_right:
3✔
234
                    if access_right.rights != right["rights"]:
1✔
235
                        access_right.rights = right["rights"]
1✔
236
                        if right["holder"]["type"] == "user":
1✔
237
                            collaborator = User.objects.get(
1✔
238
                                id=right["holder"]["id"]
239
                            )
240
                            collaborator_name = collaborator.readable_name
1✔
241
                            collaborator_email = collaborator.email
1✔
242
                            emails.send_share_notification(
1✔
243
                                document_title,
244
                                owner,
245
                                link,
246
                                collaborator_name,
247
                                collaborator_email,
248
                                right["rights"],
249
                                True,
250
                            )
251
                else:
252
                    # Make the shared path "/filename" or ""
253
                    path = "/%(last_path_part)s" % {
3✔
254
                        "last_path_part": doc.path.split("/").pop()
255
                    }
256
                    if len(path) == 1:
3✔
257
                        path = ""
3✔
258
                    if right["holder"]["type"] == "userinvite":
3✔
259
                        holder = UserInvite.objects.filter(
3✔
260
                            id=right["holder"]["id"]
261
                        ).first()
262
                    else:
263
                        holder = User.objects.filter(
1✔
264
                            id=right["holder"]["id"]
265
                        ).first()
266
                    if not holder:
3✔
267
                        continue
×
268
                    access_right = AccessRight.objects.create(
3✔
269
                        document_id=doc_id,
270
                        holder_obj=holder,
271
                        rights=right["rights"],
272
                        path=path,
273
                    )
274
                    if right["holder"]["id"] == "user":
3✔
275
                        collaborator_name = holder.readable_name
×
276
                        collaborator_email = holder.email
×
277
                        emails.send_share_notification(
×
278
                            document_title,
279
                            owner,
280
                            link,
281
                            collaborator_name,
282
                            collaborator_email,
283
                            right["rights"],
284
                            False,
285
                        )
286
                access_right.save()
3✔
287
    status = 201
3✔
288
    return JsonResponse(response, status=status)
3✔
289

290

291
@login_required
13✔
292
@ajax_required
13✔
293
@require_POST
13✔
294
def get_documentlist(request):
13✔
295
    response = {}
10✔
296
    status = 200
10✔
297
    response["documents"] = documents_list(request)
10✔
298
    response["contacts"] = []
10✔
299
    avatars = Avatars()
10✔
300
    for contact in request.user.contacts.all():
10✔
301
        contact_object = {
2✔
302
            "id": contact.id,
303
            "name": contact.readable_name,
304
            "username": contact.get_username(),
305
            "avatar": avatars.get_url(contact),
306
            "type": "user",
307
        }
308
        response["contacts"].append(contact_object)
2✔
309
    for contact in request.user.invites_by.all():
10✔
310
        contact_object = {
3✔
311
            "id": contact.id,
312
            "name": contact.username,
313
            "username": contact.username,
314
            "avatar": None,
315
            "type": "userinvite",
316
        }
317
        response["contacts"].append(contact_object)
3✔
318
    serializer = PythonWithURLSerializer()
10✔
319
    doc_styles = serializer.serialize(
10✔
320
        DocumentStyle.objects.filter(
321
            Q(document_template__user=None)
322
            | Q(document_template__user=request.user)
323
        ),
324
        use_natural_foreign_keys=True,
325
        fields=["title", "slug", "contents", "documentstylefile_set"],
326
    )
327
    response["document_styles"] = [obj["fields"] for obj in doc_styles]
10✔
328
    doc_templates = DocumentTemplate.objects.filter(
10✔
329
        Q(user=request.user) | Q(user=None)
330
    ).order_by(F("user").desc(nulls_first=True))
331
    response["document_templates"] = {}
10✔
332
    for obj in doc_templates:
10✔
333
        response["document_templates"][obj.import_id] = {
8✔
334
            "title": obj.title,
335
            "id": obj.id,
336
        }
337
    return JsonResponse(response, status=status)
10✔
338

339

340
@login_required
13✔
341
@ajax_required
13✔
342
@require_POST
13✔
343
def delete(request):
13✔
344
    response = {}
2✔
345
    status = 200
2✔
346
    doc_id = int(request.POST["id"])
2✔
347
    document = Document.objects.get(pk=doc_id, owner=request.user)
2✔
348
    if document.is_deletable():
2✔
349
        image_ids = list(
2✔
350
            DocumentImage.objects.filter(document_id=doc_id).values_list(
351
                "image_id", flat=True
352
            )
353
        )
354
        document.delete()
2✔
355
        for image in Image.objects.filter(id__in=image_ids):
2✔
356
            if image.is_deletable():
1✔
357
                image.delete()
×
358
        response["done"] = True
2✔
359
    else:
360
        response["done"] = False
×
361
    return JsonResponse(response, status=status)
2✔
362

363

364
@login_required
13✔
365
@ajax_required
13✔
366
@require_POST
13✔
367
def move(request):
13✔
368
    response = {}
1✔
369
    status = 200
1✔
370
    doc_id = int(request.POST["id"])
1✔
371
    path = request.POST["path"]
1✔
372
    document = Document.objects.filter(pk=doc_id).first()
1✔
373
    if not document:
1✔
374
        response["done"] = False
×
375
    elif document.owner == request.user:
1✔
376
        document.path = path
1✔
377
        document.save(
1✔
378
            update_fields=[
379
                "path",
380
            ]
381
        )
382
        response["done"] = True
1✔
383
    else:
384
        access_right = AccessRight.objects.filter(
×
385
            document=document, user=request.user
386
        ).first()
387
        if not access_right:
×
388
            response["done"] = False
×
389
        else:
390
            access_right.path = path
×
391
            access_right.save()
×
392
            response["done"] = True
×
393
    return JsonResponse(response, status=status)
1✔
394

395

396
@login_required
13✔
397
@ajax_required
13✔
398
@require_POST
13✔
399
def create_doc(request):
13✔
400
    response = {}
6✔
401
    template_id = request.POST["template_id"]
6✔
402
    path = request.POST["path"]
6✔
403
    document_template = DocumentTemplate.objects.filter(
6✔
404
        Q(user=request.user) | Q(user=None), id=template_id
405
    ).first()
406
    if not document_template:
6✔
407
        return JsonResponse(response, status=405)
×
408
    document = Document.objects.create(
6✔
409
        owner_id=request.user.pk, template_id=template_id, path=path
410
    )
411
    response["id"] = document.id
6✔
412
    return JsonResponse(response, status=201)
6✔
413

414

415
@login_required
13✔
416
@ajax_required
13✔
417
@require_POST
13✔
418
def import_create(request):
13✔
419
    # First step of import: Create a document and return the id of it
420
    response = {}
4✔
421
    status = 201
4✔
422
    import_id = request.POST["import_id"]
4✔
423
    document_template = (
4✔
424
        DocumentTemplate.objects.filter(
425
            Q(user=request.user) | Q(user=None), import_id=import_id
426
        )
427
        .order_by(F("user").desc(nulls_last=True))
428
        .first()
429
    )
430
    if not document_template:
4✔
431
        # The user doesn't have this template.
432
        # We check whether the template exists with one of the documents
433
        # shared with the user. If so, we'll copy it so that we can avoid
434
        # having to create an entirely new template without styles or
435
        # exporter templates
436
        access_right = AccessRight.objects.filter(
2✔
437
            user=request.user, document__template__import_id=import_id
438
        ).first()
439
        if access_right:
2✔
440
            document_template = access_right.document.template
1✔
441
            document_styles = list(document_template.documentstyle_set.all())
1✔
442
            export_templates = list(document_template.exporttemplate_set.all())
1✔
443
            document_template.pk = None
1✔
444
            document_template.user = request.user
1✔
445
            document_template.save()
1✔
446
            for ds in document_styles:
1✔
447
                style_files = list(ds.documentstylefile_set.all())
1✔
448
                ds.pk = None
1✔
449
                ds.document_template = document_template
1✔
450
                ds.save()
1✔
451
                for sf in style_files:
1✔
452
                    sf.pk = None
1✔
453
                    sf.style = ds
1✔
454
                    sf.save()
1✔
455
            for et in export_templates:
1✔
456
                et.pk = None
1✔
457
                et.document_template = document_template
1✔
458
                et.save()
1✔
459
    if not document_template:
4✔
460
        template_title = request.POST["template_title"]
1✔
461
        counter = 0
1✔
462
        base_template_title = template_title
1✔
463
        while DocumentTemplate.objects.filter(
1✔
464
            Q(title=template_title), Q(user=request.user) | Q(user=None)
465
        ).first():
466
            counter += 1
×
467
            template_title = f"{base_template_title} {counter}"
×
468
        content = json.loads(request.POST["template"])
1✔
469
        document_template = DocumentTemplate()
1✔
470
        document_template.title = template_title
1✔
471
        document_template.import_id = import_id
1✔
472
        document_template.user = request.user
1✔
473
        document_template.content = content
1✔
474
        document_template.save()
1✔
475
        files = request.FILES.getlist("files[]")
1✔
476
        document_styles = json.loads(request.POST.get("document_styles"))
1✔
477
        export_templates = json.loads(request.POST.get("export_templates"))
1✔
478
        for style in document_styles:
1✔
479
            doc_style = DocumentStyle.objects.create(
1✔
480
                title=style["title"],
481
                slug=style["slug"],
482
                contents=style["contents"],
483
                document_template=document_template,
484
            )
485
            for filepath in style["files"]:
1✔
486
                filename = filepath.split("/").pop()
1✔
487
                file = next((x for x in files if x.name == filename), None)
1✔
488
                if file:
1✔
489
                    DocumentStyleFile.objects.create(
1✔
490
                        file=file, style=doc_style
491
                    )
492
        for e_template in export_templates:
1✔
493
            filename = e_template["file"].split("/").pop()
1✔
494
            file = next((x for x in files if x.name == filename), None)
1✔
495
            if file:
1✔
496
                ExportTemplate.objects.create(
1✔
497
                    document_template=document_template,
498
                    template_file=file,
499
                    file_type=e_template["file_type"],
500
                )
501
    path = request.POST["path"]
4✔
502
    if len(path):
4✔
503
        counter = 0
4✔
504
        base_path = path
4✔
505
        while (
4✔
506
            Document.objects.filter(owner=request.user, path=path).first()
507
            or AccessRight.objects.filter(user=request.user, path=path).first()
508
        ):
509
            counter += 1
2✔
510
            path = f"{base_path} {counter}"
2✔
511
    document = Document.objects.create(
4✔
512
        owner=request.user, template=document_template, path=path
513
    )
514
    response["id"] = document.id
4✔
515
    response["path"] = document.path
4✔
516
    return JsonResponse(response, status=status)
4✔
517

518

519
@login_required
13✔
520
@ajax_required
13✔
521
@require_POST
13✔
522
def import_image(request):
13✔
523
    # create an image for a document
524
    response = {}
1✔
525
    document = Document.objects.filter(
1✔
526
        owner_id=request.user.pk, id=int(request.POST["doc_id"])
527
    ).first()
528
    if document:
1✔
529
        status = 201
1✔
530
    else:
531
        status = 401
×
532
        return JsonResponse(response, status=status)
×
533
    checksum = request.POST["checksum"]
1✔
534
    image = Image.objects.filter(checksum=checksum).first()
1✔
535
    if image is None:
1✔
536
        image = Image.objects.create(
1✔
537
            uploader=request.user,
538
            image=request.FILES["image"],
539
            checksum=checksum,
540
        )
541
    doc_image = DocumentImage.objects.create(
1✔
542
        image=image,
543
        title=request.POST["title"],
544
        copyright=json.loads(request.POST["copyright"]),
545
        document=document,
546
    )
547
    response["id"] = doc_image.image.id
1✔
548
    return JsonResponse(response, status=status)
1✔
549

550

551
@login_required
13✔
552
@ajax_required
13✔
553
@require_POST
13✔
554
def import_doc(request):
13✔
555
    response = {}
4✔
556
    doc_id = request.POST["id"]
4✔
557
    # There is a doc_id, so we overwrite an existing doc rather than
558
    # creating a new one.
559
    document = Document.objects.get(id=int(doc_id))
4✔
560
    if (
4✔
561
        document.owner != request.user
562
        and not AccessRight.objects.filter(
563
            document_id=doc_id,
564
            user=request.user,
565
            rights__in=CAN_UPDATE_DOCUMENT,
566
        ).first()
567
    ):
568
        response["error"] = "No access to file"
×
569
        status = 403
×
570
        return JsonResponse(response, status=status)
×
571
    document.title = request.POST["title"]
4✔
572
    document.content = json.loads(request.POST["content"])
4✔
573
    document.comments = json.loads(request.POST["comments"])
4✔
574
    document.bibliography = json.loads(request.POST["bibliography"])
4✔
575
    # document.doc_version should always be the current version, so don't
576
    # bother about it.
577
    document.save()
4✔
578
    response["document_id"] = document.id
4✔
579
    response["added"] = time.mktime(document.added.utctimetuple())
4✔
580
    response["updated"] = time.mktime(document.updated.utctimetuple())
4✔
581
    status = 200
4✔
582
    return JsonResponse(response, status=status)
4✔
583

584

585
@login_required
13✔
586
@ajax_required
13✔
587
@require_POST
13✔
588
def upload_revision(request):
13✔
589
    response = {}
1✔
590
    status = 405
1✔
591
    can_save = False
1✔
592
    document_id = request.POST["document_id"]
1✔
593
    document = Document.objects.filter(id=int(document_id)).first()
1✔
594
    if document:
1✔
595
        if document.owner == request.user:
1✔
596
            can_save = True
1✔
597
        else:
598
            access_rights = AccessRight.objects.filter(
×
599
                document=document, user=request.user
600
            )
601
            if len(access_rights) > 0 and access_rights[0].rights == "write":
×
602
                can_save = True
×
603
    if can_save:
1✔
604
        status = 201
1✔
605
        revision = DocumentRevision()
1✔
606
        revision.file_object = request.FILES["file"]
1✔
607
        revision.file_name = request.FILES["file"].name
1✔
608
        revision.note = request.POST["note"]
1✔
609
        revision.document_id = document_id
1✔
610
        revision.save()
1✔
611
    return JsonResponse(response, status=status)
1✔
612

613

614
# Download a revision that was previously uploaded
615
@login_required
13✔
616
def get_revision(request, revision_id):
13✔
617
    revision = DocumentRevision.objects.filter(pk=int(revision_id)).first()
1✔
618
    if revision and (
1✔
619
        request.user.is_staff
620
        or revision.document.owner == request.user
621
        or AccessRight.objects.filter(
622
            document=revision.document,
623
            user=request.user,
624
            rights__in=CAN_COMMUNICATE,
625
        ).first()
626
    ):
627
        http_response = HttpResponse(
1✔
628
            revision.file_object.file,
629
            content_type="application/zip; charset=x-user-defined",
630
            status=200,
631
        )
632
        http_response["Content-Disposition"] = (
1✔
633
            "attachment; filename=some_name.zip"
634
        )
635
    else:
636
        http_response = HttpResponse(status=404)
×
637
    return http_response
1✔
638

639

640
@login_required
13✔
641
@ajax_required
13✔
642
@require_POST
13✔
643
def delete_revision(request):
13✔
644
    response = {}
1✔
645
    status = 405
1✔
646
    revision_id = request.POST["id"]
1✔
647
    revision = DocumentRevision.objects.filter(pk=int(revision_id)).first()
1✔
648
    if revision:
1✔
649
        document = revision.document
1✔
650
        if document.owner == request.user:
1✔
651
            status = 200
1✔
652
            revision.delete()
1✔
653
    return JsonResponse(response, status=status)
1✔
654

655

656
# Check doc access rights.
657
def has_doc_access(doc, user):
13✔
658
    if doc.owner == user:
1✔
659
        return True
1✔
660
    access_rights = AccessRight.objects.filter(document=doc, user=user).first()
1✔
661
    if access_rights:
1✔
662
        return True
1✔
663
    else:
664
        return False
×
665

666

667
@login_required
13✔
668
@ajax_required
13✔
669
@require_POST
13✔
670
def comment_notify(request):
13✔
671
    response = {}
1✔
672
    doc_id = request.POST["doc_id"]
1✔
673
    collaborator_id = request.POST["collaborator_id"]
1✔
674
    comment_text = request.POST["comment_text"]
1✔
675
    comment_html = bleach.clean(request.POST["comment_html"], strip=True)
1✔
676
    notification_type = request.POST["type"]
1✔
677
    User = get_user_model()
1✔
678
    collaborator = User.objects.filter(pk=collaborator_id).first()
1✔
679
    document = Document.objects.filter(pk=doc_id).first()
1✔
680
    if (
1✔
681
        not document
682
        or not collaborator
683
        or not comment_text
684
        or not comment_html
685
        or not has_doc_access(document, request.user)
686
        or not notification_type
687
    ):
688
        return JsonResponse(response, status=403)
×
689
    if not has_doc_access(document, collaborator):
1✔
690
        # Tagged user has no access to document and will therefore not be
691
        # notified
692
        return JsonResponse(response, status=200)
×
693
    commentator = request.user.readable_name
1✔
694
    collaborator_name = collaborator.readable_name
1✔
695
    collaborator_email = collaborator.email
1✔
696
    document_title = document.title
1✔
697
    if len(document_title) == 0:
1✔
698
        document_title = _("Untitled")
×
699
    link = HttpRequest.build_absolute_uri(request, document.get_absolute_url())
1✔
700
    emails.send_comment_notification(
1✔
701
        notification_type,
702
        commentator,
703
        collaborator_name,
704
        collaborator_email,
705
        link,
706
        document_title,
707
        comment_text,
708
        comment_html,
709
    )
710
    return JsonResponse(response, status=200)
1✔
711

712

713
@login_required
13✔
714
@ajax_required
13✔
715
@require_POST
13✔
716
def get_template_for_doc(request):
13✔
717
    doc_id = request.POST.get("id")
3✔
718
    doc = (
3✔
719
        Document.objects.filter(id=doc_id)
720
        .filter(Q(owner=request.user) | Q(accessright__user=request.user))
721
        .first()
722
    )
723
    if doc is None:
3✔
724
        return JsonResponse({}, status=405)
1✔
725
    doc_template = doc.template
3✔
726
    serializer = PythonWithURLSerializer()
3✔
727
    export_templates = serializer.serialize(
3✔
728
        doc_template.exporttemplate_set.all()
729
    )
730
    document_styles = serializer.serialize(
3✔
731
        doc_template.documentstyle_set.all(),
732
        use_natural_foreign_keys=True,
733
        fields=["title", "slug", "contents", "documentstylefile_set"],
734
    )
735
    response = {
3✔
736
        "id": doc_template.id,
737
        "title": doc_template.title,
738
        "content": doc_template.content,
739
        "doc_version": doc_template.doc_version,
740
        "export_templates": export_templates,
741
        "document_styles": document_styles,
742
    }
743
    return JsonResponse(response, status=200)
3✔
744

745

746
@login_required
13✔
747
@ajax_required
13✔
748
@require_POST
13✔
749
def get_ws_host(request):
13✔
750
    response = {}
9✔
751
    doc_id = int(request.POST.get("id"))
9✔
752
    ws_server = settings.WS_SERVERS[doc_id % len(settings.WS_SERVERS)]
9✔
753
    response["ws_host"] = get_host(request.headers["Origin"], ws_server)
9✔
754
    return JsonResponse(response, status=200)
9✔
755

756

757
# maintenance views
758
@staff_member_required
13✔
759
@ajax_required
13✔
760
@require_POST
13✔
761
def get_all_old_docs(request):
13✔
762
    response = {}
1✔
763
    status = 200
1✔
764
    doc_list = Document.objects.filter(
1✔
765
        doc_version__lt=str(FW_DOCUMENT_VERSION)
766
    )[:10]
767
    response["docs"] = serializers.serialize("json", doc_list)
1✔
768
    return JsonResponse(response, status=status)
1✔
769

770

771
@staff_member_required
13✔
772
@ajax_required
13✔
773
@require_POST
13✔
774
def save_doc(request):
13✔
775
    response = {}
×
776
    status = 200
×
777
    doc_id = request.POST["id"]
×
778
    doc = Document.objects.get(pk=int(doc_id))
×
779
    # Only looking at fields that may have changed.
780
    content = request.POST.get("content", False)
×
781
    bibliography = request.POST.get("bibliography", False)
×
782
    comments = request.POST.get("comments", False)
×
783
    diffs = request.POST.get("diffs", False)
×
784
    version = request.POST.get("version", False)
×
785
    if content:
×
786
        doc.content = json.loads(content)
×
787
    if bibliography:
×
788
        doc.bibliography = json.loads(bibliography)
×
789
    if comments:
×
790
        doc.comments = json.loads(comments)
×
791
    if version:
×
792
        doc.version = version
×
793
    if diffs:
×
794
        doc.diffs = json.loads(diffs)
×
795
    doc.doc_version = FW_DOCUMENT_VERSION
×
796
    doc.save()
×
797
    return JsonResponse(response, status=status)
×
798

799

800
@staff_member_required
13✔
801
@ajax_required
13✔
802
@require_POST
13✔
803
def get_user_biblist(request):
13✔
804
    response = {}
×
805
    status = 200
×
806
    user_id = request.POST["user_id"]
×
807
    response["bibList"] = serializer.serialize(
×
808
        Entry.objects.filter(entry_owner_id=user_id),
809
        fields=("entry_key", "entry_owner", "bib_type", "fields"),
810
    )
811
    return JsonResponse(response, status=status)
×
812

813

814
@staff_member_required
13✔
815
@ajax_required
13✔
816
@require_POST
13✔
817
def get_all_template_ids(request):
13✔
818
    response = {}
1✔
819
    status = 200
1✔
820
    templates = DocumentTemplate.objects.filter(
1✔
821
        doc_version__lt=str(FW_DOCUMENT_VERSION)
822
    ).only("id")
823
    response["template_ids"] = []
1✔
824
    for template in templates:
1✔
825
        response["template_ids"].append(template.id)
1✔
826
    return JsonResponse(response, status=status)
1✔
827

828

829
@staff_member_required
13✔
830
@ajax_required
13✔
831
@require_POST
13✔
832
def get_template_admin(request, type="all"):
13✔
833
    template_id = request.POST.get("id")
1✔
834
    doc_template = DocumentTemplate.objects.filter(pk=int(template_id)).first()
1✔
835
    if doc_template is None:
1✔
836
        return JsonResponse({}, status=405)
×
837
    response = {}
1✔
838
    if type in ["all", "base"]:
1✔
839
        response["id"] = doc_template.id
1✔
840
        response["title"] = doc_template.title
1✔
841
        response["content"] = doc_template.content
1✔
842
        response["doc_version"] = doc_template.doc_version
1✔
843
    if type in ["all", "extras"]:
1✔
844
        serializer = PythonWithURLSerializer()
1✔
845
        export_templates = serializer.serialize(
1✔
846
            doc_template.exporttemplate_set.all()
847
        )
848
        document_styles = serializer.serialize(
1✔
849
            doc_template.documentstyle_set.all(),
850
            use_natural_foreign_keys=True,
851
            fields=["title", "slug", "contents", "documentstylefile_set"],
852
        )
853
        response["export_templates"] = export_templates
1✔
854
        response["document_styles"] = document_styles
1✔
855
    return JsonResponse(response, status=200)
1✔
856

857

858
@staff_member_required
13✔
859
@ajax_required
13✔
860
@require_POST
13✔
861
def save_template(request):
13✔
862
    response = {}
1✔
863
    status = 405
1✔
864
    template_id = request.POST["id"]
1✔
865
    template = DocumentTemplate.objects.filter(pk=int(template_id)).first()
1✔
866
    if template:
1✔
867
        status = 200
1✔
868
        # Only looking at fields that may have changed.
869
        content = request.POST.get("content", False)
1✔
870
        if content:
1✔
871
            template.content = json.loads(content)
1✔
872
        template.doc_version = FW_DOCUMENT_VERSION
1✔
873
        template.save()
1✔
874
    return JsonResponse(response, status=status)
1✔
875

876

877
@staff_member_required
13✔
878
@ajax_required
13✔
879
@require_POST
13✔
880
def create_template_admin(request):
13✔
881
    response = {}
1✔
882
    title = request.POST.get("title")
1✔
883
    content = json.loads(request.POST.get("content"))
1✔
884
    import_id = request.POST.get("import_id")
1✔
885
    document_styles = json.loads(request.POST.get("document_styles"))
1✔
886
    export_templates = json.loads(request.POST.get("export_templates"))
1✔
887
    template = DocumentTemplate.objects.create(
1✔
888
        title=title,
889
        content=content,
890
        doc_version=FW_DOCUMENT_VERSION,
891
        import_id=import_id,
892
    )
893
    response["id"] = template.id
1✔
894
    response["title"] = template.title
1✔
895
    date_format = "%Y-%m-%d"
1✔
896
    response["added"] = template.added.strftime(date_format)
1✔
897
    response["updated"] = template.updated.strftime(date_format)
1✔
898
    files = request.FILES.getlist("files[]")
1✔
899
    for style in document_styles:
1✔
900
        doc_style = DocumentStyle.objects.create(
1✔
901
            title=style["title"],
902
            slug=style["slug"],
903
            contents=style["contents"],
904
            document_template=template,
905
        )
906
        for filepath in style["files"]:
1✔
907
            filename = filepath.split("/").pop()
1✔
908
            file = next((x for x in files if x.name == filename), None)
1✔
909
            if file:
1✔
910
                DocumentStyleFile.objects.create(file=file, style=doc_style)
1✔
911
    for e_template in export_templates:
1✔
912
        filename = e_template["file"].split("/").pop()
1✔
913
        file = next((x for x in files if x.name == filename), None)
1✔
914
        if file:
1✔
915
            ExportTemplate.objects.create(
1✔
916
                document_template=template,
917
                template_file=file,
918
                file_type=e_template["file_type"],
919
            )
920
    return JsonResponse(response, status=201)
1✔
921

922

923
@staff_member_required
13✔
924
@ajax_required
13✔
925
@require_POST
13✔
926
def get_all_revision_ids(request):
13✔
927
    response = {}
1✔
928
    status = 200
1✔
929
    revisions = DocumentRevision.objects.filter(
1✔
930
        doc_version__lt=str(FW_DOCUMENT_VERSION)
931
    ).only("id")
932
    response["revision_ids"] = []
1✔
933
    for revision in revisions:
1✔
934
        response["revision_ids"].append(revision.id)
×
935
    return JsonResponse(response, status=status)
1✔
936

937

938
@staff_member_required
13✔
939
@ajax_required
13✔
940
@require_POST
13✔
941
def update_revision(request):
13✔
942
    response = {}
×
943
    status = 405
×
944
    revision_id = request.POST["id"]
×
945
    revision = DocumentRevision.objects.filter(pk=int(revision_id)).first()
×
946
    if revision:
×
947
        status = 200
×
948
        # keep the filename
949
        file_name = revision.file_object.name.split("/")[-1]
×
950
        # Delete the FieldFile as otherwise the file remains.
951
        revision.file_object.delete()
×
952
        revision.file_object = request.FILES["file"]
×
953
        revision.file_object.name = file_name
×
954
        revision.doc_version = FW_DOCUMENT_VERSION
×
955
        revision.save()
×
956
    return JsonResponse(response, status=status)
×
957

958

959
@staff_member_required
13✔
960
@ajax_required
13✔
961
@require_POST
13✔
962
def add_images_to_doc(request):
13✔
963
    response = {}
×
964
    status = 201
×
965
    doc_id = request.POST["doc_id"]
×
966
    doc = Document.objects.get(id=doc_id)
×
967
    # Delete all existing image links
968
    DocumentImage.objects.filter(document_id=doc_id).delete()
×
969
    ids = request.POST.getlist("ids[]")
×
970
    for id in ids:
×
971
        doc_image_data = {"document": doc, "title": "Deleted"}
×
972
        image = Image.objects.filter(id=id).first()
×
973
        if image:
×
974
            user_image = image.userimage_set.all().first()
×
975
            if user_image:
×
976
                doc_image_data["title"] = user_image.title
×
977
                doc_image_data["copyright"] = user_image.copyright
×
978
        else:
979
            image = Image()
×
980
            image.pk = id
×
981
            image.uploader = doc.owner
×
982
            f = open(
×
983
                os.path.join(
984
                    settings.PROJECT_PATH, "base/static/img/error.png"
985
                )
986
            )
987
            image.image.save("error.png", File(f))
×
988
            image.save()
×
989
        doc_image_data["image"] = image
×
990
        DocumentImage.objects.create(**doc_image_data)
×
991
    return JsonResponse(response, status=status)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc