百済茄子

アマチュアプログラマによる主にPythonとDjangoのブログ。

『退屈なことはPythonにやらせよう』8章 P.202「ランダムな問題集ファイルを作成する」をwith構文を使って書く

  2018/08/15         

<課題>
・県庁所在地を問う選択式の問題を35人分作る。
・一人一人の問題の並び順をランダムに変える。
・解答も35人分作る。


random_quiz_generator.py
# ================================================================
# インポート
# ================================================================
import random


# ================================================================
# 各種データ
# ================================================================
# 問題のデータ。キーが都道府県で値が県庁所在地
capitals = {  # (1)
    '北海道': '札幌市', '青森県': '青森市', '岩手県': '盛岡市',
    '宮城県': '仙台市', '秋田県': '秋田市', '山形県': '山形市', '福島県': '福島市',
    '茨城県': '水戸市', '栃木県': '宇都宮市', '群馬県': '前橋市',
    '埼玉県': 'さいたま市', '千葉県': '千葉市', '東京都': '東京',
    '神奈川県': '横浜市', '新潟県': '新潟市', '富山県': '富山市', '石川県': '金沢市',
    '福井県': '福井市', '山梨県': '甲府市', '長野県': '長野市', '岐阜県': '岐阜市',
    '静岡県': '静岡市', '愛知県': '名古屋市', '三重県': '津市', '滋賀県': '大津市',
    '京都府': '京都市', '大阪府': '大阪市', '兵庫県': '神戸市', '奈良県': '奈良市',
    '和歌山県': '和歌山市', '鳥取県': '鳥取市', '島根県': '松江市',
    '岡山県': '岡山市', '広島県': '広島市', '山口県': '山口市', '徳島県': '徳島市',
    '香川県': '高松市', '愛媛県': '松山市', '高知県': '高知市', '福岡県': '福岡市',
    '佐賀県': '佐賀市', '長崎県': '長崎市', '熊本県': '熊本市', '大分県': '大分市',
    '宮崎県': '宮崎市', '鹿児島県': '鹿児島市', '沖縄県': '那覇市'
}

# 問題集のヘッダ
header = """名前:

日付:

学期:

                    都道府県庁所在地クイズ(問題番号{})
                    
"""

# 問題文と解答選択肢
quiz_format = """{quiz_num}. {prefecture}の都道府県庁所在地は?
 A.{A} B.{B} C.{C} D.{D}
"""


# ================================================================
# 処理開始
# ================================================================
# 35名分の問題集を作成する
for exam_num in range(1):  # ★★★★★本番時range(35)に直すこと★★★★★
    with open('capitalsquiz{}.txt'.format(exam_num + 1), 'w', encoding='utf-8') as fq, \
         open('capitalquiz_answers{}.txt'.format(exam_num + 1), 'w', encoding='utf-8') as fa:

        # 問題集のヘッダを書く
        print(header.format(exam_num + 1), file=fq)

        # 都道府県の順番をシャッフルする
        prefectures = list(capitals.keys())
        random.shuffle(prefectures)

        # 47都道府県をループして、それぞれ問題を作成する
        for quiz_num, prefecture in enumerate(prefectures):
            correct_answer = capitals[prefecture]               # 正答
            wrong_answers = list(capitals.values())             # 全ての県庁所在地
            wrong_answers.remove(correct_answer)                # 正答を削除=誤答
            wrong_answers = random.sample(wrong_answers, 3)     # ダミーとなる誤答を3つ選ぶ
            answer_options = wrong_answers + [correct_answer]   # 正答とダミーを組み合わせる
            random.shuffle(answer_options)                      # 正答とダミーをシャッフルする

            # 問題文と解答選択肢を問題ファイルに書く
            print(
                quiz_format.format(
                    quiz_num=quiz_num + 1,
                    prefecture=prefecture,
                    A=answer_options[0],
                    B=answer_options[1],
                    C=answer_options[2],
                    D=answer_options[3],
                ),
                file=fq,
            )  # (2)

            # 解答に答えの選択肢を書く
            print(str(quiz_num + 1) + '.'
                  + 'ABCD'[answer_options.index(correct_answer)] + '\n', file=fa)

(1) PEP8的には波カッコで1行取る書き方が良い。

(2) (1)と同様、書くならprintはこの書き方。



実行結果

capiralquiz1.txt
名前:

日付:

学期:

                    都道府県庁所在地クイズ(問題番号1)
                    

1. 山口県の都道府県庁所在地は?
 A.高松市 B.山口市 C.高知市 D.鹿児島市

2. 福島県の都道府県庁所在地は?
 A.福島市 B.秋田市 C.和歌山市 D.鹿児島市

3. 長野県の都道府県庁所在地は?
 A.佐賀市 B.長野市 C.広島市 D.前橋市


capitalquiz_answers1.txt
1.B

2.A

3.B



Django、ビューの中に書くメソッドget_queryset, get_context_data, form_valid, postをkwsk 2

  2018/08/13         

前回の続き。演習問題。
「’死ね’問題」とか。


1.「完了(done)」のメモは一覧ページに表示させない。

