{"id":1735,"date":"2026-09-24T07:41:00","date_gmt":"2026-09-24T07:41:00","guid":{"rendered":"https:\/\/nayananimsara.online\/?p=1735"},"modified":"2026-09-24T07:41:04","modified_gmt":"2026-09-24T07:41:04","slug":"django-dynamic-url-single-post-page","status":"publish","type":"post","link":"https:\/\/nayananimsara.online\/django-dynamic-url-single-post-page\/","title":{"rendered":"Create a Dynamic Single Page View in Django (blog\/post1, blog\/post2)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Django dynamic URL single post page So far, every page in this course has needed its own view and its own line in <code>urls.py<\/code> \u2014 homepage gets one, About gets one. That works fine for a handful of fixed pages, but it falls apart the moment you&#8217;re building something like a blog. You&#8217;re not going to write a separate view for every single post. What you actually want is one URL pattern that changes based on what&#8217;s requested \u2014 <code>\/blog\/post1\/<\/code>, <code>\/blog\/post2\/<\/code>, <code>\/blog\/post3\/<\/code> \u2014 all handled by a single view.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s what this lesson covers. Still no templates, still no styling, and still no database \u2014 we haven&#8217;t gotten there yet in this course, so we&#8217;ll fake the data with a plain Python dictionary for now and swap it for something real later.<\/p>\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#step-1-set-up-a-url-that-captures-a-value\">Step 1: Set Up a URL That Captures a Value<\/a><\/li><li><a href=\"#step-2-fake-some-data-with-a-dictionary\">Step 2: Fake Some Data With a Dictionary<\/a><\/li><li><a href=\"#step-3-write-the-view\">Step 3: Write the View<\/a><\/li><li><a href=\"#step-4-handling-a-post-that-doesnt-exist\">Step 4: Handling a Post That Doesn&#8217;t Exist<\/a><\/li><li><a href=\"#try-it-with-real-ur-ls\">Try It With Real URLs<\/a><\/li><li><a href=\"#where-this-is-genuinely-heading\">Where This Is Genuinely Heading<\/a><\/li><li><a href=\"#where-this-connects-in-the-series\">Where This Connects in the Series<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<h2 id=\"step-1-set-up-a-url-that-captures-a-value\" class=\"wp-block-heading\">Step 1: Set Up a URL That Captures a Value<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open your app&#8217;s <code>urls.py<\/code> and add a new path that captures part of the URL as a variable:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">python<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from django.urls import path\nfrom . import views\n\nurlpatterns = &#91;\n    path('blog\/&lt;str:post_slug>\/', views.blog_post, name='blog_post'),\n]<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1024\" height=\"300\" src=\"https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-19-1024x300.png\" alt=\"\" class=\"wp-image-1739\" srcset=\"https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-19-1024x300.png 1024w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-19-768x225.png 768w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-19-300x88.png 300w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-19.png 1266w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">That <code>&lt;str:post_slug&gt;<\/code> part tells Django: whatever text appears in that position of the URL, capture it as a string and hand it to the view as an argument called <code>post_slug<\/code>. Visit <code>\/blog\/post1\/<\/code>, and <code>post_slug<\/code> becomes <code>\"post1\"<\/code>. Visit <code>\/blog\/post2\/<\/code>, and it becomes <code>\"post2\"<\/code> \u2014 same view, different input.<\/p>\n\n\n\n<h2 id=\"step-2-fake-some-data-with-a-dictionary\" class=\"wp-block-heading\">Step 2: Fake Some Data With a Dictionary<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Since we don&#8217;t have models yet, a plain Python dictionary is enough to demonstrate the pattern. At the top of <code>views.py<\/code>, above your view functions, add something like this:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">python<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>posts = {\n    \"post1\": {\n        \"title\": \"My First Blog Post\",\n        \"content\": \"This is the content of my very first post.\",\n    },\n    \"post2\": {\n        \"title\": \"Learning Django Step by Step\",\n        \"content\": \"This post is about how I'm learning Django one lesson at a time.\",\n    },\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"291\" src=\"https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-18-1024x291.png\" alt=\"\" class=\"wp-image-1738\" srcset=\"https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-18-1024x291.png 1024w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-18-300x85.png 300w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-18-765x217.png 765w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-18.png 1156w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Treat this as a stand-in for a real database. Later in the course, once models are introduced, this same view will fetch data from an actual database instead \u2014 but the URL pattern and the logic around it stay almost identical.<\/p>\n\n\n\n<h2 id=\"step-3-write-the-view\" class=\"wp-block-heading\">Step 3: Write the View<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now write a view that accepts <code>post_slug<\/code>, looks it up in the dictionary, and builds a plain HTML page around whatever it finds \u2014 no CSS, no styling, just the bare tags:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">python<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from django.http import HttpResponse, Http404\n\ndef blog_post(request, post_slug):\n    post = posts.get(post_slug)\n\n    if post is None:\n        raise Http404(\"Post not found\")\n\n    html = f\"\"\"\n    &lt;!DOCTYPE html&gt;\n    &lt;html lang=\"en\"&gt;\n    &lt;head&gt;\n        &lt;meta charset=\"UTF-8\"&gt;\n        &lt;title&gt;{post&#91;'title']}&lt;\/title&gt;\n    &lt;\/head&gt;\n    &lt;body&gt;\n        &lt;nav&gt;\n            &lt;a href=\"\/\"&gt;Home&lt;\/a&gt;\n            &lt;a href=\"\/about\/\"&gt;About&lt;\/a&gt;\n        &lt;\/nav&gt;\n\n        &lt;main&gt;\n            &lt;h1&gt;{post&#91;'title']}&lt;\/h1&gt;\n            &lt;p&gt;{post&#91;'content']}&lt;\/p&gt;\n        &lt;\/main&gt;\n\n        &lt;footer&gt;\n            &lt;p&gt;&amp;copy; 2026 My Django Site&lt;\/p&gt;\n        &lt;\/footer&gt;\n    &lt;\/body&gt;\n    &lt;\/html&gt;\n    \"\"\"\n    return HttpResponse(html)<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"950\" height=\"907\" src=\"https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-17.png\" alt=\"\" class=\"wp-image-1737\" srcset=\"https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-17.png 950w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-17-300x286.png 300w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-17-768x733.png 768w\" sizes=\"(max-width: 950px) 100vw, 950px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Notice this uses an f-string (<code>f\"\"\"<\/code>) instead of a plain triple-quoted string, because we need to insert <code>post['title']<\/code> and <code>post['content']<\/code> directly into the HTML. Without any <code>&lt;style&gt;<\/code> block or CSS to worry about, there&#8217;s no need to escape curly braces either \u2014 the f-string just handles the two variables and nothing else.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"544\" src=\"https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-16-1024x544.png\" alt=\"Django dynamic URL single post page\" class=\"wp-image-1736\" srcset=\"https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-16-1024x544.png 1024w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-16-766x407.png 766w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-16-300x159.png 300w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-16-1536x816.png 1536w, https:\/\/nayananimsara.online\/wp-content\/uploads\/2026\/09\/image-16.png 1691w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 id=\"step-4-handling-a-post-that-doesnt-exist\" class=\"wp-block-heading\">Step 4: Handling a Post That Doesn&#8217;t Exist<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Try visiting <code>\/blog\/post5\/<\/code> \u2014 a slug that isn&#8217;t in the dictionary. Instead of crashing or showing a blank page, Django shows its standard &#8220;Page not found&#8221; error, because of this check:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">python<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if post is None:\n    raise Http404(\"Post not found\")<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Without that check, <code>post['title']<\/code> would throw a confusing error instead of a clean 404. This is a small habit worth keeping from here on \u2014 anytime a view depends on something that might not exist, check for it before trying to use it.<\/p>\n\n\n\n<h2 id=\"try-it-with-real-ur-ls\" class=\"wp-block-heading\">Try It With Real URLs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run your server and visit:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>\/blog\/post1\/<\/code> \u2014 shows the first post&#8217;s title and content<\/li>\n\n\n\n<li><code>\/blog\/post2\/<\/code> \u2014 shows the second post&#8217;s, using the same view<\/li>\n\n\n\n<li><code>\/blog\/post5\/<\/code> \u2014 shows a proper 404, since it isn&#8217;t in the dictionary<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">One view, unlimited pages, as long as the data exists to back them. That&#8217;s the actual difference between this and everything built earlier in the course \u2014 up to now, every new page meant a new view. From here on, it doesn&#8217;t.<\/p>\n\n\n\n<h2 id=\"where-this-is-genuinely-heading\" class=\"wp-block-heading\">Where This Is Genuinely Heading<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I want to be honest about the limits of this approach too. A Python dictionary sitting at the top of <code>views.py<\/code> isn&#8217;t how real blogs store content \u2014 it resets every time the server restarts, and there&#8217;s no way to add a new post without editing code directly. That&#8217;s exactly the gap that models and a real database fill, once this course gets there. Styling is missing here too, on purpose \u2014 plain HTML tags are enough to prove the routing pattern works, and CSS can come back in later once it&#8217;s actually covered.<\/p>\n\n\n\n<h2 id=\"where-this-connects-in-the-series\" class=\"wp-block-heading\">Where This Connects in the Series<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This builds on the URL and view patterns from the earlier lessons \u2014 if <code>path()<\/code> or view functions still feel unfamiliar, it&#8217;s worth going back to the tutorial on views and URLs first. For more on how Django captures values from a URL, the <a href=\"https:\/\/docs.djangoproject.com\/en\/stable\/topics\/http\/urls\/#path-converters\" target=\"_blank\" rel=\"noopener\">official Django documentation on path converters<\/a> covers the other converter types beyond <code>&lt;str:&gt;<\/code>, like <code>&lt;int:&gt;<\/code> and <code>&lt;slug:&gt;<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Try adding a <code>post3<\/code> to the dictionary yourself and visiting it without touching <code>views.py<\/code> again beyond that one addition. Seeing the same view handle it automatically is the part that actually makes this pattern click. <a href=\"https:\/\/nayananimsara.online\/course\/django-course-sinhala\/\" target=\"_blank\" data-type=\"link\" data-id=\"https:\/\/nayananimsara.online\/course\/django-course-sinhala\/\" rel=\"noreferrer noopener\">Full Course<\/a><\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<div class=\"tg-oembed-container\"><iframe title=\"WebHD 720p 13\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/o3a2IDoZ5Cw?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/div>\n<\/div><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Django dynamic URL single post page So far, every page in this course has needed its own view and its own line in urls.py \u2014 homepage gets one, About gets one. That works fine for a handful of fixed pages, but it falls apart the moment you&#8217;re building something like a blog. You&#8217;re not going [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"elearning_container_layout":"tg-site-layout--default","elearning_sidebar_layout":"tg-site-layout--default","elearning_remove_content_margin":false,"elearning_sidebar":"default","elearning_transparent_header":"customizer","elearning_logo":0,"elearning_header_style":"default","elearning_menu_item_color":"","elearning_menu_item_hover_color":"","elearning_menu_item_active_color":"","elearning_menu_item_active_style":"","elearning_page_header":true,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1735","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/posts\/1735","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/comments?post=1735"}],"version-history":[{"count":2,"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/posts\/1735\/revisions"}],"predecessor-version":[{"id":1741,"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/posts\/1735\/revisions\/1741"}],"wp:attachment":[{"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/media?parent=1735"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/categories?post=1735"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nayananimsara.online\/wp-json\/wp\/v2\/tags?post=1735"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}