@django-stack/cli 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +65 -20
- package/dist/index.d.ts +24 -9
- package/dist/index.js +1 -1
- package/dist/{production-M2SsuuBb.js → production-WThC1aS2.js} +267 -30
- package/package.json +1 -1
- package/templates/backend/Dockerfile +7 -2
- package/templates/backend/config/settings.py +13 -0
- package/templates/backend/config/urls.py +11 -0
- package/templates/backend/content/admin.py +14 -5
- package/templates/backend/content/models.py +2 -2
- package/templates/backend/content/tests/test_api.py +15 -3
- package/templates/backend/pyproject.toml +1 -1
- package/templates/base/.github/workflows/deploy.yml +10 -1
- package/templates/base/AGENTS.md +86 -8
- package/templates/base/Caddyfile +24 -0
- package/templates/base/README.md +74 -6
- package/templates/base/SECURITY.md +2 -0
- package/templates/base/compose.yaml +28 -2
- package/templates/base/env.example +37 -0
- package/templates/frontend-astro/AGENTS.md +3 -3
- package/templates/frontend-astro/Dockerfile +6 -3
- package/templates/frontend-astro/astro.config.mjs +16 -5
- package/templates/frontend-astro/package.json +1 -1
- package/templates/frontend-astro/pnpm-workspace.yaml +17 -0
- package/templates/frontend-astro/src/components/ContactForm.astro +19 -3
- package/templates/frontend-astro/src/middleware.ts +9 -1
- package/templates/frontend-next/AGENTS.md +3 -3
- package/templates/frontend-next/Dockerfile +5 -2
- package/templates/frontend-next/package.json +3 -3
- package/templates/frontend-next/pnpm-workspace.yaml +11 -0
- package/templates/frontend-next/src/app/[...path]/page.tsx +2 -1
- package/templates/frontend-next/src/components/ContactForm.tsx +14 -3
- package/templates/frontend-next/src/instrumentation.ts +12 -0
- package/templates/frontend-next/src/proxy.ts +4 -1
- package/templates/frontend-nuxt/AGENTS.md +3 -3
- package/templates/frontend-nuxt/Dockerfile +5 -2
- package/templates/frontend-nuxt/nuxt.config.ts +7 -3
- package/templates/frontend-nuxt/package.json +2 -2
- package/templates/frontend-nuxt/pnpm-workspace.yaml +13 -0
- package/templates/frontend-nuxt/server/plugins/errors.ts +14 -0
- package/templates/frontend-nuxt/server/routes/_content/layout.get.ts +4 -0
- package/templates/frontend-nuxt/src/components/ContactForm.vue +15 -2
- package/templates/frontend-sveltekit/AGENTS.md +3 -3
- package/templates/frontend-sveltekit/Dockerfile +6 -3
- package/templates/frontend-sveltekit/package.json +2 -2
- package/templates/frontend-sveltekit/pnpm-workspace.yaml +11 -0
- package/templates/frontend-sveltekit/src/hooks.server.ts +13 -1
- package/templates/frontend-sveltekit/src/lib/components/ContactForm.svelte +15 -2
- package/templates/frontend-sveltekit/src/routes/+layout.server.ts +3 -0
- package/templates/frontend-sveltekit/src/routes/[...path]/+page.svelte +1 -1
- package/templates/frontend-sveltekit/vite.config.ts +10 -3
- package/templates/frontend-templates/template.json +5 -0
- package/templates/frontend-templates/website/__init__.py +0 -0
- package/templates/frontend-templates/website/apps.py +6 -0
- package/templates/frontend-templates/website/context_processors.py +16 -0
- package/templates/frontend-templates/website/forms.py +20 -0
- package/templates/frontend-templates/website/static/website/favicon.svg +1 -0
- package/templates/frontend-templates/website/static/website/global.css +232 -0
- package/templates/frontend-templates/website/templates/website/404.html +10 -0
- package/templates/frontend-templates/website/templates/website/base.html +44 -0
- package/templates/frontend-templates/website/templates/website/blocks/call_to_action.html +5 -0
- package/templates/frontend-templates/website/templates/website/blocks/image.html +4 -0
- package/templates/frontend-templates/website/templates/website/blocks/quote.html +4 -0
- package/templates/frontend-templates/website/templates/website/blocks/text.html +2 -0
- package/templates/frontend-templates/website/templates/website/home.html +8 -0
- package/templates/frontend-templates/website/templates/website/includes/contact_form.html +21 -0
- package/templates/frontend-templates/website/templates/website/includes/post_card.html +9 -0
- package/templates/frontend-templates/website/templates/website/includes/post_list.html +11 -0
- package/templates/frontend-templates/website/templates/website/page.html +10 -0
- package/templates/frontend-templates/website/templates/website/post.html +20 -0
- package/templates/frontend-templates/website/templates/website/post_list.html +11 -0
- package/templates/frontend-templates/website/templates/website/robots.txt +5 -0
- package/templates/frontend-templates/website/templates/website/rss.xml +16 -0
- package/templates/frontend-templates/website/templates/website/sitemap.xml +8 -0
- package/templates/frontend-templates/website/tests/__init__.py +0 -0
- package/templates/frontend-templates/website/tests/test_site.py +102 -0
- package/templates/frontend-templates/website/urls.py +17 -0
- package/templates/frontend-templates/website/views.py +176 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The pages of the site, rendered by Django. They read the content through
|
|
3
|
+
the ORM with ``published()``, which hides drafts and scheduled posts
|
|
4
|
+
unless an editor opened a preview, the same rules as the content API.
|
|
5
|
+
|
|
6
|
+
``@site_page`` caches each page until content changes (in Django's cache,
|
|
7
|
+
the database by default) and sets the Content-Security-Policy.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from django.conf import settings
|
|
11
|
+
from django.core.paginator import Paginator
|
|
12
|
+
from django.http import Http404, HttpResponseNotAllowed
|
|
13
|
+
from django.shortcuts import get_object_or_404, redirect, render
|
|
14
|
+
from django.views.decorators.http import require_safe
|
|
15
|
+
from django_stack.contrib.site import published, site_page
|
|
16
|
+
from rest_framework.throttling import SimpleRateThrottle
|
|
17
|
+
|
|
18
|
+
from content.models import Category, Page, Post, SiteSettings, Tag
|
|
19
|
+
|
|
20
|
+
from .forms import ContactForm
|
|
21
|
+
|
|
22
|
+
POSTS_PER_PAGE = 9
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def posts(request):
|
|
26
|
+
return published(Post.objects.select_related("author").prefetch_related("categories", "tags"), request)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def post_list(request, queryset):
|
|
30
|
+
"""One page of posts, from the ?page= parameter."""
|
|
31
|
+
number = request.GET.get("page", "1")
|
|
32
|
+
return Paginator(queryset, POSTS_PER_PAGE).get_page(number if number.isdigit() else 1)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@site_page
|
|
36
|
+
@require_safe
|
|
37
|
+
def home(request):
|
|
38
|
+
site = SiteSettings.objects.first()
|
|
39
|
+
home_page = site.home_page if site else None
|
|
40
|
+
if home_page and not published(Page.objects.filter(pk=home_page.pk), request).exists():
|
|
41
|
+
home_page = None
|
|
42
|
+
if home_page:
|
|
43
|
+
return render(request, "website/page.html", {"page": home_page})
|
|
44
|
+
return render(request, "website/home.html", {"posts": post_list(request, posts(request))})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@site_page
|
|
48
|
+
@require_safe
|
|
49
|
+
def blog(request):
|
|
50
|
+
return render(
|
|
51
|
+
request,
|
|
52
|
+
"website/post_list.html",
|
|
53
|
+
{"title": "Blog", "seo_page": {"title": "Blog"}, "posts": post_list(request, posts(request))},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@site_page
|
|
58
|
+
@require_safe
|
|
59
|
+
def post(request, slug):
|
|
60
|
+
item = get_object_or_404(posts(request), slug=slug)
|
|
61
|
+
return render(request, "website/post.html", {"post": item})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@site_page
|
|
65
|
+
@require_safe
|
|
66
|
+
def category(request, slug):
|
|
67
|
+
item = get_object_or_404(Category, slug=slug)
|
|
68
|
+
return render(
|
|
69
|
+
request,
|
|
70
|
+
"website/post_list.html",
|
|
71
|
+
{
|
|
72
|
+
"title": item.name,
|
|
73
|
+
"seo_page": {"title": item.name, "seo_description": item.description},
|
|
74
|
+
"label": "Category",
|
|
75
|
+
"description": item.description,
|
|
76
|
+
"posts": post_list(request, posts(request).filter(categories=item)),
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@site_page
|
|
82
|
+
@require_safe
|
|
83
|
+
def tag(request, slug):
|
|
84
|
+
item = get_object_or_404(Tag, slug=slug)
|
|
85
|
+
return render(
|
|
86
|
+
request,
|
|
87
|
+
"website/post_list.html",
|
|
88
|
+
{
|
|
89
|
+
"title": f"#{item.name}",
|
|
90
|
+
"seo_page": {"title": item.name},
|
|
91
|
+
"label": "Tag",
|
|
92
|
+
"posts": post_list(request, posts(request).filter(tags=item)),
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@site_page
|
|
98
|
+
def page(request, path):
|
|
99
|
+
item = published(Page.objects.all(), request).filter(path=path.strip("/")).first()
|
|
100
|
+
if item is None:
|
|
101
|
+
raise Http404
|
|
102
|
+
if item.path == "contact":
|
|
103
|
+
return contact(request, item)
|
|
104
|
+
if request.method not in ("GET", "HEAD"):
|
|
105
|
+
return HttpResponseNotAllowed(["GET", "HEAD"])
|
|
106
|
+
return render(request, "website/page.html", {"page": item})
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ContactThrottle(SimpleRateThrottle):
|
|
110
|
+
"""The API's limit for public forms, per IP address: PUBLIC_WRITE_THROTTLE_RATE (10/minute)."""
|
|
111
|
+
|
|
112
|
+
scope = "public_write"
|
|
113
|
+
|
|
114
|
+
def get_cache_key(self, request, view):
|
|
115
|
+
return self.cache_format % {"scope": self.scope, "ident": self.get_ident(request)}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@site_page(cache_page=False, captcha=True)
|
|
119
|
+
def contact(request, item):
|
|
120
|
+
"""The contact page: its blocks and the form, which posts to itself."""
|
|
121
|
+
form = ContactForm(request.POST or None)
|
|
122
|
+
sent = request.GET.get("sent") == "1"
|
|
123
|
+
status = 200
|
|
124
|
+
if request.method == "POST":
|
|
125
|
+
throttle = ContactThrottle()
|
|
126
|
+
if not throttle.allow_request(request, None):
|
|
127
|
+
form.add_error(None, "Too many messages; try again in a minute.")
|
|
128
|
+
status = 429
|
|
129
|
+
elif form.is_valid():
|
|
130
|
+
form.save()
|
|
131
|
+
# Post/redirect/get: a reload does not send the message again.
|
|
132
|
+
return redirect(f"{request.path}?sent=1")
|
|
133
|
+
else:
|
|
134
|
+
status = 400
|
|
135
|
+
return render(request, "website/page.html", {"page": item, "form": form, "sent": sent}, status=status)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@site_page
|
|
139
|
+
@require_safe
|
|
140
|
+
def rss(request):
|
|
141
|
+
site = SiteSettings.objects.first()
|
|
142
|
+
return render(
|
|
143
|
+
request,
|
|
144
|
+
"website/rss.xml",
|
|
145
|
+
{"site": site, "site_url": settings.SITE_URL, "posts": posts(request).order_by("-published_at")[:20]},
|
|
146
|
+
content_type="application/rss+xml; charset=utf-8",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@site_page
|
|
151
|
+
@require_safe
|
|
152
|
+
def sitemap(request):
|
|
153
|
+
site = SiteSettings.objects.first()
|
|
154
|
+
pages = published(Page.objects.all(), request)
|
|
155
|
+
if site and site.home_page_id:
|
|
156
|
+
pages = pages.exclude(pk=site.home_page_id)
|
|
157
|
+
return render(
|
|
158
|
+
request,
|
|
159
|
+
"website/sitemap.xml",
|
|
160
|
+
{"site_url": settings.SITE_URL, "pages": pages, "posts": posts(request)},
|
|
161
|
+
content_type="application/xml; charset=utf-8",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@site_page
|
|
166
|
+
@require_safe
|
|
167
|
+
def robots(request):
|
|
168
|
+
return render(
|
|
169
|
+
request, "website/robots.txt", {"site_url": settings.SITE_URL}, content_type="text/plain; charset=utf-8"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@site_page(cache_page=False)
|
|
174
|
+
def not_found(request, exception=None):
|
|
175
|
+
"""The 404 page, with the site's layout (handler404 in config/urls.py)."""
|
|
176
|
+
return render(request, "website/404.html", {"not_found": {"title": "Page not found"}}, status=404)
|