DjangoでニュースWebサイトを構築する:初心者向けステップバイステップガイド

Python tutorial - IT technology blog
Python tutorial - IT technology blog

実際の問題:ニュースサイトの案件を受けたけど、どこから始めればいいかわからない

以前、知人から「社内向けのニュースサイトを作ってほしい」と依頼されたことがある。要件はシンプルに聞こえた——記事一覧、詳細ページ、カテゴリ分類、そしてコードを知らない人でも記事を投稿できる管理画面が必要、というものだった。

でも実際に実装に取り掛かると、次々と疑問が湧いてきた。記事のデータはどこに保存する?URLスラッグはどう処理する?サムネイル画像はどうアップロードする?管理パネルは自前で作る?それともライブラリを使う?Flaskで数日間うろうろした末に、ようやく自分がこの問題に対して間違ったツールを選んでいたことに気づいた。

分析:ニュースサイトはなぜそんなに複雑なのか

シンプルに見えるが、小さな社内ニュースサイトでさえ、多くの要素がかみ合って動く必要がある:

  • データベース:記事、カテゴリ、公開ステータスを保存する
  • 管理パネル:編集者がSQLを知らなくてもコンテンツを管理できる
  • きれいなURLスラッグ?id=5 ではなく /bai-viet/ten-bai/ 形式
  • ページネーション:記事が数百本になったとき、一画面に全部表示できない
  • 画像のアップロードと表示:各記事のサムネイル

FlaskやPHP単体でこれを全部自前実装する?「インフラ」部分だけで最低1〜2週間はかかる見込みで、本当のロジックにたどり着く前に時間を使い果たしてしまう。まさにそこで躓いた——適切なツールを使う代わりに、自分で全部やろうとしてしまったのだ。

解決策の選択肢

選択肢1:WordPressや既存CMS

素早く機能も十分だが、プロジェクトに複雑なカスタムロジックが必要な場合——たとえば社内APIとの連携や多段階の承認フローなど——WordPressはすぐにデバッグが難しいhookとfilterの迷宮になってしまう。

選択肢2:Flaskで自前構築

Flaskは柔軟だが、ORM・フォームバリデーション・認証・管理パネルをゼロから自前で統合しなければならない。フル機能の管理画面が必要なニュースサイトには、不必要に長い道のりだ。

選択肢3:Django——batteries included

Djangoには管理パネル、強力なORM、認証、フォーム処理、URLルーティングが最初から付いてくる。ニュースサイトに必要なものの大部分がすでに用意されていて、設定してつなぎ合わせるだけでいい。

ベストな方法:Djangoでニュースサイトをステップバイステップで構築する

ステップ1:環境のセットアップ

python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate
pip install django pillow python-slugify

pillow は画像アップロードを処理し、python-slugify はアクセント付き文字を含むテキストから正しいスラッグを生成する。

ステップ2:プロジェクトとアプリの作成

django-admin startproject newsite .
python manage.py startapp news

INSTALLED_APPSnews を追加し、settings.py でメディアの設定を行う:

INSTALLED_APPS = [
    # ...
    'news',
]

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

ステップ3:モデルの定義

ファイル 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

スラッグについての注意:Djangoデフォルトの slugify() はアクセント付き文字の処理が不完全で、たとえば “Tin tức hôm nay” が tin-tc-hm-nay になってしまう。代わりに python-slugify を使えば tin-tuc-hom-nay という正しい結果が得られる。コードに組み込む前に、toolcraft.app/ja/tools/developer/regex-testerregexパターンをすぐにテストできる——インストール不要でブラウザ上で動作するので、^[a-z0-9-]+$ のようなスラッグバリデーションパターンの確認に便利だ。

ステップ4:管理パネルの設定

ファイル 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

ステップ5:ビューとURL

ファイル 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,
    })

ファイル news/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('', views.article_list, name='article-list'),
    path('bai-viet/<slug:slug>/', views.article_detail, name='article-detail'),
    path('chuyen-muc/<slug:slug>/', views.category_view, name='category'),
]

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)

ステップ6:基本テンプレート

news/templates/news/ ディレクトリを作成する。ファイル list.html

<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"><title>ニュース</title></head>
<body>
  <h1>最新ニュース</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:"d/m/Y" }} — {{ article.category }}</p>
  </div>
  {% endfor %}

  <nav>
    {% if page_obj.has_previous %}
      <a href="?page={{ page_obj.previous_page_number }}">← 前へ</a>
    {% endif %}
    {% if page_obj.has_next %}
      <a href="?page={{ page_obj.next_page_number }}">次へ →</a>
    {% endif %}
  </nav>
</body>
</html>

ステップ7:起動と確認

python manage.py runserver

http://127.0.0.1:8000/admin/ にアクセスして、作成したスーパーユーザーでログインし、いくつかカテゴリと記事を追加してみよう。その後 http://127.0.0.1:8000/ でニュース一覧を確認できる。

得られる成果

セットアップに2〜3時間ほどで、基本機能を備えたニュースサイトが動く状態になる:

  • フル機能の管理パネル——コードを知らない編集者でも使える
  • 各記事のクリーンなURLスラッグ
  • 記事数が増えても自動でページネーション
  • サムネイル画像のアップロードと表示
  • カテゴリによる記事フィルタリング

さらに拡張したい場合は?全文検索なら django-haystack、RSSフィードはDjangoが django.contrib.syndication で標準サポート済み、Next.jsやNuxt向けのヘッドレスCMSはDjango REST Frameworkを追加するだけで対応できる。プロジェクトの規模がどれだけ大きくなっても、各機能をボルトオンで追加していけるのがDjangoの強みだ——Flaskで限界が来たときのようにゼロから書き直す必要がない。

Share: