LoginSignup
1

More than 1 year has passed since last update.

Djangoアプリ開発【友達リスト】〜その④〜

Last updated at Posted at 2021-06-15

はじめに

今回はDjangoを使って友達リスト的なアプリを作成しようと思います。
プロジェクト名はTomodachi

カテゴリ機能実装

# ブランチを切り替える
$ git branch feature-category
$ git checkout feature-category

カテゴリモデル作成

friendslist/models.py
# ユーザー対カテゴリ(1対多)のリレーションで作成
from django.contrib.auth import get_user_model

class Category(models.Model):
    name = models.CharField(default="", max_length=20)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
# マイグレーションする
$ python manage.py makemigrations
$ python manage.py migrate
friendslist/admin.py
# 管理画面から投稿できるようにする
from friendslist.models import User, Friend, Category

admin.site.register(Category)

カテゴリ一覧表示機能実装

friendslist/views.py
# 友達一覧ページのカテゴリ一覧表示の記述をする(request.userに紐付いたカテゴリのみ表示)
from friendslist.models import Friend, Category

def index(request):
    user = request.user  ←☆追記する(ログインユーザー)
    categories = Category.objects.filter(user=user)  ←☆追記する
    friends = Friend.objects.all()
    context = {
        'friends': friends,
        'categories': categories,
    }
    return render(request, 'friendslist/index.html', context)
config/urls.py
# カテゴリ一覧ページのカテゴリ一覧表示のURLを設定する
path('category/', views.category_index),
friendslist/views.py
# カテゴリ一覧ページのカテゴリ一覧表示の記述をする
def category_index(request):
    user = request.user
    categories = Category.objects.filter(user=user)
    context = {
        'categories': categories,
    }
    return render(request, 'friendslist/category/index.html', context)
friendslist/category/index.html
# カテゴリ一覧ページを作成
{% extends 'friendslist/base.html' %}

{% block content %}
<main class="container mb-5">
    <div class="row">
        <div class="col col-md-4">
            <h2>カテゴリ一覧</h2>
            <ul class="list-group">
              {% for category in categories %}
                <li class="list-group-item">
                  {{ category.name }}
                </li>
              {% endfor %}
              </ul>
        </div>
    </div>
</main>
{% endblock %}

カテゴリ作成機能実装

config/urls.py
# URLを設定する
path('category/create', views.category_create),
friendslist/forms.py
# Categoryフォームを設定する
from friendslist.models import Friend, Category

class CategoryForm(forms.ModelForm):
    class Meta:
        model = Category
        fields = (
            'name',
        )
friendslist/views.py
# カテゴリ作成機能の記述をする
from friendslist.forms import FriendForm, UserCreationForm, CategoryForm

def category_create(request):
    if request.method == 'POST':
        form = CategoryForm(request.POST)
        if form.is_valid():
            category = form.save(commit=False)
            category.user = request.user
            category.save()
            return redirect('/category/')
    return render(request, 'friendslist/category/create.html')
category/create.html
# カテゴリ作成ページを作成する
{% extends 'friendslist/base.html' %}

{% block content %}
<main class="container mb-5">
    <form class="row" method="POST">
        {% csrf_token %}
        <div class="col col-md-4">
            <h2>カテゴリ作成</h2>
            <ul class="list-group">
                <li class="list-group-item">
                    <label class="form-label">カテゴリ名</label>
                    <input type="text" class="form-control" id="id_name" name="name" placeholder="仕事関係, 大学">
                </li>
                <li class="list-group-item">
                    <button type="submit" class="btn btn-success float-end">保存</button>
                </li>
            </ul>
        </div>
    </form>
</main>
{% endblock %}

カテゴリ削除機能を実装する

config/urls.py
# URLを設定する
path('category/<slug:pk>/delete', views.category_delete, name="category_delete"),
friendslist/views.py
# カテゴリ削除機能を実装する
def category_delete(request, pk):
    try:
        category = Category.objects.get(pk=pk)
    except Category.DoesNotExist:
        raise Http404
    category.delete()
    return redirect('/category/')
friendslist/category/index.html
# カテゴリ削除ボタンを実装する
<a href="{% url 'category_delete' category.pk %}" class="btn btn-danger btn-sm float-end">削除</a>

カテゴリと友達のリレーション

カテゴリと友達のリレーションを貼る

firendslist/models.py
# カテゴリ対友達を1対多で設定する
class Friend(models.Model):
    name = models.CharField(default="", max_length=20)
    furigana = models.CharField(blank=True, null=True, max_length=20)
    nickname = models.CharField(blank=True, null=True, max_length=20)
    sex = models.IntegerField(default=0)
    birthday = models.DateField(blank=True, null=True, auto_now=False, auto_now_add=False)
    birthplace = models.CharField(blank=True, null=True, max_length=30)
    hobby = models.CharField(blank=True, null=True, max_length=30)
    company = models.CharField(blank=True, null=True, max_length=30)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, default=1)  ←☆追加する
