【Python】djangoで体調管理の日記アプリ作ってみた

はじめに

こんにちは。Yuinaです🌸

今日はPythonのフレームワークDjangoを使用して、

心の状態を可視化したり、落ち込んだ時の回復方法などを表示するアプリを作りました。

最後にソースコード全体を貼っておきますので、ぜひ参考にしてみてください。

全体のコードへジャンプ

よろしくお願いいたします!

【📕参考資料】

Python公式: venv — 仮想環境の作成

Django公式: はじめての Django アプリ作成

Homebrew公式: brew.sh

【🛠 開発環境】

  • OS: macOS (M3チップ)
  • 言語: Python 3.14
  • 仮想環境: venv
  • DB: SQLite
  • ツール管理: Homebrew (tree, Pythonなどのインストールに使用)

【ディレクトリ構成】


├── db.sqlite3
├── manage.py
├── my_guide
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_selfcheck_delete_conditionlog.py
│ │ ├── 0003_selfcheck_appetite_selfcheck_creativity_and_more.py
│ │ ├── 0004_remove_selfcheck_confidence_and_more.py
│ │ ├── 0005_selfcheck_note_activity_selfcheck_note_body_and_more.py
│ │ ├── 0006_alter_selfcheck_appetite_alter_selfcheck_bad_habit_and_more.py
│ │ ├── 0007_copingstrategy.py
│ │ └── __init__.py
│ ├── models.py
│ ├── static
│ │ └── admin
│ │ └── css
│ │ └── custom_admin.css
│ ├── templates
│ │ ├── admin
│ │ │ ├── base_site.html
│ │ │ └── css
│ │ │ └── custom_admin.css
│ │ └── my_guide
│ │ ├── detail.html
│ │ ├── index.html
│ │ ├── log_form.html
│ │ ├── selfcheck_form.html
│ │ ├── strategy_form.html
│ │ └── strategy_list.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── mysite
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py

ディレクトリ構成の確認コマンド

tree -I "__pycache__|staticfiles"

環境構築

仮想環境の作成と有効化

Bash

# プロジェクトフォルダへ移動
cd my_django_project

# 仮想環境(.venv)の作成
python3 -m venv .venv

# 有効化(Mac/Linux)
source .venv/bin/activate

Djangoのインストール

Bash

pip install --upgrade pip
pip install django

プロジェクトの初期化

Bash

# カレントディレクトリに展開するのがポイント
django-admin startproject mysite .

ポイント

ModuleNotFoundError : 仮想環境を 有効化せずに実行すると、Djangoが入っていないシステムのPythonを見てしまうので、有効化します。

コードの解説

admin.py

こちらのモジュールは、各モデルの管理・設定をします。
Djangoの管理画面(Admin)で、そのモデルをどう表示し、どう操作させるかを決めます。

@admin.register(SelfCheck) 
class SelfCheckAdmin(admin.ModelAdmin):
・・・

@admin.register(SelfCheck) というデコレーターでは、SelfCheckモデルを管理画面に紐付け(登録)しています。これによって、管理画面のメニューにモデル名が表示され、データの追加や編集ができるようになります

list_display = ('date', 'status_display') 

list_displayとありますが、こちらはDjangoの ModelAdmin があらかじめ持っている『設定用の枠組み(プロパティ)』です。そこに自分で表示したい項目名を指定することで、Djangoがモデルのデータからその値を引っ張ってきて、管理画面の表に並べてくれます。(‘date’, ‘status_display’) は日付や画面の状態をModelAdminクラスから自動的に探し出します。

list_display などの変数は、

@admin.register(CopingStrategy)
class CopingStrategyAdmin(admin.ModelAdmin):
list_display = ('get_type_display', 'get_category_display', 'content', 'interval')
list_filter = ('type', 'category')
search_fields = ('content',)

CopingStrategyAdminクラスのlist_display,list_filter,search_fields についても、ModelAdminクラスの中から自動的に探し出し、保持しています。

forms.py

こちらのモジュールでは、各モデルの表示内容を設定します。

 labels = {
# 1. 活動
'hobby_enjoyment': '最近、好きなこと楽しめてる?',
'social_activity': '予定、パンパンに詰め込みたくなってない?',
      ~~~
'bad_habit': '爪を噛んだり、貧乏ゆすりしちゃったりする?',
}

◾️SelfCheckForm クラス

日々のログを入力・登録するためのフォームを定義しています。ModelForm を継承することで、モデルの項目をそのまま入力欄として利用できます。

lavelsの辞書の中のキー(‘hobby_enjoyment’,’social_activity’など)は、details.htmlで対応するvalueが設定されています。

detail.html(詳細表示画面)で <span>趣味の楽しみ</span> と書いている部分は、HTMLに直接文字を書き込んでいる状態です。

detail.html

<div class="data-row"><span>趣味の楽しみ</span> <span>{{ log.get_hobby_enjoyment_display }}</span></div>

画面側で反映されているところ:

forms.py

for field, label in labels.items():
if field in self.fields:
self.fields[field].label = label

fieldとlabel(keyとvalue)をfor文で回して、内容をfields[field].labelへ格納します。

fields[field].labelは、detail.htmlの中の {{ field.label }} という部分で、

画面に表示される文字として使われています。

🐻‍❄️ forms.pyと detail.htmlの違い

detail.html(詳細表示画面)では、HTMLに直接文字を書く「静的な表示」をしていますが、フォームでは forms.py を通じて「動的な表示」を行っています。これにより、言葉を変えたい時は forms.py 一箇所を直すだけで全ての入力画面に反映されます。

