DjangoとVue.jsを使用した生鮮食品ECサイトのデータベース設計とデータ登録

Vue.js環境のセットアップ

まずはフロントエンド環境を構築します。

# Node.jsのインストール
https://nodejs.org/en/

# cnpmのインストール
npm install -g cnpm --registry=https://registry.npm.taobao.org

# 依存パッケージのインストール
cnpm install

# 開発サーバーの起動
cnpm run dev

Django環境のセットアップ

次にバックエンド環境を構築します。

# 仮想環境で必要なパッケージをインストール
pip install django==1.11.11
pip install djangorestframework
pip install -i https://pypi.douban.com/simple django==2.0.2
pip install markdown
pip install django-filter
pip install pillow  # 画像処理用
pip install pymysql

プロジェクトディレクトリ構造の構築

以下のPythonパッケージとフォルダを作成します。

  • Pythonパッケージ:
    • extra_apps(拡張ソースコードパッケージ)
    • apps(すべてのアプリ用)
  • フォルダ:
    • media(画像保存用)
    • db_tools(データベース関連)

4つのアプリケーションの作成

users         # ユーザー管理
goods        # 商品管理
trade        # 取引管理
user_operation # ユーザー操作管理

作成した4つのアプリ、xadmin、DjangoUeditorをINSTALLED_APPSに追加します。

INSTALLED_APPS = [
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig',
    'goods.apps.GoodsConfig',
    'trade.apps.TradeConfig',
    'user_operation.apps.UserOperationConfig',
    'rest_framework',
    'xadmin',
    'crispy_forms',
    'DjangoUeditor'
]

extra_appsとappsをソースルートとしてマークし、settings.pyにパスを追加します。

# settings.py
import sys
import os

sys.path.insert(0, BASE_DIR)
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps'))

メディアファイルの保存パスを設定します。

# settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

データベースモデルの設計

ユーザーモデルの設計

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="生年月日")
    gender = models.CharField(max_length=6, choices=(("male", "男"), ("female", "女")), default="female", verbose_name="性別")
    mobile = models.CharField(null=True, blank=True, max_length=11, verbose_name="電話番号")
    email = models.EmailField(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):
    """
    SMS認証コード
    """
    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 = "SMS認証コード"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.code

システムのユーザーモデルを置き換えるには、settings.pyに以下を追加します。

# settings.py
# システムのユーザーモデルを置き換え、UserProfileを有効にする
AUTH_USER_MODEL = 'users.UserProfile'

商品カテゴリモデルの設計

from datetime import datetime
from django.db import models
from DjangoUeditor.models import UEditorField

