pythonで作るネットショップ　django-oscarを始めよう　③商品追加～カスタマイズ発展編

#はじめに

django-oscar入門の第三回目です。

https://qiita.com/idavash/items/694a04fef6c22b9a0e26

https://qiita.com/idavash/items/10bbf98872a1c7161f1e

oscarでカスタマイズを繰り返せば、私が開発したOriginallyTeeのような、C2CのECサイトを作ることまでできます。

https://originally-tee.com/top/

### 例　カタログのカスタマイズ

前回はシンプルなカスタマイズを行いました。もう少し複雑な例も説明します。

モデル、テンプレート、ビューに横断するカスタマイズを行います。

以下のドキュメント+αで説明します。

https://django-oscar.readthedocs.io/en/latest/howto/index.html#customisation

### catalogueアプリのフォーク

フォークの方法は前回と同じです。
```
$ python manage.py oscar_fork_app catalogue yourappsfolder
Creating package yourappsfolder\catalogue
Creating admin.py
Creating app config
Creating models.py
Creating migrations folder
Replace the entry 'oscar.apps.catalogue.apps.CatalogueConfig' with 'yourappsfolder.catalogue.apps.CatalogueConfig' in INSTALLED_APPS
```

settings.pyのINSTALLED_APPSを修正します。

```settings.py

INSTALLED_APPS = [

#...

# 'oscar.apps.catalogue.apps.CatalogueConfig',　
'yourappsfolder.catalogue.apps.CatalogueConfig', #追加
```

### モデル(models.py)のカスタマイズ

商品ページに動画リンクを追加したいケースとします。商品モデルへvideo_urlフィールドを追加します。

PracticeOscar\Lib\site-packages\oscar\apps\catalogue\abstract_models.py

のAbstractProductに、商品のモデル発見しました。

AbstractProductにフィールド追加するため、models.pyを書き換えます。



```yourproject/catalogue/models.py

from django.db import models
from oscar.apps.catalogue.abstract_models import AbstractProduct

class Product(AbstractProduct):
    video_url = models.CharField(max_length=128, blank=True)
    
from oscar.apps.catalogue.models import *
```

Djangoのモデルのコードですが

**以下の２点に注意して下さい。** 

**1. oscar.apps.catalogue.abstract_models.pyからインポートする**
**2. oscar.apps.catalogue.models.pyのインポートを「最後」にする**

1. oscarはabstract_models.pyでモデルを定義して、models.pyでそのモデルを読み込みます。よってabstract_models.pyをインポートして上書きします

2. 同名のモデルが存在する場合は、先に存在するモデルのみ読み込みます。よってこちらで上書きしたモデルを先に読み込むため、models.pyのインポートを最後にしています

モデル更新をデータベースに反映させます。
```
$ python manage.py makemigrations catalogue
$ python manage.py migrate
```

`python manage.py runserver` でサーバーにアクセスして、admin/catalogue/product/を見ると、商品モデルに新しいフィールドが追加されていますね。

※こちらはdjangoのデフォルトの管理画面で、oscarの管理画面のダッシュボードとは違うページなことに注意してください。両方使います。

せっかくなので 適当なリンクを記入して保存しておきましょう。

![FireShot Capture 015 - テスト商品 - 商品 を変更 - Django サイト管理 - 127.0.0.1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672580/05b659e9-231a-2b53-d551-20cb472da9ef.png)




### テンプレート(.html)のカスタマイズ

先のリンクを商品名の下に表示したいケース考えます。

テンプレートファイルの上書きは

1. 同名のテンプレートファイルを作成してから
2. djangoのテンプレート継承を行います



![FireShot Capture 012 - テスト商品 - Oscar - - 127.0.0.1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672580/a49ae785-88fd-0644-0d4e-b986c1188ca9.png)

ここもフレームワーク側での該当するコード探しからスタートです。

テンプレートファイルは、仮想環境名PracticeOscarのとき

PracticeOscar\Lib\site-packages\oscar\templates\oscar

以下に存在します。これ以下のディレクトリはアプリによって分割されていますので、商品のテンプレートはcatalogue以下を探します。

PracticeOscar\Lib\site-packages\oscar\templates\oscar\catalogue\

https://qiita.com/idavash/items/694a04fef6c22b9a0e26#oscar%E3%81%AE%E6%A9%9F%E8%83%BD%E6%A6%82%E8%A6%B3

さらに今回の修正は、商品の詳細ページの修正なので

PracticeOscar\Lib\site-packages\oscar\templates\oscar\catalogue\detail.html

の修正であることが分かります。

テンプレートの配置が分かったので、このディレクトリ構成を開発環境でも再現します。

ルート直下にtemplatesディレクトリを作成します。templates以下に修正したい全てのテンプレートを配置することになります。

そしてtemplates以降がフレームワーク側と同じになるように作成してください。

```
$ mkdir -p templates/oscar/catalogue
$ touch templates/oscar/catalogue/detail.html
```

以下はdjangoのテンプレート継承のコードとなります。

