

現在Djangoのチュートリアルをしているのですが、下記のモデルで `views.py` で値を
filterして取り出したいのですがどのようにコードを書いて良いか分かりません。詳しい方教えて頂けないでしょうか?

views.pyの `IndexView`の `return
Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]`
で Choiceモデルのchoice_textが1つもデータがないときに質問オブジェクトを返さないようにフィルターしたいです。どのように
Questionモデルと結びつけられたChoiceモデルのデータにアクセスしたら良いのでしょうか?

そして `DetailView` も同様にURLが作成されないようにしたいです。 `return
Question.objects.filter(pub_date__lte=timezone.now())` に条件を追加したいです。

よろしくお願いします。

models.py

[code]

    import datetime
    from django.db import models
    from django.utils import timezone
    
    # Create your models here.
    # 各モデルがmodels.Modelを継承して使っている。
    # models.modelのサブクラスになる。
    class Question(models.Model):
      # クラス変数を定義する。データベースフィールドを表現している。
      # Charフィールドは文字のフィールド
      question_text = models.CharField(max_length=200)
    
      # 日時のフィールド
      pub_date = models.DateTimeField('date published')
    
      def __str__(self):
        # インスタンスを生成して、printした際にここが実行される。
        # シェルで表示されるオブジェクトに質問名が使われるだけでなく
        # adminでオブジェクトを表現する際にも使用されるので追加する必要がある。
        return self.question_text
      
      def was_published_recently(self):
        now = timezone.now()
        # now - datetime.timedelta(days=1)は今の時間から一日引いた日付を出す。
        # 2021-05-19 23:29:56.216634こんな感じの値になる。
        # pub_dateが現在時刻より過去で現在時刻から一日以内の場合はTrueを返すメソッド
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    
    class Choice(models.Model):
    
      # これはChoiceがQuestionに関連付けられている事を伝えている。
      # データベースの多対一、多対多、一対一のようなデータベースリレーションシップに対応する。
      # Question ← → Choiseと双方向のやりとりが可能となる。
      question = models.ForeignKey(Question, on_delete=models.CASCADE)
      choice_text = models.CharField(max_length=200)
      votes = models.IntegerField(default=0)
    
      def __str__(self):
        return self.choice_text
    
[/code]

views.py

[code]

    from django.shortcuts import render, get_object_or_404
    from django.http import HttpResponseRedirect
    from django.template import context
    from .models import Choice, Question
    from django.urls import reverse
    from django.utils import timezone
    from django.views import generic
    
    
    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        # テンプレート側でQuestion.objects.order_by('-pub_date')[:5]を呼び出す際の名前を設定している。
        context_object_name = 'latest_question_list'
    
        def get_queryset(self):
            return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
    
    
    class DetailView(generic.DetailView):
        # テンプレートで変数にアクセスする際はquestionになる。
        model = Question
        template_name = 'polls/detail.html'
        def get_queryset(self):
            return Question.objects.filter(pub_date__lte=timezone.now())
    
    
    
    class ResultsView(generic.DetailView):
        model = Question
        template_name = 'polls/results.html'
    
    
    def vote(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        try:
            selected_choice = question.choice_set.get(pk=request.POST['choice'])
        except (KeyError, Choice.DoesNotExist):
            return render(request, 'polls/detail.html', {
                'question': question,
                'error_message': "You didn't select a choice.",
            })
        else:
            selected_choice.votes += 1
            selected_choice.save()
            # resultsページのURLをreverseで取得してリダイレクトしている。引数としてquestion.idを渡している。
            return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
    
[/code]

urls.py

[code]

    from django.urls import path
    
    from . import views
    app_name = 'polls'
    
    urlpatterns = [
        # /polls/
        path('', views.IndexView.as_view(), name='index'),
    
        # ex：/polls/5/
        path('<int:pk>/', views.DetailView.as_view(), name='detail'),
        path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
        path('<int:question_id>/vote/', views.vote, name='vote'),
    ]
    
[/code]

### 追記

**polls:index**

[![polls:index](https://i.stack.imgur.com/IPr7O.png)](https://i.stack.imgur.com/IPr7O.png)

filterをかけてない状態だとtest3のような選択肢がない質問が一覧に表示されます。  
それを取り除きたいです。

**test3**

[![test3](https://i.stack.imgur.com/CvRrs.png)](https://i.stack.imgur.com/CvRrs.png)

**what's up**

[![what's
up](https://i.stack.imgur.com/z0fXq.png)](https://i.stack.imgur.com/z0fXq.png)

それで下記のようにviews.pyにfilterを追加したところ、test3は取り除けたのですが、なぜか一つしかない what's up
の質問が2つ表示されるようになりました。

[code]

    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        # テンプレート側でQuestion.objects.order_by('-pub_date')[:5]を呼び出す際の名前を設定している。
        context_object_name = 'latest_question_list'
    
        def get_queryset(self):
            return Question.objects.filter(pub_date__lte=timezone.now(), choice__choice_text__isnull=False).order_by('-pub_date')[:5]
    
[/code]

**編集後**

[![編集後](https://i.stack.imgur.com/7Vw0s.png)](https://i.stack.imgur.com/7Vw0s.png)


CC BY-SA 4.0
2021-05-27T06:16:19.280
76105
2023-08-09T01:08:56.020
2021-05-28T00:54:25.220
3060
22565
question
python python3 django
Django逆参照で子モデルから親の値にfilterを掛けて、データを取り出したい。


Choiceが少なくとも一つはあるQuestionだけを抽出したい、ということでいいでしょうか。(choice_textはNoneや空にはなりませんから)

次のようにするとそのようなQuestionを抽出できます。

[code]

    from django.db.models import Exists, OuterRef
    
    Question.objects.filter(
        pub_date__lte=timezone.now()
    ).filter(
        Exists(Choice.objects.filter(question=OuterRef('pk'))
    )
    
[/code]

**追記**  
ご質問に追記の `.filter(choice__choice_text__isnull=False)` とする場合、同じ Question
が質問の数だけ出てきますので、`.distinct()` をつけて重複を取り除く必要があります。

なぜそうなるかですが、この場合は実際には JOIN を行います。全ての Question と Choice の組み合わせのうち、Question と
Choice が対応していて指定の条件を満たす組が全て抽出され、そこから Question だけを取り出すという感じになります。このため同じ
Question が該当する Choice の数だけ出てきます。


CC BY-SA 4.0
2021-05-27T17:55:16.237
76128
2021-05-28T03:08:05.823
2021-05-28T03:08:05.823
9464
9464
76105
answer