class GoodsCategory(models.Model):
    """
    商品カテゴリ
    """
    CATEGORY_TYPE = (
        (1, "第1階層カテゴリ"),
        (2, "第2階層カテゴリ"),
        (3, "第3階層カテゴリ"),
    )

    name = models.CharField(default="", max_length=30, 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="親カテゴリ",
                                        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="商品カテゴリ")
    name = models.CharField(default="", max_length=30, verbose_name="ブランド名", help_text="ブランド名")
    desc = models.TextField(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, verbose_name="商品カテゴリ")
    goods_sn = models.CharField(max_length=50, default="", verbose_name="商品ユニークID")
    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="商品概要")
    goods_desc = UEditorField(verbose_name="詳細内容", imagePath="goods/images/", width=1000, height=300,
                              filePath="goods/files/", default='')
    ship_free = models.BooleanField(default=True, verbose_name="送料込み")
    goods_front_image = models.ImageField(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 IndexAd(models.Model):
    category = models.ForeignKey(GoodsCategory, related_name='category', verbose_name="商品カテゴリ")
    goods = models.ForeignKey(Goods, related_name='goods')

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

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


class GoodsImage(models.Model):
    """
    商品ギャラリー画像
    """
    goods = models.ForeignKey(Goods, verbose_name="商品", related_name="images")
    image = models.ImageField(upload_to="", verbose_name="画像", null=True, blank=True)
    add_time = models.DateTimeField(default=datetime.now, verbose_name="追加時間")

    class Meta:
        verbose_name = '商品画像'
        verbose_name_plural = verbose_name

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


class Banner(models.Model):
    """
    ホームページバナー商品
    """
    goods = models.ForeignKey(Goods, verbose_name="商品")
    image = models.ImageField(upload_to='banner', 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.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

取引モデルの設計

from datetime import datetime
from django.db import models
from django.contrib.auth import get_user_model
from goods.models import Goods

User = get_user_model()

class ShoppingCart(models.Model):
    """
    ショッピングカート
    """
    user = models.ForeignKey(User, verbose_name="ユーザー")
    goods = models.ForeignKey(Goods, verbose_name="商品")
    nums = models.IntegerField(default=0, verbose_name="購入数量")

    add_time = models.DateTimeField(default=datetime.now, verbose_name="追加時間")

    class Meta:
        verbose_name = 'ショッピングカート'
        verbose_name_plural = verbose_name
        unique_together = ("user", "goods")

    def __str__(self):
        return f"{self.goods.name}({self.nums})"


class OrderInfo(models.Model):
    """
    注文情報
    """
    ORDER_STATUS = (
        ("TRADE_SUCCESS", "成功"),
        ("TRADE_CLOSED", "期限切れ"),
        ("WAIT_BUYER_PAY", "取引作成"),
        ("TRADE_FINISHED", "取引終了"),
        ("paying", "支払待ち"),
    )

    user = models.ForeignKey(User, verbose_name="ユーザー")
    order_sn = models.CharField(max_length=30, null=True, blank=True, unique=True, verbose_name="注文番号")
    trade_no = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name="取引番号")
    pay_status = models.CharField(choices=ORDER_STATUS, default="paying", max_length=30, verbose_name="注文ステータス")
    post_script = models.CharField(max_length=200, verbose_name="注文メモ")
    order_mount = models.FloatField(default=0.0, verbose_name="注文金額")
    pay_time = models.DateTimeField(null=True, blank=True, verbose_name="支払時間")

    # ユーザー情報
    address = models.CharField(max_length=100, default="", verbose_name="配送先住所")
    signer_name = models.CharField(max_length=20, default="", verbose_name="受取人名")
    singer_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 str(self.order_sn)


class OrderGoods(models.Model):
    """
    注文商品詳細
    """
    order = models.ForeignKey(OrderInfo, verbose_name="注文情報", related_name="goods")
    goods = models.ForeignKey(Goods, verbose_name="商品")
    goods_num = 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 str(self.order.order_sn)

ユーザー操作モデルの設計

from datetime import datetime
from django.db import models
from django.contrib.auth import get_user_model
from goods.models import Goods

User = get_user_model()

class UserFav(models.Model):
    """
    ユーザーお気に入り
    """
    user = models.ForeignKey(User, verbose_name="ユーザー")
    goods = models.ForeignKey(Goods, verbose_name="商品", help_text="商品ID")
    add_time = models.DateTimeField(default=datetime.now, verbose_name="追加時間")

    class Meta:
        verbose_name = 'ユーザーお気に入り'
        verbose_name_plural = verbose_name
        unique_together = ("user", "goods")

    def __str__(self):
        return self.user.username


class UserLeavingMessage(models.Model):
    """
    ユーザーメッセージ
    """
    MESSAGE_CHOICES = (
        (1, "メッセージ"),
        (2, "クレーム"),
        (3, "問い合わせ"),
        (4, "アフターサービス"),
        (5, "購入希望")
    )
    user = models.ForeignKey(User, verbose_name="ユーザー")
    message_type = models.IntegerField(default=1, choices=MESSAGE_CHOICES, verbose_name="メッセージタイプ",
                                      help_text="メッセージタイプ: 1(メッセージ),2(クレーム),3(問い合わせ),4(アフターサービス),5(購入希望)")
    subject = models.CharField(max_length=100, default="", verbose_name="件名")
    message = models.TextField(default="", verbose_name="メッセージ内容", help_text="メッセージ内容")
    file = models.FileField(upload_to="message/images/", 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.subject


class UserAddress(models.Model):
    """
    ユーザー配送先住所
    """
    user = models.ForeignKey(User, verbose_name="ユーザー")
    province = models.CharField(max_length=100, default="", verbose_name="都道府県")
    city = models.CharField(max_length=100, default="", verbose_name="市区町村")
    district = models.CharField(max_length=100, default="", verbose_name="地区")
    address = models.CharField(max_length=100, default="", verbose_name="詳細住所")
    signer_name = models.CharField(max_length=100, default="", verbose_name="受取人名")
    signer_mobile = models.CharField(max_length=11, default="", 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.address

システム設定の構成

タイムゾーンと言語設定

LANGUAGE_CODE = 'zh-hans'  # 中国語サポート、Django1.8以降でサポート
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False   # デフォルトはTrueでUTC時間、ローカル時間を使用するためにFalseに変更

画像パス設定

MEDIA_URL = "/media/"
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
)

MEDIA_ROOT = os.path.join(BASE_DIR, "media")

xadminの設定

extra_appsディレクトリに以下のソースファイルを配置します:

  • xadmin
  • DjangoUeditor

xadminの依存パッケージをインストールします。

pip install django-crispy-forms
pip install django-reversion
pip install django-formtools 
pip install future
pip install httplib2
pip install six
pip install xlwt
pip install xlsxwriter

各アプリケーションにxadmin設定を追加します。

商品管理のxadmin設定

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

class GoodsAdmin(object):
    list_display = ["name", "click_num", "sold_num", "fav_num", "goods_num", "market_price",
                    "shop_price", "goods_brief", "goods_desc", "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"]
    style_fields = {"goods_desc": "ueditor"}

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

    inlines = [GoodsImagesInline]


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


class GoodsBrandAdmin(object):
    list_display = ["category", "image", "name", "desc"]

    def get_context(self):
        context = super(GoodsBrandAdmin, self).get_context()
        if 'form' in context:
            context['form'].fields['category'].queryset = GoodsCategory.objects.filter(category_type=1)
        return context


class BannerGoodsAdmin(object):
    list_display = ["goods", "image", "index"]


class HotSearchAdmin(object):
    list_display = ["keywords", "index", "add_time"]


class IndexAdAdmin(object):
    list_display = ["category", "goods"]


xadmin.site.register(Goods, GoodsAdmin)
xadmin.site.register(GoodsCategory, GoodsCategoryAdmin)
xadmin.site.register(Banner, BannerGoodsAdmin)
xadmin.site.register(GoodsCategoryBrand, GoodsBrandAdmin)
xadmin.site.register(HotSearchWords, HotSearchAdmin)
xadmin.site.register(IndexAd, IndexAdAdmin)

取引管理のxadmin設定

import xadmin
from .models import ShoppingCart, OrderInfo, OrderGoods

class ShoppingCartAdmin(object):
    list_display = ["user", "goods", "nums", ]


class OrderInfoAdmin(object):
    list_display = ["user", "order_sn",  "trade_no", "pay_status", "post_script", "order_mount",
                    "order_mount", "pay_time", "add_time"]

    class OrderGoodsInline(object):
        model = OrderGoods
        exclude = ['add_time', ]
        extra = 1
        style = 'tab'

    inlines = [OrderGoodsInline, ]


xadmin.site.register(ShoppingCart, ShoppingCartAdmin)
xadmin.site.register(OrderInfo, OrderInfoAdmin)

ユーザー操作管理のxadmin設定

import xadmin
from .models import UserFav, UserLeavingMessage, UserAddress

class UserFavAdmin(object):
    list_display = ['user', 'goods', "add_time"]


class UserLeavingMessageAdmin(object):
    list_display = ['user', 'message_type', "message", "add_time"]


class UserAddressAdmin(object):
    list_display = ["signer_name", "signer_mobile", "district", "address"]

xadmin.site.register(UserFav, UserFavAdmin)
xadmin.site.register(UserAddress, UserAddressAdmin)
xadmin.site.register(UserLeavingMessage, UserLeavingMessageAdmin)

ユーザー管理のxadmin設定

import xadmin
from xadmin import views
from .models import VerifyCode

class BaseSetting(object):
    enable_themes = True
    use_bootswatch = True


class GlobalSettings(object):
    site_title = "生鮮食品ECサイト管理画面"
    site_footer = "mxshop"
    # menu_style = "accordion"


class VerifyCodeAdmin(object):
    list_display = ['code', 'mobile', "add_time"]


xadmin.site.register(VerifyCode, VerifyCodeAdmin)
xadmin.site.register(views.BaseAdminView, BaseSetting)
xadmin.site.register(views.CommAdminView, GlobalSettings)

ルートURLにxadminアクセスパスを設定します。

import xadmin
from MxShop.settings import MEDIA_ROOT
from django.views.static import serve

urlpatterns = [
    url(r'^admin/', xadmin.site.urls),
    url(r'^media/(?P<path>.*)$', serve, {"document_root": MEDIA_ROOT}),
]

データ登録

mediaディレクトリに登録する画像を配置します。

db_toolsディレクトリに以下のファイルを作成します。

カテゴリデータファイル

# db_tools/data/category_data.py
category_data = [
    {
        'sub_categorys': [
            {
                'sub_categorys': [
                    {'code': 'yr', 'name': '羊肉'},
                    {'code': 'ql', 'name': '禽類'},
                    {'code': 'zr', 'name': '猪肉'},
                    {'code': 'nr', 'name': '牛肉'}
                ],
                'code': 'jprl',
                'name': '精品肉类'
            },
            # 他のカテゴリデータ...
        ],
        'code': 'sxsp',
        'name': '生鲜食品'
    },
    # 他のトップカテゴリ...
]

商品データファイル

# db_tools/data/product_data.py
product_data = [
    {
        'images': [
            'goods/images/1_P_1449024889889.jpg',
            'goods/images/1_P_1449024889264.jpg'
        ],
        'categorys': ['首页', '生鲜食品', '根茎类'],
        'market_price': '¥232元',
        'name': '新鲜水果甜蜜香脆单果约800克',
        'desc': '商品説明文...',
        'sale_price': '¥156元',
        'goods_desc': '<p>商品詳細HTML</p>'
    },
    # 他の商品データ...
]

カテゴリデータインポートスクリプト

# db_tools/import_category_data.py
import sys
import os

pwd = os.path.dirname(os.path.realpath(__file__))
sys.path.append(pwd + "../")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "MxShop.settings")

import django
django.setup()

from goods.models import GoodsCategory
from db_tools.data.category_data import category_data

for lev1_cat in category_data:
    lev1_instance = GoodsCategory()
    lev1_instance.code = lev1_cat["code"]
    lev1_instance.name = lev1_cat["name"]
    lev1_instance.category_type = 1
    lev1_instance.save()

    for lev2_cat in lev1_cat["sub_categorys"]:
        lev2_instance = GoodsCategory()
        lev2_instance.code = lev2_cat["code"]
        lev2_instance.name = lev2_cat["name"]
        lev2_instance.category_type = 2
        lev2_instance.parent_category = lev1_instance
        lev2_instance.save()

        for lev3_cat in lev2_cat["sub_categorys"]:
            lev3_instance = GoodsCategory()
            lev3_instance.code = lev3_cat["code"]
            lev3_instance.name = lev3_cat["name"]
            lev3_instance.category_type = 3
            lev3_instance.parent_category = lev2_instance
            lev3_instance.save()

商品データインポートスクリプト

# db_tools/import_goods_data.py
import sys
import os

pwd = os.path.dirname(os.path.realpath(__file__))
sys.path.append(pwd + "../")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "MxShop.settings")