今回は{% block product_main %}{% endblock %}内の元のコードにaタグを追加することで、コードが変更されています。

この仕組みにより、少ないコード量で上書きできます。


```django:templates/oscar/catalogue/detail.html

{% extends "oscar/catalogue/detail.html" %}

{% load history_tags %}
{% load currency_filters %}
{% load reviews_tags %}
{% load product_tags %}
{% load display_tags %}
{% load i18n %}
{% load purchase_info_tags %}

{% block product_main %}
<div class="col-sm-6 product_main">
    {% comment %}
        This is a bit clunky here.  Better to have some kind of JS-driven dashboard menu that
        pops out when clicked.  A bit like the Django-Debug-Toolbar button
    {% endcomment %}
    {% if user.is_staff %}
        <a class="float-right d-none d-md-block" href="{% url 'dashboard:catalogue-product' pk=product.id %}">
            <small><i class="fas fa-pencil-alt"></i> {% trans "Edit this product" %}</small>
        </a>
    {% endif %}
    <h1>{{ product.get_title }}</h1>
    <a href="{{ product.video_url }}" alt="youtube" target="_blank" rel="noopener noreferrer">動画リンク</a>
    {% block product_stock_record %}
        {% include "oscar/catalogue/partials/stock_record.html" with verbose=1 %}
    {% endblock %}

    {% iffeature "reviews" %}
        {% include "oscar/catalogue/reviews/partials/review_stars.html" %}
    {% endiffeature %}

    <hr/>

    {% if not product.is_parent %}
        {% block product_basket_form %}
            {% include "oscar/catalogue/partials/add_to_basket_form.html" %}
        {% endblock %}
    {% else %}
        {% block variants %}
            <h2>{% trans 'Variants:' %}</h2>
            {% for child in product.children.public %}
                {% purchase_info_for_product request child as child_session %}
                {% if child_session.availability.is_available_to_buy %}
                    <a href="{{ child.get_absolute_url }}">{{ child.get_title }}</a><br>
                {% endif %}
            {% endfor %}
        {% endblock %}
    {% endif %}
</div><!-- /col-sm-6 -->
{% endblock %}

```

![FireShot Capture 016 - テスト商品 - Oscar - - 127.0.0.1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672580/5999f8fa-317c-a4bf-c8d4-abab671d4111.png)


以上がテンプレートの継承になります。

**まとめると**

**1. フレームワーク側でテンプレートファイルを見つける**

**2. 開発環境側のtemplatesディレクトリ以下に、ディレクトリ構成が同じになるようにテンプレートファイル設置する**

**3. djangoのテンプレート継承で、上書きしたい部分のみコードを修正する**

3.はdjangoの機能ですので、今回もカスタマイズしたい部分のコードを素早く見つけられるかが重要となります。

### ビュー(views.py)のカスタマイズ

リンクが存在するときのみ、リンクテキストを表示することを考えます。

商品詳細のビューの上書きを考えます。今回も該当のコード探しからスタートです。

PracticeOscar\Lib\site-packages\oscar\apps\catalogue\views.py

のProductDetailViewに、ビューがあることを見つけました。

views.pyを作成します。

```
$ touch yourappsfolder\catalogue\views.py 
```
ProductDetailViewを修正するため、views.pyのget_context_dataを以下のように書き換えます。

djangoのクラスビューのメソッド、get_context_dataを上書きする典型処理です。

```python:yourappsfolder\catalogue\views.py 

from oscar.core.loading import get_class

CoreProductDetailView = get_class("catalogue.views", "ProductDetailView")


class ProductDetailView(CoreProductDetailView):
    
    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        if ctx["product"].video_url:
            ctx["video_name"] = "動画リンクはこちら"
        else:
            ctx["video_name"] =""
        return ctx
```

先のテンプレートのaタグも修正します。

```django:templates/oscar/catalogue/detail.html
{% comment %}
<a href="{{ product.video_url }}" alt="youtube" target="_blank" rel="noopener noreferrer">動画リンク</a>
を以下のように修正
{% endcomment %}

<a href="{{ product.video_url }}" alt="youtube" target="_blank" rel="noopener noreferrer">{{ video_name }}</a>
```

これでリンクの表示が動的に変更されるようになりました。

# 次回予定

今回はモデル(models.py)、テンプレート(.html)、ビュー(views.py)のカスタマイズ方法を説明しました。

同様の方法でフォーム(forms.py)等の他のファイルのカスタマイズもできますので、大半のカスタマイズはできるようになりました。

次はurlディスパッチャのカスタマイズとなります。

# 便利なWebサイト
github
https://github.com/django-oscar/django-oscar

ドキュメント　最初のうちはここを見ることが一番多いと思います
https://django-oscar.readthedocs.io/en/latest/

フォーラム　過去の質問を見ることもできます
https://groups.google.com/g/django-oscar
https://app.slack.com/client/T1QSP0999/C1QUF61T3

oscarはdjangoで作られているので、開発時はこちらも参照します
https://github.com/django/django
https://docs.djangoproject.com/en/5.0/