Search code examples
pythondjangomodel

Field 'id' expected a number but got dict when seeding database django


I'm looking for a way to store a question fetched from an external API to be stored in a model called Question. My views.py module does that by requesting data based on user's question difficulty choice, then renders what's fetched in a form. It's a pretty straightforward method but for some reason I get Field 'id' expected a number but got {'type': 'multiple'...}.

I deleted a code that intended to create a another model singleton that was interfering with this implementation from last answer of this question. Then I ran ./manage.py makemigrations and ./manage.py migrate to reflect changes but the exception was again raised. After that, I deleted migrations.py and their cached files to run the same two commands and nothing changed.

Can anyone point out what I'm missing/doing wrong please?

models.py

from django.db import models


class Question(models.Model):
    type = models.TextField()
    difficulty = models.TextField()
    category = models.TextField()
    question = models.TextField()
    correct_answer = models.TextField()
    incorrect_answers = models.TextField()

views.py

from django.shortcuts import render, HttpResponse
from .forms import QuestionForm, QuestionLevelForm
from urllib.request import URLError
from .models import Question
import requests

def process_question(request):
    if "level" in request.POST:
        return fetch_question(request)
    elif "answer" in request.POST:
        return check_answer(request)
    else:
        form = QuestionLevelForm()
        return render(request, "log/question.html", {"form": form})


def fetch_question(request):
    match request.POST["difficulty"]:
        case "easy":
            url = "https://opentdb.com/api.php?amount=1&category=9&difficulty=easy&type=multiple"
        case "medium":
            url = "https://opentdb.com/api.php?amount=1&category=9&difficulty=medium&type=multiple"
        case "hard":
            url = "https://opentdb.com/api.php?amount=1&category=9&difficulty=hard&type=multiple"

    try:
        response = requests.get(url)
    except URLError as e:
        HttpResponse("Couldn't fetch data, try again")
    else:
        render_question(request, response.json())


def render_question(request, response):
    content = response["results"][0]
    question = {
        "type": content["type"],
        "difficulty": content["difficulty"],
        "category": content["category"],
        "question": content["question"],
        "correct_answer": content["correct_answer"],
        "incorrect_answers": content["incorrect_answers"],
    }
    form = QuestionForm(question)
    model = Question(question)
    model.save()
    context = {"question": question, "form": form}
    render(request, "./log/templates/log/question.html", context)

test_views.py

from django.http import HttpRequest
from django.test import TestCase, Client
from .. import views

client = Client()


class QuestionTest(TestCase):
    def test_page_load(self):
        response = self.client.get("/log/question")
        self.assertEqual(response["content-type"], "text/html; charset=utf-8")
        self.assertTemplateUsed(response, "log/question.html")
        self.assertContains(response, "Choose your question level", status_code=200)

    def test_fetch_question(self):
        request = HttpRequest()
        request.method = "POST"
        request.POST["level"] = "level"
        request.POST["difficulty"] = "hard"
        request.META["HTTP_HOST"] = "localhost"
        response = views.process_question(request)
        self.assertEqual(response.status_code, 200)

Solution

  • When using model constructor, use keyword arguments! [1]

    With keyword arguments, you can specify which arguments you are assigning to. For example:

    Question(
        type = content["type"],
        difficulty = content["difficulty"],
        category = content["category"],
        question = content["question"],
        correct_answer = content["correct_answer"],
        incorrect_answers = content["incorrect_answers"]
    )

    You may also use dict unpacking to construct a Question. This will produce the arguments automatically:

    Question(**question)

    [1] In your example, the constructor of the class Question takes question which is a dict and assigns this to its first argument id.