The Real-World Problem: You’ve Got a News Website Project but Don’t Know Where to Start
I once got a request from an acquaintance: build an internal news website for their company. The requirements sounded simple — a list of articles, detail pages, category filtering, and most importantly, an admin panel so that non-technical staff could publish posts.
But once I sat down to code, questions started piling up: where do I store the articles? How do I handle URL slugs? How do I upload thumbnail images? Should I build the admin panel myself or use a library? I spent several days going in circles with Flask before realizing I was using the wrong tool for the job.
Breaking It Down: Why Is a News Website More Complex Than It Looks?
It seems straightforward, but even a small internal news site needs a lot of moving parts working together:
- Database: storing articles, categories, and publication status
- Admin panel: so editors can manage content without knowing SQL
- Clean URL slugs: like
/articles/article-title/instead of?id=5 - Pagination: when you have hundreds of articles, you can’t dump them all on one screen
- Image upload and display: thumbnails for each article
Building all of this from scratch with Flask or raw PHP? Expect to spend at least 1–2 weeks just setting up the “infrastructure” before you even touch the actual business logic. That’s exactly where I got stuck — trying to do everything myself instead of using the right tool for the right problem.
Possible Approaches
Option 1: WordPress or an Existing CMS
Fast and feature-rich, but if the project needs complex custom logic — like integrating an internal API or a multi-step article approval workflow — WordPress quickly turns into a tangled mess of hooks and filters that’s hard to debug.
Option 2: Build It Yourself with Flask
Flask is flexible, but you have to wire up ORM, form validation, authentication, and an admin panel from scratch. For a news site that needs a full-featured admin, this is an unnecessarily long road.
Option 3: Django — Batteries Included
Django ships with a built-in admin panel, a powerful ORM, authentication, form handling, and URL routing. Most of what a news website needs is already there — you just need to configure and connect the pieces.
The Best Approach: Building a Django News Website Step by Step
Step 1: Set Up the Environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install django pillow python-slugify
pillow handles image uploads, and python-slugify generates proper slugs for non-ASCII characters.
Step 2: Create the Project and App
django-admin startproject newsite .
python manage.py startapp news
Add news to INSTALLED_APPS and configure media settings in settings.py:
INSTALLED_APPS = [
# ...
'news',
]
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
Step 3: Define the Models
File news/models.py:
from django.db import models
from slugify import slugify
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True, blank=True, max_length=250)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
content = models.TextField()
thumbnail = models.ImageField(upload_to='thumbnails/', blank=True)
published_at = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super().save(*args, **kwargs)
def __str__(self):
return self.title
class Meta:
ordering = ['-published_at']
python manage.py makemigrations
python manage.py migrate
A note on slug generation: Django’s built-in slugify() doesn’t handle non-ASCII characters well — for example, accented characters may get stripped or mangled. Use python-slugify instead for much cleaner results. I often quickly test regex patterns at toolcraft.app/en/tools/developer/regex-tester before putting them into code — it runs right in the browser, no installation needed, great for patterns like ^[a-z0-9-]+$ to validate slugs.
Step 4: Configure the Admin Panel
File news/admin.py:
from django.contrib import admin
from .models import Article, Category
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('name',)}
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'category', 'is_published', 'published_at']
list_filter = ['is_published', 'category']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
python manage.py createsuperuser
Step 5: Views and URLs
File news/views.py:
from django.shortcuts import render, get_object_or_404
from django.core.paginator import Paginator
from .models import Article, Category
def article_list(request):
articles = Article.objects.filter(is_published=True)
paginator = Paginator(articles, 10)
page_obj = paginator.get_page(request.GET.get('page'))
return render(request, 'news/list.html', {'page_obj': page_obj})
def article_detail(request, slug):
article = get_object_or_404(Article, slug=slug, is_published=True)
return render(request, 'news/detail.html', {'article': article})
def category_view(request, slug):
category = get_object_or_404(Category, slug=slug)
articles = Article.objects.filter(category=category, is_published=True)
return render(request, 'news/category.html', {
'category': category,
'articles': articles,
})
File news/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.article_list, name='article-list'),
path('articles/<slug:slug>/', views.article_detail, name='article-detail'),
path('categories/<slug:slug>/', views.category_view, name='category'),
]
Update newsite/urls.py:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('news.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Step 6: Basic Templates
Create the directory news/templates/news/. File list.html:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>News</title></head>
<body>
<h1>Latest News</h1>
{% for article in page_obj %}
<div>
{% if article.thumbnail %}
<img src="{{ article.thumbnail.url }}" alt="{{ article.title }}">
{% endif %}
<h2><a href="{% url 'article-detail' article.slug %}">{{ article.title }}</a></h2>
<p>{{ article.published_at|date:"m/d/Y" }} — {{ article.category }}</p>
</div>
{% endfor %}
<nav>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">← Previous</a>
{% endif %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next →</a>
{% endif %}
</nav>
</body>
</html>
Step 7: Run and Test
python manage.py runserver
Visit http://127.0.0.1:8000/admin/, log in with the superuser account you just created, and add a few categories and articles. Then go to http://127.0.0.1:8000/ to see your news listing.
What You End Up With
In just about 2–3 hours of setup, you have a fully functional news website with all the core features:
- A full-featured admin panel — editors with no coding knowledge can use it comfortably
- Clean, unique URL slugs per article with proper non-ASCII character support
- Automatic pagination as your article count grows
- Thumbnail image upload and display
- Filtering articles by category
Want to extend it further? For full-text search, use django-haystack; RSS feeds are built right in via django.contrib.syndication; for a headless CMS powering Next.js or Nuxt, just add Django REST Framework. The beauty is that no matter how large the project scope grows, you can bolt on each piece incrementally — no need to rewrite from scratch the way you would when outgrowing Flask.

