SEIYU風のECサイトを作りましょう（1）要求分析とプロジェクト初期化

## 前回の記事
[Django REST framework + Vue.js「SEIYU風のECサイトを作りましょう」全6回（予定）](https://qiita.com/Syoitu/items/62e6ec1437d98d6b9d28)

# 前書き
何かを開発する場合、依頼者からの**要求**を満足させる必要があります。
その依頼者とは、お客様や上司、マネージャーなどが該当します。
では、今回のECサイトにはどのような要求があるのでしょうか。

# 機能要求分析
SEIYUのECサイトを分析して機能を割り出すと、大体以下のものになります。
- 商品展示
- 商品画像
- 特定の商品展示
- 商品検索
- 商品詳細
- ログイン
- 一般ユーザー関連
- 商品レビュー
- 商品をかごに追加できる
- 商品をお気に入りにする
- 出品者と連絡
- 支払い

まだまだあるかもしれませんが、以上の機能さえ満たせば、ECサイトとして機能します。
機能に基づいて、モジュールの構成を図にしてみましょう。

<img width="791" alt="キャプチャ1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/8246127f-b81b-255f-3974-98d1ad59dd25.png">


ここまで来ればどんなモジュールを開発する必要があるのかは、明確になりました。

# プロジェクト初期化
### 仮想環境のセットアップ
**参考資料**
[Pythonの仮想環境の作り方](https://qiita.com/Syoitu/items/54d6d3e57482c1e8ab17)

ここで私はvirtualenvwrapperで仮想環境作りますが、virtualenvなどのライブラリ使用でもオーケーです。

```shell
mkvirtualenv qiita-Django-supermarket
```
以下の表示が確認できたら完成です。

```shell
(qiita-Django-supermarket) ディレクトリ>
```
### プロジェクト管理
今回のプロジェクトを配置したいディレクトリの配下で、以下のコマンドを実行してください。

```shell
 mkdir qiita-Django-supermarket
 cd qiita-Django-supermarket
 touch CHANGELOG.md && touch LICENSE && touch README.md && touch requirements.txt

```
実行後、ディレクトリ配下は以下のような構成になります。

```
qiita-Django-supermarket
|-- CHANGELOG.md
|-- LICENSE
|-- README.md
|-- requirements.txt
```
先ず、この四つのファイル何に使うか説明します。

> CHANGELOG.md
>> バージョンごとにどのような新機能が追加されたか、どのような機能が変更されたのか等を管理し説明するテキストファイルのこと。
Gitで自動生成もできるが基本は手書きを行う。
どんな内容記入すれば良いのか、Djangoのリポジトリ参考すればいいと思います。
`https://github.com/django/django/tree/master/docs/releases`


>LICENSE
>>もしオープンソースのプロジェクトなら、著作権などの声明を記入することができる。
詳細はwikiを参考してください。
`https://ja.wikipedia.org/wiki/オープンソースライセンス`
内部のプロジェクトなら、このファイルを作る必要はありません。

>README.md
>>何を記入するかは自由です。
どういった経緯で開発したのか、参加者など、プロジェクトの理解を深めるために存在します。

>requirements.txt
>> 使用してるPythonパッケージを記録する為のファイル。
`pip freeze >requirements.txt`で自動生成もできます。
複数プロジェクト作成し、環境に汚染の可能性がもしあれば、手書きでも良いでしょう。
` pip install -r requirements.txt`で記録したPythonパッケージでのインストールができます。

### ライブラリインストール

今回のアプリに必要なライブラリをインストールしましょう。
[公式ドキュメント](https://www.django-rest-framework.org/)

```shell
pip install django
pip install djangorestframework
pip install coreapi
pip install Markdown
pip install Pygments
pip install django-filter
pip install django-guardian
pip install Pillow
```
2019年08月17日では、以下のバージョンのライブラリがインストールされました。 :point_up:

```shell
certifi             2019.6.16
chardet             3.0.4
coreapi             2.3.3
coreschema          0.0.4
Django              2.2.4
django-filter       2.2.0
django-guardian     2.0.0
djangorestframework 3.10.2
idna                2.8
itypes              1.1.0
Jinja2              2.10.1
Markdown            3.1.1
MarkupSafe          1.1.1
Pillow              6.1.0
pip                 19.2.2
Pygments            2.4.2
pytz                2019.2
requests            2.22.0
setuptools          41.1.0
sqlparse            0.3.0
uritemplate         3.0.0
urllib3             1.25.3
wheel               0.33.4

```
### スタートプロジェクト

qiita-Django-supermarketディレクトリの配下で以下コマンドを実行します。
今回のDRFはAPIを提供するのがメインの役割なので、**api**と命名します。
PyCharmで作ってもいいです。

```shell:qiita-Django-supermarket/
django-admin startproject api
```
実行後、ディレクトリ構成は以下になります。

```shell
qiita-Django-supermarket
|-- requirements.txt
|-- README.md
|-- LICENSE
|-- CHANGELOG.md
|-- api
|-- |--api
|-- |-- |-- __init__.py
|-- |-- |-- settings.py
|-- |-- |-- urls.py
|-- |-- |-- wsgi.py
|-- |--manage.py
```

settings.pyファイルをエディタで開き、以下の通り内容を変更します。
ここでsetting.pyを環境分けしてもいいですが、今回は省きます。
興味のある方は下記の記事を参考にしてください。
[Django実践開発入門](https://qiita.com/Syoitu/items/d3a28b2a7acdf90fbccd)


```shell:api/settings
INSTALLED_APPS = [
    ...
    'rest_framework',
]

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True
```
サーバー立ち上げて、現時点問題ないか確認します。

```python
python manage.py runserver
```
下記の画面確認できたらオーケーです:relaxed:
<img width="662" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/e497df3b-acf2-8fb7-21b4-be72daa48232.png">

#  新規 App
モジュールの構成図に従って、Appを作ります。

```shell:qiita-Django-supermarket/api
python manage.py startapp users
python manage.py startapp goods
python manage.py startapp trade
python manage.py startapp user_operation
mkdir apps
```
その後、新規作った四つのファイルをappsに入れます。
完了後、ディレクトリは下記のようになります。

```
```shell
qiita-Django-supermarket
|-- requirements.txt
|-- README.md
|-- LICENSE
|-- CHANGELOG.md
|-- api
|-- |--apps
|-- |-- |--goods
|-- |-- |--trade
|-- |-- |--user_operation
|-- |-- |--users 
|-- |--api
|-- |-- |-- __init__.py
|-- |-- |-- settings.py
|-- |-- |-- urls.py
|-- |-- |-- wsgi.py
|-- |--manage.py
```

そしてappsを `souruces Root`に設定します。
PyCharmの操作方法は
1.appsを右クリック
2. Mark Directory as 
3. souruces Root
完了後　ファイルが青くなってることが確認できます<img width="108" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/2f8673cb-ae56-3323-1f21-61d917a0ecab.png">

その後、settings.pyファイルをエディタで開き、以下の通り内容を変更します。

```python:settings.py
...
import sys
sys.path.insert(0, BASE_DIR)
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
...
INSTALLED_APPS = [
     ...
    'users',
    'goods',
    'trade',
    'user_operation',
     ...
]
```
### model
モデルの作り方もモジュール構成図に従い作ります。
#### users
先ずは**users**のモデルから変更します。
ここで**UserProfile** クラスに**AbstractUser**を継承させて、Django本来のuserモデルを上書きします。

```python:users/models.py

from datetime import datetime

from django.db import models
from django.contrib.auth.models import AbstractUser


class UserProfile(AbstractUser):
    """
    ユーザー
    """
    name = models.CharField(max_length=30, null=True, blank=True, verbose_name="名前")
    birthday = models.DateField(null=True, blank=True, verbose_name="生年月日")
    mobile = models.CharField(max_length=20, verbose_name="携帯番号")
    gender = models.CharField(max_length=6, choices=(("male", "男性"), ("female", "女性"), ("secret", "非公開")),
                              default="male", verbose_name="性別")
    email = models.CharField(max_length=100, null=True, blank=True, verbose_name="アドレス")

    class Meta:
        verbose_name = "ユーザー"
        verbose_name_plural = verbose_name

    def __str__(self):
       return self.username


class VerifyCode(models.Model):
    """
    メール認証コード
    """
    code = models.CharField(max_length=10, verbose_name="メール認証コード")
    mobile = models.CharField(max_length=11, verbose_name="携帯番号")
    add_time = models.DateTimeField(default=datetime.now, verbose_name="作成時間")

    class Meta:
        verbose_name = "メール認証コード"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.code

```

上書きのためにsettings.pyに一個変更を追加。
VerifyCodeはユーザー登録時に使用する認証codeの検証のために作ってますが、使わない可能性もあります。

```python:settings.py
...
AUTH_USER_MODEL = "users.UserProfile"
...
```


管理画面を作成するため、admin.pyも変更します。

```python:users/admin.py
from django.contrib import admin

from .models import VerifyCode, UserProfile


class UserProfileAdmin(admin.ModelAdmin):
    list_display = ['name', 'birthday', 'mobile', 'gender', 'email']


class VerifyCodeAdmin(admin.ModelAdmin):
    list_display = ['code', 'mobile', "add_time"]


admin.site.register(VerifyCode, VerifyCodeAdmin)
admin.site.register(UserProfile, UserProfileAdmin)
```

#### goods

次は**goods App**の**models.py**を書きます

```python:goods/models.py
from datetime import datetime

from django.db import models


class GoodsCategory(models.Model):
    """
    商品カテゴリー
    """
    CATEGORY_TYPE = (
        (1, "一級カテゴリー"),
        (2, "二級カテゴリー"),
        (3, "三級カテゴリー")
    )

    name = models.CharField(default="", max_length=50, verbose_name="カテゴリー名", help_text="カテゴリー名")
    code = models.CharField(default="", max_length=30, verbose_name="カテゴリーコード", help_text="カテゴリーコード")
    desc = models.TextField(default="", verbose_name="カテゴリー説明", help_text="カテゴリー説明")
    category_type = models.IntegerField(choices=CATEGORY_TYPE, verbose_name="カテゴリーレベル", help_text="カテゴリーレベル")
    parent_category = models.ForeignKey("self", null=True, blank=True, verbose_name="親カテゴリー", help_text="親カテゴリー",
                                        on_delete=models.CASCADE, related_name="sub_cat")
    is_tab = models.BooleanField(default=False, verbose_name="ナビなのか", help_text="ナビなのか")
    add_time = models.DateTimeField(default=datetime.now, verbose_name="挿入時間")

    class Meta:
        verbose_name = "商品カテゴリー"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class GoodsCategoryBrand(models.Model):
    """
    ブランド名
    """
    category = models.ForeignKey(GoodsCategory, related_name="brands", null=True, blank=True,
                                 verbose_name="商品カテゴリー名", on_delete=models.CASCADE)
    name = models.CharField(default="", max_length=30, verbose_name="ブランド名", help_text="ブランド名")
    desc = models.CharField(default="", max_length=200, verbose_name="ブランド説明", help_text="ブランド説明")
    image = models.ImageField(max_length=200, upload_to="brands/")
    add_time = models.DateTimeField(default=datetime.now, verbose_name="挿入時間")

    class Meta:
        verbose_name = "ブランド"
        verbose_name_plural = verbose_name
        db_table = "goods_goodsbrand"

    def __str__(self):
        return self.name


class Goods(models.Model):
    """
    商品
    """
    category = models.ForeignKey(GoodsCategory, null=True, blank=True,
                                 verbose_name="商品カテゴリー", on_delete=models.CASCADE)
    goods_sn = models.CharField(max_length=50, default="", verbose_name="商品識別番号")
    name = models.CharField(max_length=100, verbose_name="商品名")
    click_num = models.IntegerField(default=0, verbose_name="クリック数")
    sold_num = models.IntegerField(default=0, verbose_name="販売数")
    fav_num = models.IntegerField(default=0, verbose_name="お気に入り登録数")
    goods_num = models.IntegerField(default=0, verbose_name="在庫数")
    market_price = models.FloatField(default=0, verbose_name="原価")
    shop_price = models.FloatField(default=0, verbose_name="販売値段")
    goods_brief = models.TextField(max_length=500, verbose_name="商品説明")
    ship_free = models.BooleanField(default=True, verbose_name="送料負担")
    goods_front_image = models.ImageField(max_length=200, upload_to="goods/images/",
                                          null=True, blank=True, verbose_name="表紙")
    is_new = models.BooleanField(default=False, verbose_name="新品なのか")
    is_hot = models.BooleanField(default=False, verbose_name="売れているのか")
    add_time = models.DateTimeField(default=datetime.now, verbose_name="挿入時間")

    class Meta:
        verbose_name = "商品"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class GoodsImage(models.Model):
    """
    商品swiperImages
    """
    goods = models.ForeignKey(Goods, verbose_name="商品", related_name="images", on_delete=models.CASCADE)
    image = models.ImageField(upload_to="", verbose_name="画像", null=True, blank=True)
    add_time = models.DateTimeField(default=datetime.now, verbose_name="挿入時間")

    class Meta:
        verbose_name = "商品swiperImages"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.goods.name


class Banner(models.Model):
    """
    swiper用の商品image
    """
    goods = models.ForeignKey(Goods, verbose_name="商品", on_delete=models.CASCADE)
    image = models.ImageField(upload_to="banner", verbose_name="ホームページswiper用画像")
    index = models.IntegerField(default=0, verbose_name="swiper順番")
    add_time = models.DateTimeField(default=datetime.now, verbose_name="挿入時間")

    class Meta:
        verbose_name = "swiper用の商品image"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.goods.name


class IndexAd(models.Model):
    category = models.ForeignKey(GoodsCategory, related_name="category",
                                 verbose_name="商品カテゴリー", on_delete=models.CASCADE)
    goods = models.ForeignKey(Goods, related_name='goods', on_delete=models.CASCADE)

    class Meta:
        verbose_name = "ホームページ商品カテゴリー広告"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.goods.name


class HotSearchWords(models.Model):
    """
    人気キーワード
    """
    keywords = models.CharField(default="", max_length=20, verbose_name="人気キーワード")
    index = models.IntegerField(default=0, verbose_name="並び順")
    add_time = models.DateTimeField(default=datetime.now, verbose_name="挿入時間")

    class Meta:
        verbose_name = "人気キーワード"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.keywords
```
管理画面を作成するため、admin.pyも変更します。

```python:goods/admin.py
from django.contrib import admin
from django.contrib.admin.options import InlineModelAdmin


from .models import Goods, GoodsImage, GoodsCategory, GoodsCategoryBrand, Banner, HotSearchWords, IndexAd


class GoodsAdmin(admin.ModelAdmin):
    list_display = ["id", "name", "click_num", "sold_num", "fav_num", "goods_num", "market_price",
                    "shop_price", "goods_brief", "is_new", "is_hot", "add_time"]
    search_fields = ['name', ]
    list_editable = ["is_hot", ]
    list_filter = ["name", "click_num", "sold_num", "fav_num", "goods_num", "market_price",
                   "shop_price", "is_new", "is_hot", "add_time", "category__name"]

    class GoodsImagesInline(InlineModelAdmin):
        model = GoodsImage
        exclude = ["add_time"]
        extra = 1
        style = 'tab'

    inlines = [GoodsImagesInline]


class GoodsCategoryAdmin(admin.ModelAdmin):
    list_display = ["name", "category_type", "parent_category", "add_time"]
    list_filter = ["category_type", "parent_category", "name"]
    search_fields = ['name', ]


class GoodsBrandAdmin(admin.ModelAdmin):
    list_display = ["category", "image", "name", "desc"]


class BannerGoodsAdmin(admin.ModelAdmin):
    list_display = ["goods", "image", "index"]


class HotSearchAdmin(admin.ModelAdmin):
    list_display = ["keywords", "index", "add_time"]


class IndexAdAdmin(admin.ModelAdmin):
    list_display = ["category", "goods"]


admin.site.register(Goods, GoodsAdmin)
admin.site.register(GoodsCategory, GoodsCategoryAdmin)
admin.site.register(Banner, BannerGoodsAdmin)
admin.site.register(GoodsCategoryBrand, GoodsBrandAdmin)
admin.site.register(HotSearchWords, HotSearchAdmin)
admin.site.register(IndexAd, IndexAdAdmin)
```
**trade**と**user_operation**はしばらく使わないので一旦飛ばします。

**settings.py**と**urls.py**に以下の内容を追加します。

```python:settings.py
...
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
...
```

```python:api/urls.py
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve
from api.settings import MEDIA_ROOT

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path(r'^media/(?P<path>.*)$', serve, {"document_root": MEDIA_ROOT}),
]
```
下記のcodeを実行しましょう。

```shell:api

python manage.py makemigrations 
python manage.py migrate
python manage.py createsuperuser
```

完了後

```shell

 python manage.py runserver 
````
でサーバーを立ち上げ、ローカルの管理画面にアクセスしてみてください。
http://127.0.0.1:8000/admin
ログインして、下記の内容確認できたらオーケーです。

<img width="927" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/3cdce46d-cb8b-9fc9-3b6d-c38e8e618403.png">

# 次回予告

**Xadmin**というパッケージ使用して、管理画面を変更します。
~~実際**Django REST framework** の機能を使用して、APIを作っていきます。~~

最後まで読んで頂き、ありがとうございました。:relaxed:
###次回
[SEIYU風のECサイトを作りましょう（2）Xadminを使って管理画面を一新します](https://qiita.com/Syoitu/items/11fac037759220b30cd2)