detail.html

        {# --- 1. 活動 --- #}
<div class="category-box">
<div class="category-title">📦 1. かつどう</div>
{% for field in form %}
{% if field.name in "hobby_enjoyment,social_activity,exercise_will,spending,irresponsible_behavior,daily_habit,net_usage" %}
<div class="question-row">
<label class="question-label">{{ field.label }}</label>
<div class="stamp-group">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
<span class="memo-label">💬 メモ:</span>
{{ form.note_activity }}
</div>

◾️CopingStrategyFormクラス

心の状態別にどんな対応をすればいいのか、マニュアルを管理しています。

fieldsやlabelはstrategy_form.htmlで参照され、上記のように画面に表示されます。

strategy_form.html

        form p { margin-bottom: 25px; }

        label { 
            display: block; 
            font-weight: bold; 
            font-size: 1rem; 
            color: var(--pixel-brown); 
            margin-bottom: 10px; 
            border-left: 6px solid var(--pixel-pink);
            padding-left: 8px;
        }

fieldslabels「何を表示するか(中身)」 を決めるのに対し、widgets「どう表示するか(見た目と機能)」 を担当しています。

fields と labels(中身の決定): fields で入力させる項目を選び、labels で「どのカテゴリ?」「具体的な内容」といった日本語の見出しを設定しています。これらは strategy_form.html などで参照され、入力欄のタイトルとして表示されます。

widgets(見た目と機能の決定): widgets は、入力欄の「種類」と「デザイン」を指定します。

  • 種類: contentTextarea(複数行入力)にすることで、長文のメモを書きやすくしています。
  • デザイン: attrs(アトリビュート)を使って、入力欄に直接CSSを適用しています。例えば border-radius: 15px; を指定することで、「角が丸い優しいデザイン」を実現しています。

models.py

こちらのモジュールでは、システムの機能の中身をクラス別に管理しています。

◾️SelfCheckクラス

入力された日記のデータから、どの対応マニュアルを表示するか判断します。

まず、質問項目ごとにデータを呼び出し、管理します。

   # 1. 活動
hobby_enjoyment = models.IntegerField("趣味の楽しみ", choices=LEVELS, default=2, blank=True, null=True)
social_activity = models.IntegerField("社交性・予定の詰め込み", choices=LEVELS, default=2, blank=True, null=True)
exercise_will = models.IntegerField("運動への意欲", choices=LEVELS, default=2, blank=True, null=True)
〜〜〜

次にget_statusメソッドでスコアを計算します。

    def get_status(self):
def val(v): return v if v is not None else 2 # 未入力は「普通(2)」扱い

mania_points = [
val(self.social_activity), val(self.spending), val(self.talk_desire),
val(self.grandiosity), val(self.thinking_speed), val(self.sensory_sensitivity)
]
dep_points = [
val(self.hobby_enjoyment), val(self.exercise_will), val(self.daily_habit),
val(self.low_tension)
]

m_score = sum(mania_points)
d_score = sum(dep_points)

スコア計算時にNone(未入力)があってもエラーにならないよう、0として扱う処理を行います。

def val(v): return v if v is not None else 2

次に躁リストと鬱リストを作成し、各質問項目の変数に割り当てていきます。

 mania_points = [
val(self.social_activity), val(self.spending), val(self.talk_desire),
val(self.grandiosity), val(self.thinking_speed), val(self.sensory_sensitivity)
]
dep_points = [
val(self.hobby_enjoyment), val(self.exercise_will), val(self.daily_habit),
val(self.low_tension)
]

各リストに格納された質問項目の得点の合計を出します。

m_score = sum(mania_points)
d_score = sum(dep_points)

得点の合計によって表示する内容を決めます。

if m_score >= 16:
return {"label": "躁(危険)", "color": "#ff4d4d", "emoji": "🔥"}
elif m_score >= 14:
return {"label": "躁の前(注意)", "color": "#ff944d", "emoji": "⚠️"}
elif d_score <= 5:
return {"label": "うつ(危険)", "color": "#4d79ff", "emoji": "🌊"}
elif d_score <= 7:
return {"label": "うつ(注意)", "color": "#8daaff", "emoji": "☔"}
else:
return {"label": "安定エリア", "color": "#7bc67b", "emoji": "🌿"}

こんな感じです✨

◾️CopingStrategyクラス

心の状態別に対応マニュアルを作成します。

登録内容を選択できるように、TYPE_CHOICESやCATEGORY_CHOICESという変数を定義しておきます。

    TYPE_CHOICES = [
('mania_down', '躁を抑える対処'),
('mania_recovery', '躁からの回復サイン'),
('depression_up', '鬱から浮上する対処'),
('depression_recovery', '鬱からの回復サイン'),
('routine', '安定維持ルーティン'),
('phrase', '大切な口癖'),
]
    CATEGORY_CHOICES = [
('activity', '📦 活動'),
('sleep', '💤 睡眠'),
('meal', '🍴 食事・嗜好品'),
('commu', '💬 コミュニケーション'),
('mind', '🧠 気分や思考性'),
('body', '🧘 身体反応'),
]

最後に、状況に合わせた呼び出しができるように保持しておきます。

TYPE_CHOICESやCATEGORY_CHOICESなど、画面に反映させたい内容はここにすべて入れておきます。

マニュアル入力・編集画面のstrategy_form.htmlやマニュアル閲覧用画面のstrategy_list.htmlで参照されます。

    type = models.CharField(max_length=20, choices=TYPE_CHOICES)
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES)
content = models.TextField(verbose_name="内容")
interval = models.CharField(max_length=50, blank=True, help_text="毎日、週一、月一など")

views.py

このモジュールでは、動きのある処理の設定を行います。
(鬱っぽい時は鬱の攻略法を表示、安定している時はルーティンと口癖を表示)

models.pyから引き継いだ日付データです。”-“は降順並べるという意味です。

class IndexView(generic.ListView):
model = SelfCheck
template_name = 'my_guide/index.html'
context_object_name = 'checks'
ordering = ['-date']

IndexViewクラスでは、一覧表示用の画面を作ります。

HTML側で、データになんという名前をつけますか?という設定です。

HTMLの中で checks と呼ぶだけで、取得したデータの塊を扱えるようになります。

class SelfCheckCreateView(generic.CreateView):
    model = SelfCheck
    form_class = SelfCheckForm
    template_name = 'my_guide/log_form.html'
    success_url = reverse_lazy('my_guide:index')

urls.py

ユーザーがブラウザで入力したURLを見て、
どのviewを呼び出すかを振り分けるための指標です。

name=’xxx’の部分は、Naming URL patternsと言います。

リンクに名前をつけており、ブラウザでは写真のように表示されます。

例:name=’add’の場合


🐻‍❄️仕組み

  1. まずユーザーが、ブラウザ側で .../add/ にアクセスすると、以下のような処理が走ります。
  2. path('add/', ...) が、「/add/ というURLに来たら SelfCheckCreateView を呼び出せ」と指示します。

urls.py

path('add/', views.SelfCheckCreateView.as_view(), name='add'),

views.py

class SelfCheckCreateView(generic.CreateView):
model = SelfCheck
form_class = SelfCheckForm
template_name = 'my_guide/log_form.html'
success_url = reverse_lazy('my_guide:index')

SelfCheckCreateView は Djangoの CreateView を継承して作られています。この CreateView には、「モデルを元にフォームを作り、指定されたHTML(テンプレート)に流し込む」 というプログラムが最初から書き込まれています。

3.ビューが selfcheck_form.html というファイルを探し出し、そこに「入力欄」をガッチャンコして、最終的なHTMLとしてブラウザに返します。

まとめ

フロントエンド側の解説については今回は割愛しますが、

最後にソースコード全体を貼っておきますので、ぜひ参考にしてみてください。

Djangoを使ってみて改めて感じたのは、

フロントからバックエンド、そして管理画面までを一元管理できる圧倒的な安心感です。

ツールを作る際、modelからform、そしてcssまでを一つのフレームワークで

一貫してコントロールできるのは、開発者としてとても心強いと思いました。

今回登場したコード全体

admin.py

from django.contrib import admin
from .models import SelfCheck, CopingStrategy

# --- 1. SelfCheckの管理設定 ---
@admin.register(SelfCheck)
class SelfCheckAdmin(admin.ModelAdmin):
list_display = ('date', 'status_display')

def status_display(self, obj):
return obj.get_status()['label']

class Media:
css = {
'all': ('admin/css/custom_admin.css',)
}

# --- 2. CopingStrategyの管理設定 ---
@admin.register(CopingStrategy)
class CopingStrategyAdmin(admin.ModelAdmin):
list_display = ('get_type_display', 'get_category_display', 'content', 'interval')
list_filter = ('type', 'category')
search_fields = ('content',)

class Media:
css = {
'all': ('admin/css/custom_admin.css',)
}

apps.py

from django.apps import AppConfig


#Djangoプロジェクトにアプリを認識させる
class MyGuideConfig(AppConfig):
name = 'my_guide'

forms.py

from django import forms
from .models import CopingStrategy, SelfCheck

