Django製フリマサイト「ホクマ」ができるまで part5
今回は、 4.2 「ホクマ」の機能構成・技術の話 について話します。
「ホクマができるまで」シリーズについて
シリーズの目次はこちらにあります。今回は第5回です。
この度「ホクマ」という北大生限定フリマWebサービスを作りました。(GitHub)
その作り方について大雑把に解説していくことで、(基本個人)学生によるWebサービス開発の流れを説明していきたいと思います。
(ただし、ビジネス的な観点はあまり考えて作っていません。 収益化の方法などは書いていかない予定なので気をつけてください。 )
使った技術
サービス開発の流れ(簡単に)
- 1 前提知識
- 2 Djangoを始めた(Django Girls Tutorial)
- 3 いろんなDjangoサイトを適当に作った
- 4 「ホクマ」を作り始める
- 4.1 開発初期にやったこと
- 4.2 「ホクマ」の機能構成・技術の話(今回!)
- 4.3 「ホクマ」でつかったDjangoパターンとサイトデザインについて
- 5 開発を続けていくために
- 6 開発ツールについて
今回は「 4.2 「ホクマ」の機能構成・技術の話 」について書いていきます。
4.2 「ホクマ」の機能構成・技術の話
前回までは、開発初期の話をしました。具体的には以下のような話でした。
- どんなものを作るかなんとなく決める
- 具体的にどんなものを作るのかを考える・必須なことの列挙
- ユーザーが操作する一連の流れをシンプルに作る
- githubやweb上にある似たようなサービスを参考にしよう
今回は、もう少し開発が進んだ時点での話や、「ホクマ」の詳しい機能の構成や技術構成について話していきます。今回は、以下の二つの話をします
- フリマサイト「ホクマ」のモデル設計(相当拙いものですが)
- インフラを含めた技術構成
さらに次回で、以下の二つの話をする予定です
- その他Djangoについての細かいこと
- サイトのデザインについて
4.2.1 「ホクマ」のモデル設計
pygraphvizを使って、djangoのモデルのクラス図を自動生成してみました。
ごちゃごちゃしていてわかりませんね・・・一つずつ簡単に説明していきます
「ホクマ」で使ったモデルクラス
- User: ユーザアカウント
- Product: 商品
- ProductImage: 商品の画像(複数あるため)
- Chat: チャットルーム一つ
- Talk: チャットメッセージ一通
- UserRating: ユーザ評価
- Notification: 通知(お知らせ)
- TODO: TODO
- RatingTodo: ユーザ評価を督促するTODO
- ReportToRecieveTodo: 商品受け取りの報告を督促するTODO
- Contact: お問い合わせ
そんなに多くないですね。ここだけ見ると、すぐに作れる人もいると思います。実際、そこまで複雑な機能が求められるかはユーザヒアリングをしないとわからないので、まずはこれくらいでいいのではないでしょうか。
一つずつ書いていきます。djangoのコードになっていますが、なんとなく構造はわかると思います
Userモデル
DjangoのAbstractBaseUserクラスを継承して作成しました。
class User(PermissionsMixin, AbstractBaseUser): username = models.CharField( ('username'), max_length=150, unique=True, help_text=( '英数字と@, ., +, -, _が使えます'), validators=[username_validator], error_messages={ 'unique': ("このユーザ名は既に登録されています"), }, ) email = models.EmailField(('email'), unique=True) intro = models.TextField(('intro'), max_length=200, blank=True) date_joined = models.DateTimeField(default=timezone.now, editable=False) is_active = models.BooleanField(default=False) icon = VersatileImageField('',upload_to='account',blank=True) EMAIL_FIELD = 'email' USERNAME_FIELD = 'username'
Productモデル
商品画像に関しては、ProductImageモデルが管理しています。Djangoは若干この関連付けが特殊jなような気がしてます。Productモデルは画像関係のカラムを持っていないんですね。しかし、実はこれでもProductインスタンスから、ProductImageを取り出すことができるんです。
詳しくは以下をどうぞ
多対一 (many-to-one) 関係 | Django documentation | Django
class Product(models.Model): seller = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='seller') title = models.CharField('商品名', max_length=200, validators=[has_no_singlequote]) description = models.TextField('説明文', max_length=2000) price = models.PositiveIntegerField('値段(円)', default=0) is_sold = models.BooleanField(default=False) created_date = models.DateTimeField(default=timezone.now) updated_date = models.DateTimeField(blank=True, null=True) wanting_users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='wanting_users') buyer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, related_name='buyer') access_level = models.CharField('公開/非公開', max_length=10, choices=[(level.name, level.value) for level in AccessLevelChoice], default='public' ) def update(self): self.updated_date = timezone.now() self.save() def __str__(self): return self.title
ProductImageモデル
class ProductImage(models.Model): image = VersatileImageField( '', upload_to='product', blank=True, null=True ) product = models.ForeignKey(Product, on_delete=models.CASCADE) def update(self): self.save() @property def thumbnail_url(self): if self.image and hasattr(self.image, 'url'): return self.image.thumbnail['600x600'].url
Chatモデル
商品の値段交渉や受け渡し方法の相談などをするためのチャットのためのモデルです。
ChatモデルとTalkモデルで分けています。
Chatモデルは以下の情報を持っています。チャットルームのような扱いです。
- 商品(どの商品についてのチャットか)
- 商品の売り手
- 商品の買い手
Talkが実際のメッセージになります
class Chat(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True) product_seller = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='product_seller') product_wanting_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='product_wanting_user') created_date = models.DateTimeField(default=timezone.now) updated_date = models.DateTimeField(blank=True, null=True) def update(self): self.updated_date = timezone.now() self.save()
Talkモデル
Talkモデルは以下の情報を持っています。実際のメッセージになります。
- 話し手
- チャットID(これでどのチャットルームで話されているか判定)
- メッセージ本文
class Talk(models.Model): talker = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) chat = models.ForeignKey(Chat, on_delete=models.CASCADE, null=True) sentence = models.TextField(max_length=200) created_date = models.DateTimeField(default=timezone.now) updated_date = models.DateTimeField(blank=True, null=True) def update(self): self.updated_date = timezone.now() self.save()
UserRatingモデル
ユーザ評価のモデルです。
- 商品ID
- レビュアーID
- レビュイーID
- 評価値
class UserRating(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True) rating_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='rating_user') rated_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='rated_user') rating = models.CharField(max_length=6)
Notificationモデル
お知らせ・通知のモデルです。商品が売れたりした時の通知などに使います。
- 通知の受け手
- 既読フラグ
- 通知メッセージ本文
- 通知にあるURL(このように分離しておくとHTMLとして表現しやすくなる)
class Notification(models.Model): reciever = models.ForeignKey(User, on_delete=models.CASCADE, null=True) unread = models.BooleanField(default=True) message = models.TextField(max_length=256, null=True) relative_url = models.TextField(max_length=256, null=True)
TODOモデル(親クラス)
通知とほぼ同じなので省略
class Todo(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True) is_done = models.BooleanField(default=False) message = models.TextField(max_length=256, null=True) relative_url = models.TextField(max_length=256, null=True) created_date = models.DateTimeField(default=timezone.now) def done(self): self.is_done = True def update(self): self.save()
ReportToRecieveTodo
通知とほぼ同じなので省略
class ReportToRecieveTodo(Todo): product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True) def set_template_message(self): self.message = '「'+self.product.title+'」の受け取りが完了しましたら、「商品を受け取りました」をクリックしてください'
RatingTodo
通知とほぼ同じなので省略
class RatingTodo(Todo): product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True) def set_template_message(self): if self.user == self.product.seller: self.message = self.product.buyer.username+'との間での「'+self.product.title+'」の受け渡しの完了を確認しました。最後に購入者を評価してください。' else: self.message = self.product.seller.username+'との間での「'+self.product.title+'」の受け渡しの完了を確認しました。最後に出品者を評価してください。'
Contactモデル
問い合わせ(質問)についてのモデル。
- 問い合わせ本文
- メールアドレス
class Contact(models.Model): text = models.TextField(('お問い合わせ内容'), max_length=1000) email = models.EmailField(('メールアドレス'), default='')
4.2.2 インフラを含めた技術構成
技術構成図を再掲します。
だいたい以下のようにレイヤ分けされると思います。ここを詳しく話すと枝葉の話になるのでとりあえずここでは省略します。
ここについてはまた今度かきたいとおもいます。開発環境/QA環境/本番環境の3つに分けてdockerを使った開発について語ります多分。(11月)
- アプリケーション
- Django
- Bootstrap4
- データベース
- Postgres
- Webサーバ
- Nginx
- Let's Encrypt(HTTPS対応)
- その他
- CentOS7
- Docker
- お名前.com(ドメイン取得)
- ConohaVPS(配信サーバ)
- メールサーバ
次回
次回は、以下の二つの話をする予定です
- その他Djangoについての細かいこと
- サイトのデザインについて
ついで: 僕のプログラミング歴について
以下の過去投稿にあります