# マイグレーションする
$ python manage.py makemigrations
$ python manage.py migrate

データ追加後のモデルにForeignKeyを実装する場合は、エラーが出るので、
こちらを参照にして実装します。
結果的にデフォルト値を1にしました。(あとからカテゴリを変更する機能をつけるしかないようです)

仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする)(友達一覧では友達すべてが見れるようにする)

config/urls.py
# 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする)
(友達一覧では友達すべてが見れるようにする)
path('category/<slug:pk>', views.category_index),
friendslist/views.py
# 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする)
(友達一覧では友達すべてが見れるようにする)
def category_index(request, pk):
    user = request.user
    categories = Category.objects.filter(user=user)
    current_category = Category.objects.get(pk=pk)
    friends = Friend.objects.filter(category=current_category)
    context = {
        'friends': friends,
        'categories': categories,
        'current_category': current_category,
    }
    return render(request, 'friendslist/category/index.html', context)
index.html
# 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする)
(友達一覧では友達すべてが見れるようにする)
{% extends 'friendslist/base.html' %}

{% block content %}
<main class="container mb-5">
    <div class="row">
        <div class="column col-md-8">
            <h2>友達一覧</h2>
            <ul class="list-group">
            {% for friend in friends %}
                <li class="list-group-item">
                  {{ friend.name }}
                  <div class="float-end">
                    <a href="{% url 'friend' friend.pk %}" class="btn btn-success btn-sm">詳細</a>
                    <a href="{% url 'delete' friend.pk %}" class="btn btn-danger btn-sm">削除</a>
                  </div>
                </li>
            {% endfor %}
            </ul>
        </div>
    </div>
</main>
{% endblock %}
category/index.html
# 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする)
(友達一覧では友達すべてが見れるようにする)
{% extends 'friendslist/base.html' %}

{% block content %}
<main class="container mb-5">
    <div class="row">
        <div class="col col-md-4">
            <h2>カテゴリ一覧</h2>
            <ul class="list-group">
              {% for category in categories %}
                <li class="list-group-item">
                  {{ category.name }}
                  <a href="{% url 'category_delete' category.pk %}" class="btn btn-danger btn-sm float-end">削除</a>
                </li>
              {% endfor %}
              </ul>
        </div>
        <div class="column col-md-8">
          <h2>「{{ current_category.name }}」の友達一覧</h2>
          <ul class="list-group">
          {% for friend in friends %}
              <li class="list-group-item">
                {{ friend.name }}
                <div class="float-end">
                  <a href="{% url 'friend' friend.pk %}" class="btn btn-success btn-sm">詳細</a>
                  <a href="{% url 'delete' friend.pk %}" class="btn btn-danger btn-sm">削除</a>
                </div>
              </li>
          {% endfor %}
          </ul>
        </div>
    </div>
</main>
{% endblock %}

上記の仕様ではエラーが出てしまうので、ユーザーが持つ最初のカテゴリページをそのユーザーのカテゴリ一覧ページの基準とする

friendslist/views.py
# 最初のカテゴリページを基準とする(作成と削除のリダイレクト先にする)
def category_create(request):
    if request.method == 'POST':
        form = CategoryForm(request.POST)
        if form.is_valid():
            category = form.save(commit=False)
            category.user = request.user
            category.save()
            user = request.user
            categories = Category.objects.filter(user=user)
            first_category = categories.first()
            return redirect('/category/{}'.format(first_category.id))
    return render(request, 'friendslist/category/create.html')

def category_delete(request, pk):
    try:
        category = Category.objects.get(pk=pk)
    except Category.DoesNotExist:
        raise Http404
    category.delete()

    user = request.user
    categories = Category.objects.filter(user=user)
    first_category = categories.first()
    return redirect('/category/{}'.format(first_category.id))

カレントカテゴリのリストをactiveにする

category/index.html
# カレントカテゴリのリストをactiveにする
<li class="list-group-item {% if category.pk == current_category.pk %}list-group-item-warning{% endif %}">

カテゴリ一覧のリンクを作成する

category/index.html
# カレントカテゴリのリストをactiveにする
<a href="/category/{{ category.pk }}" class="text-decoration-none text-dark">{{ category.name }}</a>

リンクボタンのリンクを修正する