class SelfCheckForm(forms.ModelForm):
class Meta:
model = SelfCheck
fields = '__all__'

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
labels = {
# 1. 活動
'hobby_enjoyment': '最近、好きなこと楽しめてる?',
'social_activity': '予定、パンパンに詰め込みたくなってない?',
'exercise_will': '運動したい!って気持ちはどうかな?',
'spending': 'お買い物、ついつい手が伸びちゃう?',
'irresponsible_behavior': '「なんとかなるさ!」って大胆になってない?',
'daily_habit': 'お風呂や歯磨き、いつもの通りできてる?',
'net_usage': 'スマホやSNS、ずっと見ちゃってないかな?',
# 2. 睡眠
'sleep_amount': 'ねむねむ...睡眠時間は足りてる?',
'sleep_induction': 'お布団に入ってから、すぐ眠れてる?',
'mid_wake': '夜中や早朝に目が覚めちゃうことある?',
'daytime_sleepiness': 'お昼間、ぼーっとしたり眠くなったりする?',
# 3. 食事
'meal_frequency': 'ごはんはちゃんと食べられてる?',
'appetite': 'お腹のすき具合はどうかな?',
'indulgence': 'お酒や甘いもの、欲しくなっちゃう?',
# 4. コミュ
'talk_desire': '誰かとお話ししたい!って気分?',
'sns_post': 'SNSに何か書き込みたい気持ち、強いかな?',
'conflict_reaction': '誰かと意見が違うと、ムカッとしちゃう?',
'social_distance': '人との距離、グイグイ行きたくなってない?',
# 5. 気分・思考
'irritability': 'いつもより、ちょっとイライラしちゃう?',
'negative_thought': '自分や誰かを「ダメだな」って思っちゃう?',
'low_tension': 'なんだか、元気がでないな...って感じ?',
'creativity': '「名案!」「これやりたい!」が止まらない?',
'grandiosity': 'もしかして自分って天才かも!無敵!って思う?',
'restlessness_mind': '心がソワソワして落ち着かないかな?',
'mood_swing': '気分の波が激しいなって感じる?',
'thinking_speed': '頭の回転が速すぎて、止まらない感じ?',
# 6. 身体
'restlessness_body': '体がムズムズして、じっとしていられない?',
'sensory_sensitivity': '音や光が、いつもよりうるさく感じる?',
'bad_habit': '爪を噛んだり、貧乏ゆすりしちゃったりする?',
}
for field, label in labels.items():
if field in self.fields:
self.fields[field].label = label

class CopingStrategyForm(forms.ModelForm):
class Meta:
model = CopingStrategy
fields = ['type', 'category', 'content', 'interval']
labels = {
'type': 'どんな時のためのマニュアル?',
'category': 'どのカテゴリ?',
'content': '具体的な内容(対処法や回復サイン)',
'interval': '頻度の目安(毎日、疲れた時など)',
}
widgets = {
'content': forms.Textarea(attrs={'rows': 3, 'placeholder': '例:スマホを別の部屋に置いて寝る', 'style': 'border-radius: 15px; border: 1px solid #e2d5c3; padding: 10px;'}),
'type': forms.Select(attrs={'style': 'border-radius: 10px; border: 1px solid #e2d5c3;'}),
'category': forms.Select(attrs={'style': 'border-radius: 10px; border: 1px solid #e2d5c3;'}),
'interval': forms.TextInput(attrs={'placeholder': '例:毎日、週一', 'style': 'border-radius: 10px; border: 1px solid #e2d5c3;'}),
}

models.py

from django.db import models

class SelfCheck(models.Model):
LEVELS = [
(1, 'あんまり'),
(2, 'いつも通り'),
(3, 'かなり!'),
]

date = models.DateTimeField("診断日", auto_now_add=True)

# 1. 活動
hobby_enjoyment = models.IntegerField("趣味の楽しみ", choices=LEVELS, default=2, blank=True, null=True)
social_activity = models.IntegerField("社交性・予定の詰め込み", choices=LEVELS, default=2, blank=True, null=True)
exercise_will = models.IntegerField("運動への意欲", choices=LEVELS, default=2, blank=True, null=True)
spending = models.IntegerField("買い物・浪費", choices=LEVELS, default=2, blank=True, null=True)
irresponsible_behavior = models.IntegerField("無責任な行動", choices=LEVELS, default=1, blank=True, null=True)
daily_habit = models.IntegerField("習慣(入浴・服薬等)の維持", choices=LEVELS, default=2, blank=True, null=True)
net_usage = models.IntegerField("ネット・SNS閲覧時間", choices=LEVELS, default=2, blank=True, null=True)

# 2. 睡眠
sleep_amount = models.IntegerField("睡眠時間の増減", choices=LEVELS, default=2, blank=True, null=True)
sleep_induction = models.IntegerField("寝つきの良さ", choices=LEVELS, default=2, blank=True, null=True)
mid_wake = models.IntegerField("中途・早朝覚醒", choices=LEVELS, default=1, blank=True, null=True)
daytime_sleepiness = models.IntegerField("日中の眠気", choices=LEVELS, default=2, blank=True, null=True)

# 3. 食事・嗜好品
meal_frequency = models.IntegerField("食事・間食の回数", choices=LEVELS, default=2, blank=True, null=True)
appetite = models.IntegerField("食欲の増減", choices=LEVELS, default=2, blank=True, null=True)
indulgence = models.IntegerField("嗜好品(酒・タバコ等)の頻度", choices=LEVELS, default=2, blank=True, null=True)

# 4. コミュニケーション
talk_desire = models.IntegerField("話したい欲求", choices=LEVELS, default=2, blank=True, null=True)
sns_post = models.IntegerField("SNS投稿・コメント頻度", choices=LEVELS, default=2, blank=True, null=True)
conflict_reaction = models.IntegerField("意見相違への過敏さ", choices=LEVELS, default=2, blank=True, null=True)
social_distance = models.IntegerField("人との距離感の詰め", choices=LEVELS, default=2, blank=True, null=True)

# 5. 気分・思考
irritability = models.IntegerField("怒りっぽさ", choices=LEVELS, default=2, blank=True, null=True)
negative_thought = models.IntegerField("自他への否定", choices=LEVELS, default=2, blank=True, null=True)
low_tension = models.IntegerField("テンションの低さ", choices=LEVELS, default=2, blank=True, null=True)
creativity = models.IntegerField("創造性の増減", choices=LEVELS, default=2, blank=True, null=True)
grandiosity = models.IntegerField("有能感・万能感", choices=LEVELS, default=2, blank=True, null=True)
restlessness_mind = models.IntegerField("心の落ち着きなさ", choices=LEVELS, default=2, blank=True, null=True)
mood_swing = models.IntegerField("気分の波(高揚・落ち込み)", choices=LEVELS, default=2, blank=True, null=True)
thinking_speed = models.IntegerField("思考の速度", choices=LEVELS, default=2, blank=True, null=True)

# 6. 身体反応
restlessness_body = models.IntegerField("じっとしていられなさ", choices=LEVELS, default=2, blank=True, null=True)
sensory_sensitivity = models.IntegerField("光・音・匂いへの敏感さ", choices=LEVELS, default=2, blank=True, null=True)
bad_habit = models.IntegerField("癖(爪噛み・貧乏ゆすり等)", choices=LEVELS, default=2, blank=True, null=True)

note_activity = models.TextField("活動のメモ", blank=True, null=True)
note_sleep = models.TextField("睡眠のメモ", blank=True, null=True)
note_meal = models.TextField("食事・嗜好品のメモ", blank=True, null=True)
note_commu = models.TextField("コミュニケーションのメモ", blank=True, null=True)
note_mind = models.TextField("気分・思考のメモ", blank=True, null=True)
note_body = models.TextField("からだのメモ", blank=True, null=True)

def get_status(self):
def val(v): return v if v is not None else 2 # 未入力は「普通(2)」扱い