これはテンプレート(memo_list.html)でやる方法とviews.pyでやる方法がある。
views.pyでやったほうがスマート。(参考:memo_list.htmlでやる方法

views.py

class MemoListView(ListView):
    model = Memo

    def get_queryset(self):
        queryset = Memo.objects.order_by('done', '-created_at').filter(done=False)  # 追加
        return queryset

Memo.objectsはMemoモデルをqueryset型に変換する。
Memo.objects.order_by~ でqueryset型が返され、それが変数querysetに入る。
Memo.objects.all()もqueryset型。
完了(done)がFalseのもののみを表示させたいのだから、filter(done=False)を繋げてやれば良い。

3つあったメモが、完了1つが非表示になり2つになった。





2.詳細ページの上部に、全てのカテゴリを表示する。

views.py
from django.views.generic import ListView, DetailView
from django.views.generic.edit import ModelFormMixin
from django.shortcuts import redirect, get_object_or_404
from django.urls import reverse_lazy
from .models import Memo, Comment, Category  # 追加
from .forms import CommentForm

...

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        memo_pk = self.kwargs['pk']
        context['comments'] = Comment.objects.filter(memo_id=memo_pk)
        context['categories'] = Category.objects.all()  # 追加
        return context

変数指定部分が2行あり、1つ目のcontext[‘comments’] = はメモ詳細ページのidを指定しているが
今回の「全てのカテゴリ」は全て出力すれば良いのでall()とする。

memo_detail.html
{% extends 'memoapp_kai/base.html' %}

{% block content %}

<!-- 3行追加 -->
{% for category in categories %}
  {{ category }}
{% endfor %}

<div class="card mb-2">
  <div class="card-body">





3.「死ね」というコメントを送信できないようにする。

form_validでcomment.save()をする前に判定したい。
よって、

views.py
    def form_valid(self, form):
        comment = form.save(commit=False)
        memo_pk = self.kwargs['pk']
        comment.memo = get_object_or_404(Memo, pk=memo_pk)
        if '死ね' in コメント:
            return redirect('memo_detail', pk=memo_pk)
        comment.save()
        return redirect('memo_detail', pk=memo_pk)

とすれば良いが、「コメント」の部分に何を指定すれば良いのかがよく分からなかった。
if ‘死ね’ in form: だと普通に送信される。



print(form)として、送信してみたときのformの中身を見てみる。

views.py
    def form_valid(self, form):
        comment = form.save(commit=False)
        print(form)  # 追加
        memo_pk = self.kwargs['pk']
        comment.memo = get_object_or_404(Memo, pk=memo_pk)
        if '死ね' in form:
            return redirect('memo_detail', pk=memo_pk)
        comment.save()
        return redirect('memo_detail', pk=memo_pk)

コマンドプロンプト
<tr><th><label for="id_name">名前:</label></th><td><input type="text" name="name" class="form-control" maxlength="20" id="id_name" /></td
></tr>
<tr><th><label for="id_comment">コメント:</label></th><td><input type="text" name="comment" value="死ね" class="form-control" maxlength="
50" required id="id_comment" /></td></tr>
[13/Aug/2018 08:34:58] "POST /detail/2/ HTTP/1.1" 302 0
[13/Aug/2018 08:34:58] "GET /detail/2/ HTTP/1.1" 200 3484

formには単純な「送信されたコメント」ではなく、HTMLタグがいろいろと入っていることがわかる。

print(form.comment) としてみると



「そんな属性ねーよ」的なエラーが返る。
よって、formで取得するのはやめておく。

次に、commentとは何だろうと考え、print(comment) として見てみる。

コマンドプロンプト
死ね
[13/Aug/2018 08:43:52] "POST /detail/2/ HTTP/1.1" 302 0
[13/Aug/2018 08:43:52] "GET /detail/2/ HTTP/1.1" 200 3517

普通に「死ね」と入っている。

さらに、print(comment.comment) としてみると

コマンドプロンプト
死ね
[13/Aug/2018 08:46:47] "POST /detail/2/ HTTP/1.1" 302 0
[13/Aug/2018 08:46:47] "GET /detail/2/ HTTP/1.1" 200 3550

同じような結果が返ってくる。
ここからわかることは、「commentというオブジェクトの中にcommentという属性を持っていて、そこにformのコメントの内容が入っている」ということ。

print(comment) としたときになぜ同じ「死ね」が出力されたかというと
models.pyのCommentクラスで、__str__(self) の特殊メソッドを指定していたため

models.py
class Comment(models.Model):
    """コメント"""
    id = models.AutoField('ID', primary_key=True)
    name = models.CharField('名前', max_length=20, null=True, blank=True)
    comment = models.TextField('コメント', max_length=50, null=False)
    memo = models.ForeignKey(Memo, on_delete=models.CASCADE)
    created_at = models.DateTimeField('作成日時', auto_now_add=True)
    updated_at = models.DateTimeField('更新日時', auto_now=True)

    def __str__(self):
        return self.comment

    class Meta:
        db_table = 'comments'

モデルの属性を指定しないと自動でコメントの内容が返されていた。
commentだけだとモデルを指定しただけなので、comment.commentとするのが正しい。
よって、

views.py
    def form_valid(self, form):
        comment = form.save(commit=False)
        memo_pk = self.kwargs['pk']
        comment.memo = get_object_or_404(Memo, pk=memo_pk)
        if '死ね' in comment.comment:
            return redirect('memo_detail', pk=memo_pk)
        comment.save()
        return redirect('memo_detail', pk=memo_pk)

とすると正しく動作する。



  -  Django

PAGE TOP