Database entry changes to 'polling' web app from official Django docs

I used the Django tutorial in the official docs to build that polling web app.

I completed most of it. I kinda struggled my way through the automated testing tutorial. Maybe as my experience with Python increases, writing robust testing elements to my apps will become easier. But all the code is in place and working. I’ve also extended it with some additional basic features.

Backtracking to tutorial #02 - - for the Playing with API section - - I have a question. This part of the tutorial teaches how to query, add, delete, and save these changes to rows and columns from the db.sqlite3 database.

For example, here is how I activate my shell, import the models and classes, and then print out their contents:

$ python manage.py shell
Python 3.10.5 (main, Jun  6 2022, 18:49:26) [GCC 12.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from polls.models import Choice, Question
>>> Question.objects.all()
<QuerySet [<Question: What's up?>, <Question: How many elected politicians does it take to change a light bulb?>, <Question: What generation do you belong to?>]>
>>> Choice.objects.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just building my website again>, <Choice: Huh?>, <Choice: Lost Gen : 1883-1900>, <Choice: Greatest Gen : 1901-1927>, <Choice: Silent Gen : 1928-1945>, <Choice: Baby Boomers : 1946-1964>, <Choice: Generation X : 1965-1980>, <Choice: Millenials : 1981-2001>, <Choice: Zoomers : 2002-2020>, <Choice: Undefined yet : 2021-current>]>

Next is how I query stored Choices and Questions specified by their unique identifier:

>>> Question.objects.filter(id=4)
<QuerySet [<Question: What generation do you belong to?>]>
>>> Choice.objects.filter(id=4)
<QuerySet [<Choice: Huh?>]>

Below is me instantiating a Question (with primary key = 3) and adding 4 unique Choices:

>>> q = Question.objects.get(pk=3)
>>> q
<Question: How many elected politicians does it take to change a light bulb?>
>>> q.choice_set.create(choice_text="One?", votes=0)
<Choice: One?>
>>> q.choice_set.create(choice_text="Two?", votes=0)
<Choice: Two?>
>>> q.choice_set.create(choice_text="Ten?", votes=0)
<Choice: Ten?>
>>> q.choice_set.create(choice_text="None! Elected politicians never change anything!", votes=0)
<Choice: None! Elected politicians never change anything!>
>>> q.choice_set.create(question_if)

So that all works. I understand all of that. For the choice_set mechanism, the explanation in the official Django doc is sparse but I found an interaction on Stack Overflow which fills that knowledge gap titled: What is choice_set in this Django app tutorial?

How do I change one of the choice_text’s that is already created above? I have already got two answers to that question: I can use the Admin Dashboard user interface, find the entry, change it, then click the green “Save” button. Alternatively, I can use my sqlitebrowser utility and manually change the entry. But my question is: How do I change an existing choice_text from the CLI shell?

Here are some of my futile attempts:

>>> q.choice_set.modify(choice_text="None!")
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'RelatedManager' object has no attribute 'modify'
>>> q.choice_set.change(choice_text="None!")
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'RelatedManager' object has no attribute 'change'
>>> q.choice_modify.filter(choice_text="Ten?", votes=0)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Question' object has no attribute 'choice_modify'
>>> q.choice_change.filter(choice_text="Ten?")
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Question' object has no attribute 'choice_change'
>>> q.choice_change.filter(choice_text="Ten?")

Any ideas on how to change / modify choice text that is already entered into the database?

For your reference, here is the views.py:

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Choice, Question
 
 
class IndexView(generic.ListView):
   template_name = 'polls/index.html'
   context_object_name = 'latest_question_list'
 
   def get_queryset(self):
       """Return the last five published questions."""
       return Question.objects.order_by('-pub_date')[:5]
 
 
class DetailView(generic.DetailView):
   model = Question
   template_name = 'polls/detail.html'
 
 
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):
       # Redisplay the question voting form.
       return render(request, 'polls/detail.html', {
           'question': question,
           'error_message': "You didn't select a choice.",
       })
   else:
       selected_choice.votes += 1
       selected_choice.save()
       # Always return an HttpResponseRedirect after successfully dealing
       # with POST data. This prevents data from being posted twice if a
       # user hits the Back button.
       return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
 
class AboutView(generic.TemplateView):
   template_name = "polls/about.html"

And my models.py:

from django.db import models
from django.utils import timezone
import datetime
 
class Question(models.Model):
   question_text = models.CharField(max_length=200)
   title_text = models.CharField(max_length=200, blank=True)
   pub_date = models.DateTimeField('date published')
  
   def was_published_recently(self):
       return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
 
   def __str__(self):
       return self.question_text
 
class Choice(models.Model):
   question = models.ForeignKey(Question, on_delete=models.CASCADE,)
   choice_text = models.CharField(max_length=200)
   notes = models.CharField(max_length=200)
   votes = models.IntegerField(default=0)
 
   def __str__(self):
       return self.choice_text

In shell, get the choice object which you want to change the text.

>>>c = Choice.objects.get(pk=?) or Choice.objects.filter(condition..) (filter may return multiple objects if the condition matches)
>>>c.choice_text = 'updated text'
>>>c.save()

But why do you want to do it only from shell?

1 Like

Hey there!
If you want to update all objects related to your q (Question) object you can use the update:
q.choice_set.update(choice_text="The text goes here")
This method will return to you the number (int) of rows affected by the change you just made.

1 Like

This worked perfectly. Thank you @oneflydown.

For two reasons:

  1. For the sake of ‘rigor’ because I am curious
  2. I am still learning the basics of Python OOP and playing around and wrangling with objects in the Django shell is instructive

I feel that this is a gap which should be in the official docs. But now that it works, I will always be able to refer back to here.