Open10

Django: ECサイト作成

R2I5wR2I5w

テンプレリポジトリをcloneして、リネームして自分のgithub管理配下に置く。

git remote set-url origin https://github.com/OWNER/REPOSITORY.git
  • cloneしてきたリポジトリのリモート設定確認
git remote -v
  • pushする。これでテンプレートのgit logを保持したままにできる。余談だが、リポジトリ管理はghq x pecoがとても楽なのでオススメ。
  • 適切なブランチを切って、開発開始。の前にまずは開発環境を少し整える。
R2I5wR2I5w

開発環境を整える。

  • Docker コンテナ内に 、nvimで使うLanguage Serverを入れる。以下はあくまでsample。
Dockerfile
FROM python:3.12.5-slim
ENV PYTHONUNBUFFERED=1

WORKDIR /sample_django/
COPY . /sample_django/

# python-lsp-serverをインストール
RUN pip install --upgrade pip && \
        pip install 'python-lsp-server[all]' && \
        pip install -r requirements.txt
init.lua
local lspconfig = require("lspconfig")

lspconfig.pylsp.setup {
        cmd = {
                'docker',
                'exec',
                '-i',
                project_name_to_container_name(),
                'pylsp'
        }
}
  • project_name_to_container_name()にはよしなにコンテナ名を取ってくるロジックを考える必要がある。コンテナのデフォルト命名は規則があるので、そこをフックにしよう。
  • 今回はDockerfileを弄る方法を取ってしまったが、本来チーム開発で自分の為の変更を加えることは御法度だと思うので別途方法は考える。
R2I5wR2I5w

ファビコンにこんな苦しめられることある?

  • ページのソース確認すると、画像自体は正しく配信されている。ということはstaticファイルの設定はちゃんとできているはず。
  • キャッシュ吹き飛ばしてみてもダメ。

解決

  • " assets/favicon.ico"のように、assetsの前に空白が入ってた。かなり初歩的。だがこれが発生した原因には一癖あって、formatterが「単語の前後に空白を挿入する」という悪さをしていた。
  • 合間にvscode-html-language-serverの設定を見直してみよう。
  • しかし、なぜ画像の配信(=URLが正しく変換されて解決されている)ができていたのだろうか。
R2I5wR2I5w

商品一覧/詳細画面の仮実装

R2I5wR2I5w

ER図

  • 課題の要件だけでいうと、ProductとProductImageは一対一の関係で良いが、詳細画面で画像を複数にしたい要件が後から出てきた時を想定して、一つのProductは複数のProductImageを持つことができる一対多(ProductImageは必ず1つのProductに属する)にした。
R2I5wR2I5w

ProductImage モデルの画像アップロード先のパスを動的に生成する

model.py
def product_image_path(instance, filename):
    return f'ecsite/{instance.product.slug}/{filename}'

このようにして使う。

model.py
image = models.ImageField(upload_to=product_image_path)
  • 引数instanceには「FileField(ImageFieldの継承元) が定義されているモデルのインスタンス」が入る。
  • 公式ドキュメントにサンプルがあるので、参照するといいかも
R2I5wR2I5w

slug(コードは主要な部分以外端折ってる)

model.py
from django.utils.text import slugify

class Product(models.Model):
    slug = models.SlugField(unique=True, null=True, blank=True)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('ecsite:product_detail', kwargs={'slug': self.slug})
  • save()をオーバーライドして、nameカラムからよしなにslug名を作成している。blank=Trueにしたのは、自動で生成する仕組みを確立したかったから(if not self.slugの条件を通したいから)。
  • get_absolute_url()は以下のようにtemplateでパスの記載を簡略化するために書いた。
list.html
<a href="{{ product.get_absolute_url }}">..
R2I5wR2I5w

select_related/prefetch_relatedについて書こうと思ったら、ガッツリN+1問題発生しとる・・予防できたと思ってたのに(この画像を載せたのはあくまで架空のサイトだから)