import django
django.setup()

from goods.models import Goods, GoodsCategory, GoodsImage
from db_tools.data.product_data import product_data

for goods_detail in product_data:
    goods = Goods()
    goods.name = goods_detail["name"]
    goods.market_price = float(int(goods_detail["market_price"].replace("¥", "").replace("元", "")))
    goods.shop_price = float(int(goods_detail["sale_price"].replace("¥", "").replace("元", "")))
    goods.goods_brief = goods_detail["desc"] if goods_detail["desc"] is not None else ""
    goods.goods_desc = goods_detail["goods_desc"] if goods_detail["goods_desc"] is not None else ""
    goods.goods_front_image = goods_detail["images"][0] if goods_detail["images"] else ""

    category_name = goods_detail["categorys"][-1]
    category = GoodsCategory.objects.filter(name=category_name)
    if category:
        goods.category = category[0]
    goods.save()

    for goods_image in goods_detail["images"]:
        goods_image_instance = GoodsImage()
        goods_image_instance.image = goods_image
        goods_image_instance.goods = goods
        goods_image_instance.save()

以下のコマンドを実行してデータをインポートします。

python db_tools/import_category_data.py
python db_tools/import_goods_data.py

管理者ユーザーを作成し、管理画面にログインすると、登録したデータを確認できます。

タグ: Django vue.js REST Framework ECサイト データベース設計

5月19日 01:05 投稿