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:list Methods: GET, POST Requires: Authentication (via django.contrib.auth.decorators.login_required()) Templates:

  • On GET requests: django_resume/pages/plain/resume_list.html

  • On POST requests: django_resume/pages/plain/resume_list_main.html

Behavior:

  • When requesting via GET, displays a list of the user’s resumes and an empty ResumeForm for creating a new resume.

  • When requesting via POST, processes the submitted form data. If valid, a new Resume is 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:delete Methods: DELETE Requires: Authentication (via django.contrib.auth.decorators.login_required()) Template: None (returns an HTTP status code)

Behavior:

  • Fetches the Resume by slug.

  • 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 ResumePage in django_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:detail Methods: GET Template: django_resume/pages/{resume.current_theme}/resume_detail.html

Behavior:

  • Resolves the Resume by slug.

  • If ?edit=true is 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 ResumePage in django_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:cv Methods: GET (non-GET requests return HTTP 405) Template: django_resume/pages/{resume.current_theme}/resume_cv.html

Behavior:

  • Resolves the Resume by slug.

  • If ?edit=true is 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_access enforces token permissions explicitly (via TokenPlugin) before rendering. If the token plugin is registered and the check fails, it short-circuits with a 403 rendered from cv_403.html.

  • finalize_response sets Referrer-Policy: no-referrer when 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 ResumePage in django_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:403 Methods: GET Requires: Authentication (only resume owners can edit their 403 page) Template: django_resume/pages/{resume.current_theme}/cv_403.html

Behavior:

  • Resolves the specified Resume.

  • check_access redirects anonymous users to the login page, and returns a 403 response if the current user is not the owner.

  • Otherwise renders cv_403.html through the same shared renderer the CV denial path uses, building the permission_denied section 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-redirect Methods: GET Template: None (redirect response)

Behavior: - Resolves the final CV URL via reverse(), pointing to django_resume:cv with the same slug. - 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/"