|
4ce269c…
|
ragelink
|
1 |
import markdown |
|
4ce269c…
|
ragelink
|
2 |
from django.contrib import messages |
|
4ce269c…
|
ragelink
|
3 |
from django.contrib.auth.decorators import login_required |
|
c588255…
|
ragelink
|
4 |
from django.core.paginator import Paginator |
|
4ce269c…
|
ragelink
|
5 |
from django.http import HttpResponse |
|
4ce269c…
|
ragelink
|
6 |
from django.shortcuts import get_object_or_404, redirect, render |
|
4ce269c…
|
ragelink
|
7 |
from django.utils.safestring import mark_safe |
|
4ce269c…
|
ragelink
|
8 |
|
|
c588255…
|
ragelink
|
9 |
from core.pagination import PER_PAGE_OPTIONS, get_per_page |
|
4ce269c…
|
ragelink
|
10 |
from core.permissions import P |
|
c588255…
|
ragelink
|
11 |
from core.sanitize import sanitize_html |
|
4ce269c…
|
ragelink
|
12 |
from organization.views import get_org |
|
4ce269c…
|
ragelink
|
13 |
|
|
4ce269c…
|
ragelink
|
14 |
from .forms import PageForm |
|
4ce269c…
|
ragelink
|
15 |
from .models import Page |
|
4ce269c…
|
ragelink
|
16 |
|
|
4ce269c…
|
ragelink
|
17 |
|
|
4ce269c…
|
ragelink
|
18 |
def page_list(request): |
|
c588255…
|
ragelink
|
19 |
# Published pages are visible to everyone (including anonymous users). |
|
c588255…
|
ragelink
|
20 |
# Authenticated editors/admins can also see unpublished drafts. |
|
c588255…
|
ragelink
|
21 |
if request.user.is_authenticated and (request.user.has_perm("pages.change_page") or request.user.is_superuser): |
|
4ce269c…
|
ragelink
|
22 |
pages = Page.objects.all() |
|
c588255…
|
ragelink
|
23 |
else: |
|
c588255…
|
ragelink
|
24 |
pages = Page.objects.filter(is_published=True) |
|
4ce269c…
|
ragelink
|
25 |
|
|
4ce269c…
|
ragelink
|
26 |
search = request.GET.get("search", "").strip() |
|
4ce269c…
|
ragelink
|
27 |
if search: |
|
4ce269c…
|
ragelink
|
28 |
pages = pages.filter(name__icontains=search) |
|
4ce269c…
|
ragelink
|
29 |
|
|
c588255…
|
ragelink
|
30 |
per_page = get_per_page(request) |
|
c588255…
|
ragelink
|
31 |
paginator = Paginator(pages, per_page) |
|
c588255…
|
ragelink
|
32 |
page_obj = paginator.get_page(request.GET.get("page", 1)) |
|
c588255…
|
ragelink
|
33 |
|
|
c588255…
|
ragelink
|
34 |
ctx = {"pages": page_obj, "page_obj": page_obj, "search": search, "per_page": per_page, "per_page_options": PER_PAGE_OPTIONS} |
|
c588255…
|
ragelink
|
35 |
|
|
4ce269c…
|
ragelink
|
36 |
if request.headers.get("HX-Request"): |
|
c588255…
|
ragelink
|
37 |
return render(request, "pages/partials/page_table.html", ctx) |
|
4ce269c…
|
ragelink
|
38 |
|
|
c588255…
|
ragelink
|
39 |
return render(request, "pages/page_list.html", ctx) |
|
4ce269c…
|
ragelink
|
40 |
|
|
4ce269c…
|
ragelink
|
41 |
|
|
4ce269c…
|
ragelink
|
42 |
@login_required |
|
4ce269c…
|
ragelink
|
43 |
def page_create(request): |
|
4ce269c…
|
ragelink
|
44 |
P.PAGE_ADD.check(request.user) |
|
4ce269c…
|
ragelink
|
45 |
org = get_org() |
|
4ce269c…
|
ragelink
|
46 |
|
|
4ce269c…
|
ragelink
|
47 |
if request.method == "POST": |
|
4ce269c…
|
ragelink
|
48 |
form = PageForm(request.POST) |
|
4ce269c…
|
ragelink
|
49 |
if form.is_valid(): |
|
4ce269c…
|
ragelink
|
50 |
page = form.save(commit=False) |
|
4ce269c…
|
ragelink
|
51 |
page.organization = org |
|
4ce269c…
|
ragelink
|
52 |
page.created_by = request.user |
|
4ce269c…
|
ragelink
|
53 |
page.save() |
|
4ce269c…
|
ragelink
|
54 |
messages.success(request, f'Page "{page.name}" created.') |
|
4ce269c…
|
ragelink
|
55 |
return redirect("pages:detail", slug=page.slug) |
|
4ce269c…
|
ragelink
|
56 |
else: |
|
4ce269c…
|
ragelink
|
57 |
form = PageForm() |
|
4ce269c…
|
ragelink
|
58 |
|
|
4ce269c…
|
ragelink
|
59 |
return render(request, "pages/page_form.html", {"form": form, "title": "New Page"}) |
|
4ce269c…
|
ragelink
|
60 |
|
|
4ce269c…
|
ragelink
|
61 |
|
|
4ce269c…
|
ragelink
|
62 |
def page_detail(request, slug): |
|
c588255…
|
ragelink
|
63 |
from django.core.exceptions import PermissionDenied |
|
c588255…
|
ragelink
|
64 |
|
|
4ce269c…
|
ragelink
|
65 |
page = get_object_or_404(Page, slug=slug, deleted_at__isnull=True) |
|
c588255…
|
ragelink
|
66 |
# Published pages are public. Unpublished drafts require auth + edit permission. |
|
c588255…
|
ragelink
|
67 |
if not page.is_published: |
|
c588255…
|
ragelink
|
68 |
if not request.user.is_authenticated: |
|
c588255…
|
ragelink
|
69 |
raise PermissionDenied("Authentication required.") |
|
c588255…
|
ragelink
|
70 |
if not (request.user.has_perm("pages.change_page") or request.user.is_superuser): |
|
c588255…
|
ragelink
|
71 |
raise PermissionDenied("You don't have permission to view this draft page.") |
|
c588255…
|
ragelink
|
72 |
content_html = mark_safe(sanitize_html(markdown.markdown(page.content, extensions=["fenced_code", "tables", "toc"]))) |
|
4ce269c…
|
ragelink
|
73 |
return render(request, "pages/page_detail.html", {"page": page, "content_html": content_html}) |
|
4ce269c…
|
ragelink
|
74 |
|
|
4ce269c…
|
ragelink
|
75 |
|
|
4ce269c…
|
ragelink
|
76 |
@login_required |
|
4ce269c…
|
ragelink
|
77 |
def page_update(request, slug): |
|
4ce269c…
|
ragelink
|
78 |
P.PAGE_CHANGE.check(request.user) |
|
4ce269c…
|
ragelink
|
79 |
page = get_object_or_404(Page, slug=slug, deleted_at__isnull=True) |
|
4ce269c…
|
ragelink
|
80 |
|
|
4ce269c…
|
ragelink
|
81 |
if request.method == "POST": |
|
4ce269c…
|
ragelink
|
82 |
form = PageForm(request.POST, instance=page) |
|
4ce269c…
|
ragelink
|
83 |
if form.is_valid(): |
|
4ce269c…
|
ragelink
|
84 |
page = form.save(commit=False) |
|
4ce269c…
|
ragelink
|
85 |
page.updated_by = request.user |
|
4ce269c…
|
ragelink
|
86 |
page.save() |
|
4ce269c…
|
ragelink
|
87 |
messages.success(request, f'Page "{page.name}" updated.') |
|
4ce269c…
|
ragelink
|
88 |
return redirect("pages:detail", slug=page.slug) |
|
4ce269c…
|
ragelink
|
89 |
else: |
|
4ce269c…
|
ragelink
|
90 |
form = PageForm(instance=page) |
|
4ce269c…
|
ragelink
|
91 |
|
|
4ce269c…
|
ragelink
|
92 |
return render(request, "pages/page_form.html", {"form": form, "page": page, "title": "Edit Page"}) |
|
4ce269c…
|
ragelink
|
93 |
|
|
4ce269c…
|
ragelink
|
94 |
|
|
4ce269c…
|
ragelink
|
95 |
@login_required |
|
4ce269c…
|
ragelink
|
96 |
def page_delete(request, slug): |
|
4ce269c…
|
ragelink
|
97 |
P.PAGE_DELETE.check(request.user) |
|
4ce269c…
|
ragelink
|
98 |
page = get_object_or_404(Page, slug=slug, deleted_at__isnull=True) |
|
4ce269c…
|
ragelink
|
99 |
|
|
4ce269c…
|
ragelink
|
100 |
if request.method == "POST": |
|
4ce269c…
|
ragelink
|
101 |
page.soft_delete(user=request.user) |
|
4ce269c…
|
ragelink
|
102 |
messages.success(request, f'Page "{page.name}" deleted.') |
|
4ce269c…
|
ragelink
|
103 |
|
|
4ce269c…
|
ragelink
|
104 |
if request.headers.get("HX-Request"): |
|
4ce269c…
|
ragelink
|
105 |
return HttpResponse(status=200, headers={"HX-Redirect": "/kb/"}) |
|
4ce269c…
|
ragelink
|
106 |
|
|
4ce269c…
|
ragelink
|
107 |
return redirect("pages:list") |
|
4ce269c…
|
ragelink
|
108 |
|
|
4ce269c…
|
ragelink
|
109 |
return render(request, "pages/page_confirm_delete.html", {"page": page}) |