Pomijając brak testów i mniejsze lub większe pierdoły (np osobiście bym zamienił te widoki z funkcji na widoki klasowe, powinno być trochę mniej kodu), to najbardziej w oczy się rzuca słabe używanie ORMa.
answer_2 = question.answers.filter(is_correct=False).all()[0]
answer_3 = question.answers.filter(is_correct=False).all()[1]
answer_4 = question.answers.filter(is_correct=False).all()[2]
for j in range(4):
choices.append([
question.answers.all()[j], question.answers.all()[j].is_correct,
int(question.answers.all()[j].id) == int(data.get(f'answer{question.id}'))])
if request.user in quiz.likes.all():
quiz.likes.remove(request.user)
else:
if request.user in quiz.dislikes.all():
question = get_object_or_404(Question, pk=pk)
if request.user != question.quiz.author:
Robisz tutaj kilka razy więcej zapytań SQLowych niż jest potrzeba. Polecam poczytać o funkcjonalności querysetów, w szczególności select_related, prefetch_related.
Na przykład w tym ostatnim samo `question.quiz` wygeneruje nowe zapytanie bazowanowe wyciągające quiz tego pytania. To jest podręcznikowe miejsce na użycie `select_related`.