mania_points = [
val(self.social_activity), val(self.spending), val(self.talk_desire),
val(self.grandiosity), val(self.thinking_speed), val(self.sensory_sensitivity)
]
dep_points = [
val(self.hobby_enjoyment), val(self.exercise_will), val(self.daily_habit),
val(self.low_tension)
]

m_score = sum(mania_points)
d_score = sum(dep_points)

if m_score >= 16:
return {"label": "躁(危険)", "color": "#ff4d4d", "emoji": "🔥"}
elif m_score >= 14:
return {"label": "躁の前(注意)", "color": "#ff944d", "emoji": "⚠️"}
elif d_score <= 5:
return {"label": "うつ(危険)", "color": "#4d79ff", "emoji": "🌊"}
elif d_score <= 7:
return {"label": "うつ(注意)", "color": "#8daaff", "emoji": "☔"}
else:
return {"label": "安定エリア", "color": "#7bc67b", "emoji": "🌿"}

class CopingStrategy(models.Model):
TYPE_CHOICES = [
('mania_down', '躁を抑える対処'),
('mania_recovery', '躁からの回復サイン'),
('depression_up', '鬱から浮上する対処'),
('depression_recovery', '鬱からの回復サイン'),
('routine', '安定維持ルーティン'),
('phrase', '大切な口癖'),
]

CATEGORY_CHOICES = [
('activity', '📦 活動'),
('sleep', '💤 睡眠'),
('meal', '🍴 食事・嗜好品'),
('commu', '💬 コミュニケーション'),
('mind', '🧠 気分や思考性'),
('body', '🧘 身体反応'),
]

type = models.CharField(max_length=20, choices=TYPE_CHOICES)
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES)
content = models.TextField(verbose_name="内容")
interval = models.CharField(max_length=50, blank=True, help_text="毎日、週一、月一など")

views.py

from django.views import generic
from django.urls import reverse_lazy
from .models import CopingStrategy, SelfCheck
from .forms import CopingStrategyForm, SelfCheckForm

class IndexView(generic.ListView):
model = SelfCheck
template_name = 'my_guide/index.html'
context_object_name = 'checks'
ordering = ['-date'] # models.pyからきた日付データ(-:降順)

class SelfCheckCreateView(generic.CreateView):
model = SelfCheck
form_class = SelfCheckForm
template_name = 'my_guide/log_form.html'
# データの保存(作成)が成功した後に、自動的に移動する
# lazy:怠惰、ちょっと遅れてリダイレクトする
success_url = reverse_lazy('my_guide:index')

# DetailView:Djangoが用意してくれている「データのリスト(一覧)を表示するための便利セット」
class SelfCheckDetailView(generic.DetailView):
model = SelfCheck
template_name = 'my_guide/detail.html'
context_object_name = 'log'

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 現在のステータス(躁・うつ・安定)を取得
# models.pyのpk="番号"を参照して取得する
# hobby_enjoyment = models.IntegerField("趣味の楽しみ", choices=LEVELS, default=2, blank=True, null=True)
status = self.object.get_status()
label = status['label']

# 状態に合わせて表示する対処法をフィルタリング
strategies = CopingStrategy.objects.all()

if "躁" in label:
# 躁の時は抑える対処と回復サイン
context['suggested_strategies'] = strategies.filter(type__in=['mania_down', 'mania_recovery'])
context['advice_title'] = "🔥 躁を落ち着かせるヒント"
elif "うつ" in label:
# うつの時は浮上する対処と回復サイン
context['suggested_strategies'] = strategies.filter(type__in=['depression_up', 'depression_recovery'])
context['advice_title'] = "🌊 少しずつ元気を取り戻すヒント"
else:
# 安定している時はルーティンと口癖
context['suggested_strategies'] = strategies.filter(type__in=['routine', 'phrase'])
context['advice_title'] = "🌿 安定をキープするヒント"

return context

class SelfCheckDeleteView(generic.DeleteView):
model = SelfCheck
success_url = reverse_lazy('my_guide:index')

class StrategyListView(generic.ListView):
model = CopingStrategy
template_name = 'my_guide/strategy_list.html'
context_object_name = 'strategies'

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 画面で見やすいようにタイプ別に分けて渡す
context['mania_down'] = CopingStrategy.objects.filter(type='mania_down')
context['mania_recovery'] = CopingStrategy.objects.filter(type='mania_recovery')
context['depression_up'] = CopingStrategy.objects.filter(type='depression_up')
context['depression_recovery'] = CopingStrategy.objects.filter(type='depression_recovery')
context['routine'] = CopingStrategy.objects.filter(type='routine')
context['phrase'] = CopingStrategy.objects.filter(type='phrase')
return context

class LogCreateView(generic.CreateView):
model = SelfCheck
form_class = SelfCheckForm
template_name = 'my_guide/log_form.html'

class StrategyCreateView(generic.CreateView):
model = CopingStrategy
form_class = CopingStrategyForm
template_name = 'my_guide/strategy_form.html'
success_url = reverse_lazy('my_guide:strategy_list')

class StrategyDeleteView(generic.DeleteView):
model = CopingStrategy
form_class = SelfCheckForm
success_url = reverse_lazy('my_guide:strategy_list')

class StrategyEditView(generic.UpdateView):
model = CopingStrategy
form_class = CopingStrategyForm
template_name = 'my_guide/strategy_form.html'
success_url = reverse_lazy('my_guide:strategy_list')

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['edit_mode'] = True
return context

urls.py

from django.urls import path
from . import views

app_name = 'my_guide'

urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('add/', views.SelfCheckCreateView.as_view(), name='add'),
path('<int:pk>/', views.SelfCheckDetailView.as_view(), name='detail'),
path('<int:pk>/delete/', views.SelfCheckDeleteView.as_view(), name='delete'),
path('strategy/', views.StrategyListView.as_view(), name='strategy_list'),
path('strategy/add/', views.StrategyCreateView.as_view(), name='strategy_add'),
path('strategy/<int:pk>/delete/', views.StrategyDeleteView.as_view(), name='strategy_delete'),
path('strategy/<int:pk>/edit/', views.StrategyEditView.as_view(), name='strategy_edit'),
]

base_site.html

{% extends "admin/base.html" %}
{% load static %}

{% block extrastyle %}
{{ block.super }}
<style>
/* ドット絵フォントの読み込み */
@font-face {
font-family: 'MisakiGothic';
src: url('https://cdn.leafscape.be/misaki/misaki_gothic_web.woff2') format('woff2');
}

/* 1. 全体のデザインをピクセルピンクに変更 */
body {
font-family: 'MisakiGothic', sans-serif !important;
background-color: #FFDDEE !important; /* 薄ピンク背景 */
color: #442233 !important;
image-rendering: pixelated;
}

#header {
background: #FF88BB !important; /* 中間ピンク */
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
border-bottom: 4px solid #442233 !important; /* ドット絵風の太線 */
}

#branding h1 a {
color: white !important;
text-shadow: 2px 2px 0px #442233; /* 文字のカクカク影 */
}

/* 2. デフォルト要素の非表示(継続) */
body.dashboard #content > h1,
body.dashboard #content-main,
body.dashboard #recent-actions-module,
body.dashboard .sidebar {
display: none !important;
}

/* 3. 中央メニュー:ピクセルボックス化 */
body.dashboard .custom-dashboard-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 70vh;
gap: 25px;
}

.big-menu-btn {
background: white !important;
color: #442233 !important;
text-decoration: none;
width: 300px;
padding: 30px 15px;
/* 角を丸くせず、直角か少しの丸みに留めるのがドット絵風 */
border: 4px solid #442233 !important;
font-size: 1.4rem;
font-weight: bold;
text-align: center;
/* ドット絵風のベタ塗り影 */
box-shadow: 8px 8px 0px #FF88BB !important;
transition: all 0.1s;
}

