views.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. # textdocs/views.py
  2. from django.shortcuts import render, get_object_or_404, redirect
  3. from django.urls import reverse_lazy # NEW: Useful for delete success URL
  4. from django.http import HttpResponse
  5. from django.template.loader import render_to_string
  6. from weasyprint import HTML
  7. from django.contrib.auth.decorators import login_required
  8. from django.http import JsonResponse
  9. from django.views.decorators.csrf import csrf_exempt
  10. from django.core.files.storage import default_storage
  11. import os
  12. from django.conf import settings
  13. from .models import Document, DocumentVersion # Changed from Document
  14. from .forms import DocumentVersionForm, DocumentCreateForm # Changed from DocumentForm
  15. from .utils import convert_media_urls_to_file_paths # NEW: Utility function for media URL conversion
  16. @login_required
  17. def document_list(request):
  18. """Shows a list of all created documents."""
  19. documents = Document.objects.filter(author=request.user).order_by('-created_at')
  20. return render(request, 'textdocs/document_list.html', {'documents': documents})
  21. @login_required
  22. def document_detail(request, pk):
  23. """Shows the details of a single document."""
  24. document = get_object_or_404(Document, pk=pk, author=request.user)
  25. return render(request, 'textdocs/document_detail.html', {'document': document})
  26. @login_required
  27. def document_create(request):
  28. """
  29. Handles the creation of a new parent Document and its first version.
  30. This is a two-form process now, but we'll combine them logically.
  31. """
  32. if request.method == 'POST':
  33. title_form = DocumentCreateForm(request.POST)
  34. content_form = DocumentVersionForm(request.POST)
  35. if title_form.is_valid() and content_form.is_valid():
  36. # Create the parent Document
  37. document = Document.objects.create(
  38. author=request.user,
  39. title=title_form.cleaned_data['title']
  40. )
  41. # Create the first version
  42. version = content_form.save(commit=False)
  43. version.document = document
  44. version.version_number = 1
  45. version.save()
  46. return redirect(document.get_absolute_url())
  47. else:
  48. title_form = DocumentCreateForm()
  49. content_form = DocumentVersionForm()
  50. return render(request, 'textdocs/document_form.html', {
  51. 'title_form': title_form,
  52. 'content_form': content_form
  53. })
  54. @login_required
  55. def document_edit(request, pk):
  56. """
  57. Handles creating a NEW version of an existing document.
  58. This replaces the old 'update' view.
  59. """
  60. document = get_object_or_404(Document, pk=pk, author=request.user)
  61. latest_version = document.latest_version
  62. if request.method == 'POST':
  63. form = DocumentVersionForm(request.POST)
  64. if form.is_valid():
  65. new_version = form.save(commit=False)
  66. new_version.document = document
  67. # Increment the version number
  68. new_version.version_number = latest_version.version_number + 1
  69. new_version.save()
  70. return redirect(document.get_absolute_url())
  71. else:
  72. # Pre-populate the form with the content of the latest version
  73. form = DocumentVersionForm(instance=latest_version)
  74. return render(request, 'textdocs/document_edit_form.html', {
  75. 'form': form,
  76. 'document': document
  77. })
  78. # NEW VIEW: To see old versions
  79. @login_required
  80. def view_version(request, pk, version_number):
  81. document = get_object_or_404(Document, pk=pk, author=request.user)
  82. version = get_object_or_404(DocumentVersion, document=document, version_number=version_number)
  83. return render(request, 'textdocs/version_detail.html', {'version': version})
  84. # --- NEW: View for deleting a document ---
  85. @login_required
  86. def document_delete(request, pk):
  87. """Handles the deletion of a document and all its versions."""
  88. document = get_object_or_404(Document, pk=pk, author=request.user)
  89. if request.method == 'POST':
  90. # This is after the user confirms the deletion
  91. document.versions.all().delete() # First delete all versions
  92. document.delete() # Then delete the document itself
  93. return redirect('document_list') # Redirect to the list after deletion
  94. # If it's a GET request, show the confirmation page
  95. return render(request, 'textdocs/document_confirm_delete.html', {'document': document})
  96. @login_required
  97. def generate_pdf(request, pk):
  98. """
  99. Generates a PDF for a document's LATEST version.
  100. This is triggered from the main document detail page.
  101. """
  102. # First, get the parent document to ensure ownership
  103. document = get_object_or_404(Document, pk=pk, author=request.user)
  104. # MODIFIED: Get the latest version using our model property
  105. latest_version = document.latest_version
  106. # ADDED: A safety check in case a document has no versions
  107. if not latest_version:
  108. return HttpResponse("This document has no versions and cannot be exported.", status=404)
  109. # REUSE: We can use our existing version_pdf.html template!
  110. html_string = render_to_string('textdocs/version_pdf.html', {'version': latest_version})
  111. html_string = convert_media_urls_to_file_paths(html_string) # NEW: Convert media URLs to file paths for WeasyPrint
  112. # Generate the PDF with WeasyPrint
  113. html = HTML(string=html_string)
  114. pdf = html.write_pdf()
  115. # Create the HTTP response with the PDF file
  116. response = HttpResponse(pdf, content_type='application/pdf')
  117. # Make the filename include the version number for clarity
  118. filename = f'{document.title}_v{latest_version.version_number}.pdf'
  119. response['Content-Disposition'] = f'attachment; filename="{filename}"'
  120. return response
  121. @login_required
  122. def generate_version_pdf(request, pk, version_number):
  123. """Generates a PDF for a SPECIFIC historical version of a document."""
  124. # First, get the parent document to ensure ownership
  125. document = get_object_or_404(Document, pk=pk, author=request.user)
  126. # Now, get the specific version associated with that document
  127. version = get_object_or_404(DocumentVersion, document=document, version_number=version_number)
  128. # We'll create a dedicated PDF template for versions to keep things clean
  129. html_string = render_to_string('textdocs/version_pdf.html', {'version': version})
  130. html_string = convert_media_urls_to_file_paths(html_string) # Convert media URLs to file paths for WeasyPrint
  131. # Generate the PDF with WeasyPrint
  132. html = HTML(string=html_string)
  133. pdf = html.write_pdf()
  134. # Create the HTTP response with the PDF file
  135. response = HttpResponse(pdf, content_type='application/pdf')
  136. # Make the filename include the version number for clarity
  137. filename = f'{version.document.title}_v{version.version_number}.pdf'
  138. response['Content-Disposition'] = f'attachment; filename="{filename}"'
  139. return response
  140. @csrf_exempt # Use with caution, see note below
  141. @login_required
  142. def tinymce_image_upload(request):
  143. """
  144. Handles image uploads from the TinyMCE editor.
  145. """
  146. if request.method != 'POST':
  147. return JsonResponse({'error': 'Invalid request method.'})
  148. # 'file' is the name TinyMCE uses for the uploaded file
  149. file = request.FILES.get('file')
  150. if not file:
  151. return JsonResponse({'error': 'No file provided.'})
  152. # You can add more validation here (e.g., file type, size)
  153. # Save the file to your media directory
  154. file_path = default_storage.save(os.path.join('tinymce_uploads', file.name), file)
  155. # Get the URL for the saved file
  156. file_url = default_storage.url(file_path)
  157. # Return the URL in the format TinyMCE expects
  158. return JsonResponse({'location': file_url})
  159. @login_required
  160. def image_browser(request):
  161. """
  162. Displays a gallery of all previously uploaded images.
  163. TinyMCE opens this page in a dialog, and clicking an image
  164. selects it and closes the dialog.
  165. """
  166. upload_dir = os.path.join(settings.MEDIA_ROOT, 'tinymce_uploads')
  167. images = []
  168. if os.path.exists(upload_dir):
  169. for filename in sorted(os.listdir(upload_dir)):
  170. # Only include common image file types
  171. if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg')):
  172. images.append({
  173. 'filename': filename,
  174. 'url': f'{settings.MEDIA_URL}tinymce_uploads/{filename}',
  175. })
  176. return render(request, 'textdocs/image_browser.html', {'images': images})