snippets/linkbtns.html
# リンクボタンのリンクを修正する
<div class="container mb-5">
    <div class="row">
      <div class="col">
        <a class="btn btn-danger" href="/" role="button">友達一覧</a>
      </div>
      <div class="col">
        <a class="btn btn-success" href="/create/" role="button">友達登録</a>
      </div>
      <div class="col">
        <a class="btn btn-warning" href="/category/{{ first_category.pk }}/" role="button">カテゴリ一覧</a>
      </div>
      <div class="col">
        <a class="btn btn-info" href="/category/create/" role="button">カテゴリ登録</a>
      </div>
    </div>
</div>
friendslist/views.py
# 色んなページからのfirst_categoryを記述する(後ほど、一つの関数にしたい)
    user = request.user
    categories = Category.objects.filter(user=user)
    first_category = categories.first()
    context = {
        'first_category': first_category,
    }

友達作成時にカテゴリに紐付けて作成する

forms.py
# カテゴリを追加する
class FriendForm(forms.ModelForm):
    class Meta:
        model = Friend
        fields = (
            'name',
            'furigana',
            'nickname',
            'sex',
            'birthday',
            'birthplace',
            'hobby',
            'company',
            'category',  ←☆追加する
        )
views.py
# categoriesを追加する
    context = {
        'categories': categories,  ←☆追加する
        'first_category': first_category,
    }
create.html
# カテゴリを選択するボックスを作成
                    <select class="form-control" id="id_category" name="category">
                        <option value="0" selected>選択しない</option>
                        {% for category in categories %}
                        <option value="{{ category.pk }}">{{ category.name }}</option>
                        {% endfor %}
                    </select>   
models.py
# カテゴリのdefault値を0(選択しない)にする
category = models.ForeignKey(Category, on_delete=models.CASCADE, default=0)
# マイグレーションする
$ python manage.py makemigrations
$ python manage.py migrate

見た目はadmin画面のFriendsのCategoryを検証して参考にする

友達一覧で紐付いたカテゴリボタンを表示する(bootstrapiconsを使う)

index.html
# カテゴリリンクボタンを作成する
                    <a href="/category/{{ friend.category.pk }}" class="btn btn-outline-secondary btn-sm">
                      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bookmarks" viewBox="0 0 16 16">
                        <path d="M2 4a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v11.5a.5.5 0 0 1-.777.416L7 13.101l-4.223 2.815A.5.5 0 0 1 2 15.5V4zm2-1a1 1 0 0 0-1 1v10.566l3.723-2.482a.5.5 0 0 1 .554 0L11 14.566V4a1 1 0 0 0-1-1H4z"/>
                        <path d="M4.268 1H12a1 1 0 0 1 1 1v11.768l.223.148A.5.5 0 0 0 14 13.5V2a2 2 0 0 0-2-2H6a2 2 0 0 0-1.732 1z"/>
                      </svg>
                      {{ friend.category.name }}
                    </a>

友達編集画面にカテゴリ紐付けの選択肢を追加する

views.py
# categoriesを追加する
def friend(request, pk):
    if request.method == 'POST':
        friend = Friend.objects.get(pk=pk)
        form = FriendForm(request.POST, instance=friend)
        if form.is_valid():
            friend.save()

    user = request.user
    categories = Category.objects.filter(user=user)            
    friend = Friend.objects.get(pk=pk)
    context = {
        'categories': categories,
        'friend': friend
    }
    return render(request, 'friendslist/friend.html', context)
friend.html
# 選択肢を追加する
                <li class="list-group-item">
                    <label class="form-label">カテゴリ</label>
                    <select class="form-control" id="id_category" name="category">
                        <option value="0" {% if friend.category.pk == 0 %}selected{% endif %}>選択しない</option>
                        {% for category in categories %}
                        <option value="{{ category.pk }}" {% if category.pk == friend.category.pk %}selected{% endif %}>{{ category.name }}</option>
                        {% endfor %}
                    </select>
                </li>

友達のカテゴリが選択されないときに作成と更新ができないので修正

models.py
# default値を決めるのではなく、nullでOKとする
としたかったが上手く行かなかった
おそらくデータベース側の処理で
カテゴリを選択しないときのデータの作成がvalidで止められてしまう

# てことでカテゴリ選択は必須とする
category = models.ForeignKey(Category, on_delete=models.CASCADE, default=1)
create.html,friend.html
# てことでカテゴリ選択は必須とする(以下を削除)
<option value="0" selected>選択しない</option>

さいごに

今回は
カテゴリ機能を実装しました
カテゴリとユーザーのリレーション
カテゴリと友達のリレーション
カテゴリの一覧表示
カテゴリの作成
カテゴリの削除
カテゴリに紐付いた友達の一覧表示
カテゴリに紐付いた友達の作成

次回はメモと友達のリレーションを作成したいと思います。
デプロイも残っていますね(HerokuとDB)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1