The Views¶
About this document
This section covers the core views in django-resume for displaying, creating, and deleting resumes. It also includes a redirect view for convenience.
Overview¶
The resume_list and resume_delete views are defined in
django_resume.views. The cover/detail, CV, and permission-denied pages are
no longer plain views; they are registered ResumePage classes
(CoverLetterPage, CvPage, PermissionDeniedPage) in
django_resume.pages, and their routes are generated by
page_registry.get_urls(). The bare <slug:slug>/ catch-all (the
detail page) is emitted last by construction, so the more specific
cv/ and 403/ routes still resolve unambiguously. The URL names
(detail, cv, 403) are preserved for reverse() and external
links.
The page templates below resolve as
django_resume/pages/{resume.current_theme}/{template_name} and fall back to
the plain theme (frame and section fragments together) when the active theme
ships no such template, so a missing theme template does not raise
TemplateDoesNotExist.
The page registry itself – the ResumePage base
class, PageRegistry, capability-based section
selection, page discovery (installed apps and entry points), and the navigation
template tags – is documented in The Page Registry. See
Creating Page Plugins to add your own pages and
Pluggable Resume Pages for the design rationale.
The URL patterns are found in django_resume.urls (shown below for
reference):
from django.urls import path, reverse
from django.views.generic import RedirectView
from . import views
from .pages import page_registry
class CvRedirectView(RedirectView):
permanent = True
def get_redirect_url(self, *args, **kwargs) -> str:
slug = kwargs["slug"]
return reverse("django_resume:cv", kwargs={"slug": slug})
app_name = "django_resume"
urlpatterns = [
# resumes (non-page routes)
path("", views.resume_list, name="list"),
path("<slug:slug>/delete/", views.resume_delete, name="delete"),
path("cv/<slug:slug>/", CvRedirectView.as_view(), name="cv-redirect"),
# cover, cv and 403 pages (generated; bare "<slug:slug>/" catch-all is last)
*page_registry.get_urls(),
]
resume_list(request)¶
- django_resume.views.resume_list(request: HttpRequest) HttpResponse¶
Displays a list of resumes belonging to the currently authenticated user. It also provides a form for creating new resumes.
URL name:
django_resume:listMethods:GET,POSTRequires: Authentication (viadjango.contrib.auth.decorators.login_required()) Templates:On
GETrequests:django_resume/pages/plain/resume_list.htmlOn
POSTrequests:django_resume/pages/plain/resume_list_main.html
Behavior:
When requesting via
GET, displays a list of the user’s resumes and an emptyResumeFormfor creating a new resume.When requesting via
POST, processes the submitted form data. If valid, a newResumeis created, owned by the current user. The template re-renders with the newly created resume’s info.
Example:
from django.urls import reverse from django.test import Client client = Client() client.login(username="alice", password="password") # GET usage response = client.get(reverse("django_resume:list")) assert response.status_code == 200 # POST usage to create a new resume response = client.post( reverse("django_resume:list"), data={"name": "My New Resume", "slug": "my-new-resume"} ) assert response.status_code == 200
resume_delete(request, slug)¶
- django_resume.views.resume_delete(request: HttpRequest, slug: str) HttpResponse¶
Deletes the specified resume if it belongs to the currently authenticated user.
URL name:
django_resume:deleteMethods:DELETERequires: Authentication (viadjango.contrib.auth.decorators.login_required()) Template: None (returns an HTTP status code)Behavior:
Fetches the
Resumebyslug.If the resume’s owner matches the current user, it deletes the resume.
Responds with a 403 (Forbidden) if ownership does not match.
Returns HTTP 200 on success (rather than 204) for HTMX compatibility.
Example:
from django.urls import reverse from django.test import Client client = Client() client.login(username="alice", password="password") response = client.delete(reverse("django_resume:delete", args=["my-resume"])) assert response.status_code == 200
CoverLetterPage (django_resume:detail)¶
- class django_resume.views.CoverLetterPage¶
A registered
ResumePageindjango_resume.pages. Displays the “detail” page of a resume, often used for a cover letter or high-level resume info, along with select plugin data. It is the default/root page (path = ""), so its route is the bare<slug:slug>/catch-all.URL name:
django_resume:detailMethods:GETTemplate:django_resume/pages/{resume.current_theme}/resume_detail.htmlBehavior:
Resolves the
Resumebyslug.If
?edit=trueis present in the query, shows “edit” modes if the user is authenticated and is the owner.Builds context for an explicit subset of section plugins (
["about", "identity", "cover", "theme"]). If these plugins are registered, their data is added to the template context.
Example:
from django.urls import reverse from django.test import Client client = Client() response = client.get("/django-resume/my-resume/") assert response.status_code == 200 # The response includes the "cover" plugin's data if installed.
CvPage (django_resume:cv)¶
- class django_resume.views.CvPage¶
A registered
ResumePageindjango_resume.pages. Renders a specialized “CV” (Curriculum Vitae) page of the resume (path = "cv/"), with plugin data injected for each registered plugin.URL name:
django_resume:cvMethods:GET(non-GETrequests return HTTP 405) Template:django_resume/pages/{resume.current_theme}/resume_cv.htmlBehavior:
Resolves the
Resumebyslug.If
?edit=trueis present, toggles an edit mode for owners.Builds context from all registered section plugins.
Each plugin’s context is merged into the template context under the plugin’s name key (
context[plugin.name]).check_accessenforces token permissions explicitly (viaTokenPlugin) before rendering. If the token plugin is registered and the check fails, it short-circuits with a 403 rendered fromcv_403.html.finalize_responsesetsReferrer-Policy: no-referrerwhen the resume requires a token, on both the rendered page and the 403 response.
Example:
client = Client() response = client.get("/django-resume/my-resume/cv/") assert response.status_code == 200 # Renders the user’s resume in CV format, including all plugin contexts.
PermissionDeniedPage (django_resume:403)¶
- class django_resume.views.PermissionDeniedPage¶
A registered
ResumePageindjango_resume.pages(path = "403/"). A special owner-only page for inline editing of the 403 (Forbidden) page, useful if a user wants to customize the message shown when someone lacks permission to view their CV.URL name:
django_resume:403Methods:GETRequires: Authentication (only resume owners can edit their 403 page) Template:django_resume/pages/{resume.current_theme}/cv_403.htmlBehavior:
Resolves the specified
Resume.check_accessredirects anonymous users to the login page, and returns a 403 response if the current user is not the owner.Otherwise renders
cv_403.htmlthrough the same shared renderer the CV denial path uses, building thepermission_deniedsection context, so the two paths cannot drift.
Example:
client = Client() client.login(username="alice", password="password") response = client.get("/django-resume/my-resume/403/") assert response.status_code == 200
CvRedirectView¶
- class django_resume.views.CvRedirectView¶
Inherits from
django.views.generic.RedirectView. Redirects from a URL structure of/cv/<slug:slug>/to/<slug:slug>/cv/.URL name:
django_resume:cv-redirectMethods:GETTemplate: None (redirect response)Behavior: - Resolves the final CV URL via
reverse(), pointing todjango_resume:cvwith the sameslug. - Returns a permanent redirect (HTTP 301) by default.Example:
client = Client() response = client.get("/django-resume/cv/my-resume/") assert response.status_code == 301 assert response["Location"] == "/django-resume/my-resume/cv/"