.big-menu-btn:hover {
transform: translate(2px, 2px);
box-shadow: 4px 4px 0px #FF88BB !important;
background-color: #FFF0F5 !important;
}

.big-menu-btn span {
display: block;
font-size: 0.8rem;
margin-top: 10px;
color: #AA6688;
}

/* ヘッダーの小ボタンもピクセル風に */
.nav-mini-btn {
background: white !important;
color: #FF88BB !important;
padding: 5px 12px;
border: 2px solid #442233 !important;
text-decoration: none;
font-size: 0.7rem;
font-weight: bold;
box-shadow: 3px 3px 0px #442233;
}
</style>
{% endblock %}

{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">🎮 わたしのトリセツ</a></h1>
<div style="display: flex; gap: 10px; margin-left: 20px;">
<a href="{% url 'my_guide:index' %}" class="nav-mini-btn">🏠 ログ</a>
<a href="{% url 'my_guide:strategy_list' %}" class="nav-mini-btn">📖 マニュアル</a>
</div>
{% endblock %}

{% block content %}
<div class="custom-dashboard-area">
<a href="{% url 'my_guide:index' %}" class="big-menu-btn">
💖 ログ一覧・入力
<span>日々の冒険をきろくする</span>
</a>
<a href="{% url 'my_guide:strategy_list' %}" class="big-menu-btn">
🧸 操縦マニュアル
<span>最強のぶきをチェックする</span>
</a>
</div>
{{ block.super }}
{% endblock %}

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>My Self-Care Guide 🎮</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Zen+Maru+Gothic:wght@400;700&display=swap" rel="stylesheet">
<style>
/* ドット絵フォントの読み込み */
@font-face {
font-family: 'MisakiGothic';
src: url('https://cdn.leafscape.be/misaki/misaki_gothic_web.woff2') format('woff2');
}

:root {
--bg-color: #FFDDEE; /* 背景:パステルピンク */
--pixel-pink: #FF88BB; /* アクセント:ベリーピンク */
--pixel-brown: #442233; /* 文字・枠線:チョコブラウン */
--card-bg: #FFFFFF; /* カード背景:白 */
--sub-text: #AA6688; /* 補助文字:くすみピンク */
}

body {
font-family: 'MisakiGothic', sans-serif;
background: var(--bg-color);
color: var(--pixel-brown);
padding: 20px 0 140px;
margin: 0;
line-height: 1.5;
image-rendering: pixelated;
}

.container { max-width: 500px; margin: 0 auto; padding: 0 20px; }

header { text-align: center; margin-bottom: 30px; }
h1 {
font-size: 2rem;
color: var(--pixel-brown);
margin-bottom: 5px;
text-shadow: 2px 2px 0 var(--pixel-pink);
}
.subtitle { font-size: 0.8rem; color: var(--sub-text); font-weight: bold; }

/* ナビゲーション:ドット絵ボタン風 */
.nav-links {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.nav-item {
font-size: 0.8rem;
text-decoration: none;
color: var(--pixel-brown);
background: white;
padding: 8px 15px;
border: 3px solid var(--pixel-brown);
box-shadow: 3px 3px 0 var(--pixel-pink);
transition: 0.1s;
}
.nav-item:hover { transform: translate(1px, 1px); box-shadow: 2px 2px 0 var(--pixel-pink); }

/* ログカード:セーブスロット風 */
.log-card {
background: var(--card-bg);
border: 4px solid var(--pixel-brown);
padding: 15px;
margin-bottom: 20px;
box-shadow: 6px 6px 0 var(--pixel-pink);
display: block;
text-decoration: none;
color: inherit;
position: relative;
}
.log-card:active { transform: translate(2px, 2px); box-shadow: 4px 4px 0 var(--pixel-pink); }

.log-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.date-box { display: flex; flex-direction: column; }
.date-main { font-weight: bold; font-size: 1.2rem; }
.date-sub { font-size: 0.7rem; color: var(--sub-text); }

.status-badge {
font-size: 0.8rem;
font-weight: bold;
padding: 4px 10px;
border: 2px solid var(--pixel-brown);
color: white;
text-shadow: 1px 1px 0 var(--pixel-brown);
}

.memo-preview {
font-size: 0.8rem;
color: var(--pixel-brown);
background: #FFF0F5;
padding: 8px;
border: 2px dashed var(--pixel-pink);
margin-top: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

/* 削除ボタン:ファミコンの「×」風 */
.delete-btn {
position: absolute;
top: -10px;
right: -10px;
background: #EEE;
color: #999;
border: 3px solid var(--pixel-brown);
width: 28px;
height: 28px;
cursor: pointer;
font-family: 'MisakiGothic';
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.delete-btn:hover { background: #FFADAD; color: white; }

/* +ボタン:コマンドボタン風 */
.add-btn {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: var(--pixel-pink);
color: white;
padding: 15px 30px;
border: 4px solid var(--pixel-brown);
font-weight: bold;
text-decoration: none;
box-shadow: 0 6px 0 var(--pixel-brown);
display: flex;
align-items: center;
gap: 10px;
z-index: 100;
font-size: 1.1rem;
text-shadow: 2px 2px 0 var(--pixel-brown);
}
.add-btn:active { transform: translateX(-50%) translateY(4px); box-shadow: 0 2px 0 var(--pixel-brown); }

.empty-state {
text-align: center;
margin-top: 80px;
color: var(--sub-text);
}
</style>
</head>
<body>

<div class="container">
<header>
<h1>🎮 じぶんのログ</h1>
<p class="subtitle">だい1しょう: hit end run との契約🚩</p>
</header>

<div class="nav-links">
<a href="{% url 'my_guide:strategy_list' %}" class="nav-item">🧸 ぶきをみる</a>
<a href="/admin/" class="nav-item">⚙️ せってい</a>
</div>

{% if checks %}
{% for log in checks %}
<div style="position: relative;">
<a href="{% url 'my_guide:detail' log.pk %}" class="log-card" style="border-top: 8px solid {{ log.get_status.color }};">
<div class="log-header">
<div class="date-box">
<span class="date-main">{{ log.date|date:"m/d" }}</span>
<span class="date-sub">{{ log.date|date:"l" }} ({{ log.date|date:"H:i" }})</span>
</div>
<span class="status-badge" style="background: {{ log.get_status.color }};">
{{ log.get_status.emoji }} {{ log.get_status.label }}
</span>
</div>

{% if log.note_activity or log.note_mind or log.note_sleep or log.note_meal or log.note_commu or log.note_body %}
<div class="memo-preview">
💬 {{ log.note_activity|default:log.note_mind|default:log.note_sleep|default:log.note_meal|default:log.note_commu|default:log.note_body }}
</div>
{% endif %}

<form action="{% url 'my_guide:delete' log.pk %}" method="post" onsubmit="return confirm('この記録をけ去ってもいいですか?');">
{% csrf_token %}
<button type="submit" class="delete-btn" onclick="event.stopPropagation();">×</button>
</form>
</a>
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<p style="font-size: 4rem; margin-bottom: 10px;">👾</p>
<p>きろくが ありません</p>
</div>
{% endif %}

<a href="{% url 'my_guide:add' %}" class="add-btn">
<span>+</span> きろくをつける
</a>
</div>

</body>
</html>

detail.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>きろく詳細 🎮</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Zen+Maru+Gothic:wght@400;700&display=swap" rel="stylesheet">
<style>
/* ドット絵フォントの読み込み */
@font-face {
font-family: 'MisakiGothic';
src: url('https://cdn.leafscape.be/misaki/misaki_gothic_web.woff2') format('woff2');
}

:root {
--bg-color: #FFDDEE; /* 背景:パステルピンク */
--pixel-pink: #FF88BB; /* アクセント:ベリーピンク */
--pixel-brown: #442233; /* 文字・枠線:チョコブラウン */
--card-bg: #FFFFFF; /* カード背景:白 */
--sub-bg: #FFF0F5; /* 薄ピンク(メモ用) */
}

body {
font-family: 'MisakiGothic', sans-serif;
background: var(--bg-color);
color: var(--pixel-brown);
padding: 20px;
margin: 0;
line-height: 1.6;
image-rendering: pixelated;
}

.container { max-width: 550px; margin: 0 auto 50px; }

.back-link {
text-decoration: none;
color: #AA6688;
font-weight: bold;
display: block;
margin-bottom: 20px;
}

/* ヘッダー:ステータスウィンドウ風 */
.header-card {
background: var(--card-bg);
border: 4px solid var(--pixel-brown);
padding: 20px;
text-align: center;
box-shadow: 6px 6px 0px var(--pixel-pink);
margin-bottom: 25px;
}

.status-big {
font-size: 2rem;
font-weight: bold;
margin: 10px 0;
text-shadow: 2px 2px 0 var(--pixel-pink);
}

/* 詳細カード:データウィンドウ */
.detail-card {
background: var(--card-bg);
border: 4px solid var(--pixel-brown);
padding: 20px;
margin-bottom: 20px;
box-shadow: 4px 4px 0px var(--pixel-pink);
}

.section-title {
font-weight: bold;
color: white;
background: var(--pixel-brown);
display: inline-block;
padding: 2px 10px;
margin-bottom: 15px;
font-size: 1.1rem;
}

.data-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 2px dashed var(--bg-color);
font-size: 0.9rem;
}
.data-row span:last-child { font-weight: bold; color: var(--pixel-pink); }

.memo-box {
background: var(--sub-bg);
padding: 12px;
border: 2px solid var(--pixel-pink);
margin-top: 15px;
font-size: 0.85rem;
}

/* アドバイス:特別なイベントウィンドウ風 */
.advice-card {
border: 4px solid var(--pixel-pink);
background: #FFFDF9;
box-shadow: 6px 6px 0 var(--pixel-brown);
}

.strategy-item {
font-size: 0.95rem;
padding: 8px 0 8px 12px;
border-left: 5px solid var(--pixel-pink);
margin-bottom: 8px;
background: white;
}

.delete-btn {
display: block;
width: 100%;
text-align: center;
margin-top: 40px;
color: #AA6688;
background: none;
border: none;
cursor: pointer;
font-family: 'MisakiGothic';
font-size: 0.8rem;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<a href="{% url 'my_guide:index' %}" class="back-link">◀︎ 戻る</a>

<div class="header-card" style="border-top: 10px solid {{ log.get_status.color }};">
<div style="font-size: 0.8rem; color: #AA6688;">きろく:{{ log.date|date:"Y/m/d H:i" }}</div>
<div class="status-big">
{{ log.get_status.emoji }} {{ log.get_status.label }}
</div>
</div>

<div class="detail-card">
<div class="section-title">📦 1. かつどう</div>
<div class="data-row"><span>趣味の楽しみ</span> <span>{{ log.get_hobby_enjoyment_display }}</span></div>
<div class="data-row"><span>社交性</span> <span>{{ log.get_social_activity_display }}</span></div>
<div class="data-row"><span>運動への意欲</span> <span>{{ log.get_exercise_will_display }}</span></div>
<div class="data-row"><span>買い物・浪費</span> <span>{{ log.get_spending_display }}</span></div>
<div class="data-row"><span>大胆な行動</span> <span>{{ log.get_irresponsible_behavior_display }}</span></div>
<div class="data-row"><span>日常の習慣</span> <span>{{ log.get_daily_habit_display }}</span></div>
<div class="data-row"><span>ネット・SNS</span> <span>{{ log.get_net_usage_display }}</span></div>
{% if log.note_activity %}<div class="memo-box">💬 {{ log.note_activity }}</div>{% endif %}
</div>

<div class="detail-card">
<div class="section-title">💤 2. すいみん</div>
<div class="data-row"><span>睡眠時間</span> <span>{{ log.get_sleep_amount_display }}</span></div>
<div class="data-row"><span>寝つき</span> <span>{{ log.get_sleep_induction_display }}</span></div>
<div class="data-row"><span>中途覚醒</span> <span>{{ log.get_mid_wake_display }}</span></div>
<div class="data-row"><span>日中の眠気</span> <span>{{ log.get_daytime_sleepiness_display }}</span></div>
{% if log.note_sleep %}<div class="memo-box">💬 {{ log.note_sleep }}</div>{% endif %}
</div>

<div class="detail-card">
<div class="section-title">🍴 3. しょくじ</div>
<div class="data-row"><span>食事回数</span> <span>{{ log.get_meal_frequency_display }}</span></div>
<div class="data-row"><span>食欲</span> <span>{{ log.get_appetite_display }}</span></div>
<div class="data-row"><span>嗜好品</span> <span>{{ log.get_indulgence_display }}</span></div>
{% if log.note_meal %}<div class="memo-box">💬 {{ log.note_meal }}</div>{% endif %}
</div>

<div class="detail-card advice-card">
<div class="section-title" style="background: var(--pixel-pink);">💡 攻略のヒント</div>

{% if suggested_strategies %}
{% regroup suggested_strategies by get_category_display as category_list %}
{% for category in category_list %}
<div style="margin-bottom: 15px;">
<div style="font-size: 0.8rem; color: var(--pixel-pink); margin-bottom: 5px; font-weight: bold;">
▼ {{ category.grouper }}
</div>
{% for s in category.list %}
<div class="strategy-item">
<span style="font-weight: bold;">{{ s.content }}</span>
{% if s.interval %}<span style="color: #AA6688; font-size: 0.7rem;"> ({{ s.interval }})</span>{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
{% else %}
<p style="font-size: 0.85rem; color: #AA6688; text-align: center;">まだ ヒントが ありません。<br>じゅもんを 登録しよう!</p>
{% endif %}
</div>

<form action="{% url 'my_guide:delete' log.pk %}" method="post" onsubmit="return confirm('この記録を け去っても いいですか?')">
{% csrf_token %}
<button type="submit" class="delete-btn">▶︎ この記録を 消去する</button>
</form>

</div>
</body>
</html>

log_form.html

{% extends "admin/base.html" %}
{% block content %}
<style>
/* ドット絵フォントの読み込み */
@font-face {
font-family: 'MisakiGothic';
src: url('https://cdn.leafscape.be/misaki/misaki_gothic_web.woff2') format('woff2');
}

:root {
--bg-color: #FFDDEE;
--pixel-pink: #FF88BB;
--pixel-brown: #442233;
--card-bg: #FFFFFF;
--sub-bg: #FFF0F5;
}

body {
background: var(--bg-color) !important;
font-family: 'MisakiGothic', sans-serif !important;
color: var(--pixel-brown) !important;
image-rendering: pixelated;
}

.form-container {
max-width: 600px;
margin: 0 auto 80px;
background: var(--card-bg);
padding: 25px;
border: 4px solid var(--pixel-brown);
box-shadow: 10px 10px 0px var(--pixel-pink);
}

.category-box {
border: 3px solid var(--pixel-brown);
padding: 20px;
margin-bottom: 40px;
background: var(--sub-bg);
position: relative;
}

.category-title {
font-weight: bold;
color: white;
background: var(--pixel-brown);
display: inline-block;
padding: 5px 15px;
margin-top: -35px;
margin-left: -5px;
margin-bottom: 20px;
font-size: 1.1rem;
border: 2px solid var(--pixel-brown);
box-shadow: 3px 3px 0 var(--pixel-pink);
}

.question-row { margin-bottom: 30px; }
.question-label {
display: block;
margin-bottom: 15px;
font-size: 1rem;
color: var(--pixel-brown);
border-left: 6px solid var(--pixel-pink);
padding-left: 10px;
}

/* スタンプ(ラジオボタン)のピクセル化 */
.stamp-group ul {
display: flex;
list-style: none;
padding: 0;
margin: 0;
gap: 8px;
flex-wrap: wrap;
}
.stamp-group input[type="radio"] { display: none; }
.stamp-group label {
min-width: 80px;
height: 40px;
border: 3px solid var(--pixel-brown);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 0.8rem;
background: white;
color: var(--pixel-brown);
transition: 0.1s;
}
.stamp-group input[type="radio"]:checked + label {
background: var(--pixel-pink);
color: white;
box-shadow: inset 3px 3px 0px rgba(0,0,0,0.1);
text-shadow: 1px 1px 0 var(--pixel-brown);
}

textarea {
width: 100%;
border: 3px solid var(--pixel-brown);
padding: 10px;
font-family: 'MisakiGothic', sans-serif;
box-sizing: border-box;
background: white;
font-size: 1rem;
}

.memo-label {
font-size: 0.9rem;
color: var(--pixel-brown);
margin: 20px 0 10px;
display: block;
font-weight: bold;
}

/* 送信ボタン:コマンド選択風 */
.submit-btn {
width: 100%;
padding: 25px;
background: var(--pixel-pink);
color: white;
border: 4px solid var(--pixel-brown);
font-size: 1.4rem;
font-weight: bold;
cursor: pointer;
margin-top: 30px;
box-shadow: 0 8px 0 var(--pixel-brown);
text-shadow: 2px 2px 0 var(--pixel-brown);
font-family: 'MisakiGothic', sans-serif;
}
.submit-btn:active {
transform: translateY(6px);
box-shadow: 0 2px 0 var(--pixel-brown);
}
</style>

<div class="form-container">
<h2 style="text-align: center; color: var(--pixel-brown); margin-bottom: 40px; text-shadow: 2px 2px 0 var(--pixel-pink);">🎮 きょうの ステータス</h2>
<form method="post">
{% csrf_token %}

{# --- 1. 活動 --- #}
<div class="category-box">
<div class="category-title">📦 1. かつどう</div>
{% for field in form %}
{% if field.name in "hobby_enjoyment,social_activity,exercise_will,spending,irresponsible_behavior,daily_habit,net_usage" %}
<div class="question-row">
<label class="question-label">{{ field.label }}</label>
<div class="stamp-group">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
<span class="memo-label">💬 メモ:</span>
{{ form.note_activity }}
</div>

{# --- 2. 睡眠 --- #}
<div class="category-box">
<div class="category-title">💤 2. すいみん</div>
{% for field in form %}
{% if field.name in "sleep_amount,sleep_induction,mid_wake,daytime_sleepiness" %}
<div class="question-row">
<label class="question-label">{{ field.label }}</label>
<div class="stamp-group">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
<span class="memo-label">💬 メモ:</span>
{{ form.note_sleep }}
</div>

{# --- 3. 食事・嗜好品 --- #}
<div class="category-box">
<div class="category-title">🍴 3. しょくじ</div>
{% for field in form %}
{% if field.name in "meal_frequency,appetite,indulgence" %}
<div class="question-row">
<label class="question-label">{{ field.label }}</label>
<div class="stamp-group">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
<span class="memo-label">💬 メモ:</span>
{{ form.note_meal }}
</div>

{# --- 4. 人間関係 --- #}
<div class="category-box">
<div class="category-title">💬 4. コミュニケーション</div>
{% for field in form %}
{% if field.name in "talk_desire,sns_post,conflict_reaction,social_distance" %}
<div class="question-row">
<label class="question-label">{{ field.label }}</label>
<div class="stamp-group">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
<span class="memo-label">💬 メモ:</span>
{{ form.note_commu }}
</div>

{# --- 5. 思考・気分 --- #}
<div class="category-box">
<div class="category-title">🧠 5. しこう・きぶん</div>
{% for field in form %}
{% if field.name in "irritability,negative_thought,low_tension,creativity,grandiosity,restlessness_mind,mood_swing,thinking_speed" %}
<div class="question-row">
<label class="question-label">{{ field.label }}</label>
<div class="stamp-group">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
<span class="memo-label">💬 メモ:</span>
{{ form.note_mind }}
</div>

{# --- 6. 身体 --- #}
<div class="category-box">
<div class="category-title">🧘 6. からだ</div>
{% for field in form %}
{% if field.name in "restlessness_body,sensory_sensitivity,bad_habit" %}
<div class="question-row">
<label class="question-label">{{ field.label }}</label>
<div class="stamp-group">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
<span class="memo-label">💬 メモ:</span>
{{ form.note_body }}
</div>

<button type="submit" class="submit-btn">
▶ ぼうけんを きろくする
</button>
</form>
<p style="text-align:center; margin-top:30px;">
<a href="{% url 'my_guide:index' %}" style="color:var(--sub-text); text-decoration:none; font-weight:bold;">◀ もどる</a>
</p>
</div>
{% endblock %}

selfcheck.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>体調を記録する 🌿</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Zen+Maru+Gothic:wght@400;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Zen Maru Gothic', sans-serif; background: #f9f7f2; color: #7d736a; padding: 20px; }
.container { max-width: 500px; margin: 0 auto; background: white; padding: 25px; border-radius: 20px; box-shadow: 4px 4px 0px #e2d5c3; }
h1 { font-size: 1.2rem; text-align: center; margin-bottom: 20px; }
form p { margin-bottom: 15px; }
input, select, textarea { width: 100%; padding: 10px; border: 1px solid #e2d5c3; border-radius: 10px; box-sizing: border-box; }
.save-btn { background: #d4c4b0; color: white; border: none; width: 100%; padding: 15px; border-radius: 50px; font-size: 1rem; cursor: pointer; margin-top: 20px; }
</style>
</head>
<body>
<div class="container">
<h1>📝 今日のととのえログ</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save-btn">この内容で記録する</button>
</form>
<p style="text-align: center; margin-top: 20px;"><a href="{% url 'my_guide:index' %}" style="color: #b0a496;">もどる</a></p>
</div>
</body>
</html>

strategy_form.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>マニュアル追加 🎮</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Zen+Maru+Gothic:wght@400;700&display=swap" rel="stylesheet">
<style>
/* ドット絵フォントの読み込み */
@font-face {
font-family: 'MisakiGothic';
src: url('https://cdn.leafscape.be/misaki/misaki_gothic_web.woff2') format('woff2');
}

:root {
--bg-color: #FFDDEE; /* 背景:パステルピンク */
--pixel-pink: #FF88BB; /* アクセント:ベリーピンク */
--pixel-brown: #442233; /* 文字・枠線:チョコブラウン */
--card-bg: #FFFFFF; /* カード背景:白 */
--sub-text: #AA6688; /* 補助文字:くすみピンク */
}

body {
font-family: 'MisakiGothic', sans-serif;
background: var(--bg-color);
color: var(--pixel-brown);
padding: 20px;
margin: 0;
image-rendering: pixelated;
}

.container { max-width: 500px; margin: 0 auto; }

.card {
background: var(--card-bg);
border: 4px solid var(--pixel-brown);
padding: 25px;
box-shadow: 8px 8px 0px var(--pixel-pink);
}

h1 {
font-size: 1.5rem;
text-align: center;
margin-bottom: 25px;
text-shadow: 2px 2px 0 var(--pixel-pink);
}

/* フォームのラベルと入力欄をゲーム風に */
form p { margin-bottom: 25px; }

label {
display: block;
font-weight: bold;
font-size: 1rem;
color: var(--pixel-brown);
margin-bottom: 10px;
border-left: 6px solid var(--pixel-pink);
padding-left: 8px;
}

input, select, textarea {
width: 100%;
box-sizing: border-box;
font-family: 'MisakiGothic', sans-serif;
font-size: 1.1rem;
border: 3px solid var(--pixel-brown);
background: #FFFDF9;
padding: 12px;
outline: none;
}

input:focus, select:focus, textarea:focus {
background: #FFF0F5;
border-color: var(--pixel-pink);
}

/* 保存ボタン:決定コマンド風 */
.btn-submit {
background: var(--pixel-pink);
color: white;
border: 4px solid var(--pixel-brown);
padding: 18px;
width: 100%;
font-weight: bold;
font-size: 1.2rem;
cursor: pointer;
margin-top: 15px;
box-shadow: 0 6px 0 var(--pixel-brown);
text-shadow: 2px 2px 0 var(--pixel-brown);
font-family: 'MisakiGothic', sans-serif;
}

.btn-submit:active {
transform: translateY(4px);
box-shadow: 0 2px 0 var(--pixel-brown);
}

.back-link {
display: block;
text-align: center;
margin-top: 30px;
color: var(--sub-text);
text-decoration: none;
font-size: 1rem;
font-weight: bold;
}
.back-link:hover { color: var(--pixel-pink); }
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>🧸 ぶき・ぼうぐ登録</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn-submit">▶︎ データを セーブする</button>
</form>
</div>
<a href="{% url 'my_guide:strategy_list' %}" class="back-link">◀︎ 戻る</a>
</div>

</body>
</html>

strategy_list.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>わたしの操縦マニュアル 🎮</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Zen+Maru+Gothic:wght@400;700&display=swap" rel="stylesheet">
<style>
/* ドット絵フォントの読み込み */
@font-face {
font-family: 'MisakiGothic';
src: url('https://cdn.leafscape.be/misaki/misaki_gothic_web.woff2') format('woff2');
}

:root {
--bg-color: #FFDDEE; /* 背景:パステルピンク */
--pixel-pink: #FF88BB; /* アクセント:ベリーピンク */
--pixel-brown: #442233; /* 文字・枠線:チョコブラウン */
--card-bg: #FFFFFF; /* カード背景:白 */
--sub-text: #AA6688; /* 補助文字:くすみピンク */
}

body {
font-family: 'MisakiGothic', sans-serif;
background: var(--bg-color);
color: var(--pixel-brown);
padding: 20px;
margin: 0;
image-rendering: pixelated;
}

.container { max-width: 500px; margin: 0 auto; }

.back-link {
text-decoration: none;
color: var(--sub-text);
display: block;
margin-bottom: 20px;
font-weight: bold;
}

h1 {
font-size: 1.8rem;
text-align: center;
margin-bottom: 25px;
text-shadow: 2px 2px 0 var(--pixel-pink);
}

/* 追加ボタン:コマンド風 */
.add-btn {
background: var(--pixel-pink);
color: white;
text-decoration: none;
padding: 15px;
border: 4px solid var(--pixel-brown);
display: block;
text-align: center;
margin-bottom: 30px;
font-weight: bold;
box-shadow: 0 5px 0px var(--pixel-brown);
text-shadow: 2px 2px 0 var(--pixel-brown);
}
.add-btn:active {
transform: translateY(3px);
box-shadow: 0 2px 0px var(--pixel-brown);
}

/* セクションカード:カテゴリーごとの枠 */
.section-card {
background: var(--card-bg);
border: 4px solid var(--pixel-brown);
padding: 20px;
margin-bottom: 25px;
box-shadow: 6px 6px 0px var(--pixel-pink);
}

.type-title {
font-weight: bold;
font-size: 1.2rem;
color: var(--pixel-pink);
border-bottom: 4px solid var(--pixel-brown);
margin-bottom: 20px;
padding-bottom: 5px;
text-shadow: 1px 1px 0 var(--pixel-brown);
}

/* 各アイテム:アイテムスロット風 */
.strategy-item {
position: relative;
margin-bottom: 15px;
padding: 12px;
background: #FFFDF9;
border: 2px solid var(--pixel-brown);
}

.category-badge {
font-size: 0.7rem;
background: var(--pixel-pink);
color: white;
padding: 2px 8px;
border: 2px solid var(--pixel-brown);
display: inline-block;
margin-bottom: 5px;
}

.item-content {
font-weight: bold;
font-size: 1rem;
margin-top: 5px;
padding-right: 35px;
line-height: 1.4;
}

.item-interval {
font-size: 0.75rem;
color: var(--sub-text);
margin-top: 5px;
}

/* 削除ボタン:小さな「×」コマンド */
.delete-btn {
position: absolute;
top: 10px;
right: 10px;
background: #EEE;
border: 2px solid var(--pixel-brown);
color: #442233;
cursor: pointer;
font-family: 'MisakiGothic';
font-size: 1rem;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
}
.delete-btn:hover {
background: #FFADAD;
color: white;
}

/* 編集ボタン:削除ボタンの左に配置 */
.edit_btn {
position: absolute;
top: 10px;
right: 40px; /* 削除ボタン(10px) + ボタン幅(25px) + 余白(5px) */
background: #EEE;
border: 2px solid var(--pixel-brown);
color: #442233;
text-decoration: none;
cursor: pointer;
font-family: 'MisakiGothic';
font-size: 0.9rem;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.edit_btn:hover {
background: #BEE3FF; /* 編集は水色っぽくしても可愛い */
color: var(--pixel-brown);
}
</style>
</head>
<body>
<div class="container">
<a href="{% url 'my_guide:index' %}" class="back-link">◀︎ ログ一覧へ</a>
<h1>🧸 じゅもんリスト</h1>

<a href="{% url 'my_guide:strategy_add' %}" class="add-btn">+ あたらしい じゅもん</a>
{% regroup strategies by get_type_display as type_list %}
{% for type in type_list %}
<div class="section-card">
<div class="type-title">【 {{ type.grouper }} 】</div>
{% for s in type.list %}
<div class="strategy-item">
<span class="category-badge">{{ s.get_category_display }}</span>
<div class="item-content">{{ s.content }}</div>
{% if s.interval %}<div class="item-interval">とき:{{ s.interval }}</div>{% endif %}

<a href="{% url 'my_guide:strategy_edit' s.pk %}" class="edit_btn" title="メンテナンス">✏︎</a>

<form action="{% url 'my_guide:strategy_delete' s.pk %}" method="post"
onsubmit="return confirm('この じゅもんを わすれても いいですか?');"
style="display: inline;">
{% csrf_token %}
<button type="submit" class="delete-btn">×</button>
</form>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</body>
</html>

custome_admin.css

body {
background-color: #fcfaf5 !important;
}
#header {
background: #d4c4b0 !important;
}
タイトルとURLをコピーしました