Django 如何返回到表单的上一页

Django 如何返回到表单的上一页,django,django-forms,Django,Django Forms,我正在django中创建一个测验应用程序。我通过一个表格收集了用户的答案,当用户在测验中单击“下一步”时,它会转到下一个问题,但我不知道如何在表格中添加“上一步”按钮,允许用户转到他选择的上一个问题。。我尝试在JavaScript中使用window.history.back(),但它只返回一次 这是我的观点; ''' ''' 这是我的模板 ''' {%if-question%} {%if progress%} {%trans“问题”%}{{progress.0 |添加:1}{{%trans”的“

我正在django中创建一个测验应用程序。我通过一个表格收集了用户的答案,当用户在测验中单击“下一步”时,它会转到下一个问题,但我不知道如何在表格中添加“上一步”按钮,允许用户转到他选择的上一个问题。。我尝试在JavaScript中使用window.history.back(),但它只返回一次 这是我的观点; '''

'''

这是我的模板

'''

{%if-question%}
{%if progress%}
{%trans“问题”%}{{progress.0 |添加:1}{{%trans”的“%}{{progress.1}}
{%endif%}

{%trans“问题类别”%}:
{{question.category}

{{question.content}

{%if question.figure%} {%endif%} {%csrf_令牌%}
    {%用于表单中的答案。答案%}
  • {{答案}
  • {%endfor%}
{%if progress.0 | add:1==progress.1%} {%else%} {%endif%} {%endif%}
{%endblock%}
'''

这是我的模特

'''

重新导入
导入json
导入csv
从django.db导入模型
从django.core.exceptions导入验证错误,配置不正确
从django.core.validators导入MaxValueValidator,验证\逗号\分隔\整数\列表
从django.utils.timezone立即导入
从django.conf导入设置
从django.utils.translation导入ugettext作为_
从model_utils.managers导入继承管理器
从django.db.models.signals导入预保存、后保存
输入io
从.signals导入已上载的csv_
从.validators导入csv\u文件\u验证程序
从django.contrib.auth.models导入用户
从django.contrib导入消息
类CategoryManager(models.Manager):
def新_类别(自身、类别):
new_category=self.create(category=re.sub(“\s+”,“-”,category)
.lower())
新建_category.save()
返回新类别
类别(models.Model):
类别=models.CharField(
详细名称=uu(“类别”),
最大长度=250,空白=真,
唯一=真,空=真)
对象=CategoryManager()
类元:
详细名称=uu(“类别”)
详细名称复数=(类别)
定义(自我):
返回自我类别
课堂测验(models.Model):
title=models.CharField(
详细名称=uu(“标题”),
最大长度=60,空白=假)
description=models.TextField(
详细名称=“(说明”),
blank=True,help\u text=(测验说明)
url=models.SlugField(
最大长度=60,空白=假,
help_text=(用户友好的url),
详细名称=(用户友好url)
类别=models.ForeignKey(
类别,null=True,blank=True,
详细名称=u(“类别”),在_delete=models.CASCADE上)
随机_顺序=models.BooleanField(
blank=False,default=False,
详细名称=uu(“随机顺序”),
help_text=(“在中显示问题”
“随机顺序或按顺序排列”
“设置好了吗?”)
max_questions=models.PositiveIntegerField(
blank=True,null=True,verbose\u name=“(最大问题”),
help_text=(每次尝试回答的问题数量)
答案在结尾=models.BooleanField(
blank=False,default=False,
help_text=u(“问题后不显示正确答案。”
“最后显示答案。”),
详细名称=(结尾处的答案)
试卷=models.BooleanField(
blank=False,default=False,
help_text=(如果是,则显示每个选项的结果)
“用户的尝试将是”
“储存。标记所需。”),
详细名称=(试卷)
单次尝试=models.BooleanField(
blank=False,default=False,
help_text=(如果是,则仅由用户进行一次尝试)
“将允许用户。”
“非用户不能参加此考试。”),
详细名称=(单个尝试)
pass_mark=models.SmallIntegerField(
blank=True,默认值=0,
详细名称=“(通过标记”),
help_text=u(“通过考试所需的百分比”),
验证器=[MaxValueValidator(100)])
success\u text=models.TextField(
blank=True,help\u text=\(“如果用户通过,则显示”),
详细名称=(成功文本)
失败\u text=models.TextField(
详细名称=“(失败文本”),
blank=True,help\u text=(如果用户失败,则显示)
草稿=models.BooleanField(
空白=真,默认值=假,
详细名称=“(草案”),
help_text=(如果是,则不显示测验)
“在测验列表中,并且只能”
“由可编辑的用户拍摄”
(小测验)
def保存(self、force_insert=False、force_update=False、*args、**kwargs):
self.url=re.sub('\s+','-',self.url).lower()
self.url=''.join(如果
letter.isalnum()或letter='-')
如果self.single_尝试为真:
自考试卷=正确
如果自检合格分数>100:
raise ValidationError(“%s”高于100“%self.pass\u标记)
超级(测验,自我)。保存(强制插入,强制更新,*args,**kwargs)
类元:
详细名称=uu(“测验”)
详细的名字复数形式(“测验”)
定义(自我):
返回自己的标题
def获取_问题(自我):
返回self.question\u set.all()。选择子类()
@财产
def get_max_分数(自我):
返回self.get_questions().count()
def anon_得分_id(自我):
返回str(self.id)+“_分数”
def anon_q_列表(自身):
返回str(self.id)+“\u q\u列表”
def anon_q_数据(自身):
返回str(self.id)+
   class QuizTake(FormView):
       form_class = QuestionForm
       template_name = 'question.html'

    def dispatch(self, request, *args, **kwargs):
        self.quiz = get_object_or_404(Quiz, url=self.kwargs['quiz_name'])
        if self.quiz.draft and not request.user.has_perm('quiz.change_quiz'):
            raise PermissionDenied

        self.logged_in_user = self.request.user.is_authenticated

        if self.logged_in_user:
            self.sitting = Sitting.objects.user_sitting(request.user,
                                                        self.quiz)
        else:
            form_error= "You must login to your ACAID account to access the page you requested!"
            return render(request, 'login.html', { 'form_error': form_error})  

        if self.sitting is False:
            return render(request, 'single_complete.html')

        return super(QuizTake, self).dispatch(request, *args, **kwargs)

    def get_form(self, form_class=QuestionForm):
        if self.logged_in_user:
            self.question = self.sitting.get_first_question()
            self.progress = self.sitting.progress()
        return form_class(**self.get_form_kwargs())

    def get_form_kwargs(self):
        kwargs = super(QuizTake, self).get_form_kwargs()

        return dict(kwargs, question=self.question)

    def form_valid(self, form):
        if self.logged_in_user:
            self.form_valid_user(form)
            if self.sitting.get_first_question() is False:
                return self.final_result_user()
        self.request.POST = {}

        return super(QuizTake, self).get(self, self.request)

    def get_context_data(self, **kwargs):
        context = super(QuizTake, self).get_context_data(**kwargs)
        context['question'] = self.question
        context['quiz'] = self.quiz
        if hasattr(self, 'previous'):
            context['previous'] = self.previous
        if hasattr(self, 'progress'):
            context['progress'] = self.progress
        return context

    def form_valid_user(self, form):
        progress, c = Progress.objects.get_or_create(user=self.request.user)
        guess = form.cleaned_data['answers']
        is_correct = self.question.check_if_correct(guess)

        if is_correct is True:
            self.sitting.add_to_score(1)
            progress.update_score(self.question, 1, 1)
        else:
            self.sitting.add_incorrect_question(self.question)
            progress.update_score(self.question, 0, 1)

        if self.quiz.answers_at_end is not True:
            self.previous = {'previous_answer': guess,
                             'previous_outcome': is_correct,
                             'previous_question': self.question,
                             'answers': self.question.get_answers(),
                             'question_type': {self.question
                                               .__class__.__name__: True}}
        else:
            self.previous = {}

        self.sitting.add_user_answer(self.question, guess)
        self.sitting.remove_first_question()

    def final_result_user(self):
        results = {
            'quiz': self.quiz,
            'score': self.sitting.get_current_score,
            'max_score': self.sitting.get_max_score,
            'percent': self.sitting.get_percent_correct,
            'sitting': self.sitting,
            'previous': self.previous,
        }

        self.sitting.mark_quiz_complete()

        if self.quiz.answers_at_end:
            results['questions'] =\
                self.sitting.get_questions(with_answers=True)
            results['incorrect_questions'] =\
                self.sitting.get_incorrect_questions

        if self.quiz.exam_paper is False:
            self.sitting.delete()

        return render(self.request, 'result.html', results)
{% if question %}

{% if progress %}
<div style="float: right;">
{% trans "Question" %} {{ progress.0|add:1 }} {% trans "of" %} {{ progress.1 }}
</div>
{% endif %}

<p>
  <small class="muted">{% trans "Question category" %}:</small>
  <strong>{{ question.category }}</strong>
</p>

<p class="lead">{{ question.content }}</p>

{% if question.figure %}
    <img src="{{ question.figure.url }}" alt="{{ question.content }}" />
{% endif %}

<form action="" method="POST">{% csrf_token %}
  <input type=hidden name="question_id" value="{{ question.id }}">

  <ul class="list-group">

    {% for answer in form.answers %}
      <li class="list-group-item">
        {{ answer }}
      </li>
    {% endfor %}

  </ul>
    {% if progress.0|add:1 == progress.1 %}
    <input type="submit" value={% trans "Submit" %} class="btn btn-large btn-block btn-warning" >
    <input type="button" value= "previous page"  onclick= "previous()"class="btn btn-large btn-block btn-warning" />

    {% else %}

    <input type="button" value= "previous page"  onclick= "previous()"class="btn btn-large btn-block btn-warning" />
        <input type="submit" value={% trans "Next" %} class="btn btn-large btn-block btn-warning" >
    {% endif %}
</form>

{% endif %}

<hr>


{% endblock %}
import re
import json
import csv
from django.db import models
from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.core.validators import MaxValueValidator, validate_comma_separated_integer_list
from django.utils.timezone import now
from django.conf import settings
from django.utils.translation import ugettext as _
from model_utils.managers import InheritanceManager
from django.db.models.signals import pre_save, post_save
import io
from .signals import csv_uploaded
from .validators import csv_file_validator
from django.contrib.auth.models import User
from django.contrib import messages


class CategoryManager(models.Manager):

    def new_category(self, category):
        new_category = self.create(category=re.sub('\s+', '-', category)
                                   .lower())

        new_category.save()
        return new_category


class Category(models.Model):

    category = models.CharField(
        verbose_name=_("Category"),
        max_length=250, blank=True,
        unique=True, null=True)

    objects = CategoryManager()

    class Meta:
        verbose_name = _("Category")
        verbose_name_plural = _("Categories")

    def __str__(self):
        return self.category


class Quiz(models.Model):

    title = models.CharField(
        verbose_name=_("Title"),
        max_length=60, blank=False)

    description = models.TextField(
        verbose_name=_("Description"),
        blank=True, help_text=_("a description of the quiz"))

    url = models.SlugField(
        max_length=60, blank=False,
        help_text=_("a user friendly url"),
        verbose_name=_("user friendly url"))

    category = models.ForeignKey(
        Category, null=True, blank=True,
        verbose_name=_("Category"), on_delete=models.CASCADE)

    random_order = models.BooleanField(
        blank=False, default=False,
        verbose_name=_("Random Order"),
        help_text=_("Display the questions in "
                    "a random order or as they "
                    "are set?"))

    max_questions = models.PositiveIntegerField(
        blank=True, null=True, verbose_name=_("Max Questions"),
        help_text=_("Number of questions to be answered on each attempt."))

    answers_at_end = models.BooleanField(
        blank=False, default=False,
        help_text=_("Correct answer is NOT shown after question."
                    " Answers displayed at the end."),
        verbose_name=_("Answers at end"))

    exam_paper = models.BooleanField(
        blank=False, default=False,
        help_text=_("If yes, the result of each"
                    " attempt by a user will be"
                    " stored. Necessary for marking."),
        verbose_name=_("Exam Paper"))

    single_attempt = models.BooleanField(
        blank=False, default=False,
        help_text=_("If yes, only one attempt by"
                    " a user will be permitted."
                    " Non users cannot sit this exam."),
        verbose_name=_("Single Attempt"))

    pass_mark = models.SmallIntegerField(
        blank=True, default=0,
        verbose_name=_("Pass Mark"),
        help_text=_("Percentage required to pass exam."),
        validators=[MaxValueValidator(100)])

    success_text = models.TextField(
        blank=True, help_text=_("Displayed if user passes."),
        verbose_name=_("Success Text"))

    fail_text = models.TextField(
        verbose_name=_("Fail Text"),
        blank=True, help_text=_("Displayed if user fails."))

    draft = models.BooleanField(
        blank=True, default=False,
        verbose_name=_("Draft"),
        help_text=_("If yes, the quiz is not displayed"
                    " in the quiz list and can only be"
                    " taken by users who can edit"
                    " quizzes."))

    def save(self, force_insert=False, force_update=False, *args, **kwargs):
        self.url = re.sub('\s+', '-', self.url).lower()

        self.url = ''.join(letter for letter in self.url if
                           letter.isalnum() or letter == '-')

        if self.single_attempt is True:
            self.exam_paper = True

        if self.pass_mark > 100:
            raise ValidationError('%s is above 100' % self.pass_mark)

        super(Quiz, self).save(force_insert, force_update, *args, **kwargs)

    class Meta:
        verbose_name = _("Quiz")
        verbose_name_plural = _("Quizzes")

    def __str__(self):
        return self.title

    def get_questions(self):
        return self.question_set.all().select_subclasses()

    @property
    def get_max_score(self):
        return self.get_questions().count()

    def anon_score_id(self):
        return str(self.id) + "_score"

    def anon_q_list(self):
        return str(self.id) + "_q_list"

    def anon_q_data(self):
        return str(self.id) + "_data"


# progress manager
class ProgressManager(models.Manager):

    def new_progress(self, user):
        new_progress = self.create(user=user,
                                   score="")
        new_progress.save()
        return new_progress


class Progress(models.Model):
    """
    Progress is used to track an individual signed in users score on different
    quiz's and categories
    Data stored in csv using the format:
        category, score, possible, category, score, possible, ...
    """
    user = models.OneToOneField(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE)

    score = models.CharField(validators=[validate_comma_separated_integer_list], max_length=1024,
                                              verbose_name=_("Score"))

    correct_answer = models.CharField(max_length=10, verbose_name=_('Correct Answers'))

    wrong_answer = models.CharField(max_length=10, verbose_name=_('Wrong Answers')) 

    objects = ProgressManager()

    class Meta:
        verbose_name = _("User Progress")
        verbose_name_plural = _("User progress records")

    @property
    def list_all_cat_scores(self):
        """
        Returns a dict in which the key is the category name and the item is
        a list of three integers.
        The first is the number of questions correct,
        the second is the possible best score,
        the third is the percentage correct.
        The dict will have one key for every category that you have defined
        """
        score_before = self.score
        output = {}

        for cat in Category.objects.all():
            to_find = re.escape(cat.category) + r",(\d+),(\d+),"
            #  group 1 is score, group 2 is highest possible

            match = re.search(to_find, self.score, re.IGNORECASE)

            if match:
                score = int(match.group(1))
                possible = int(match.group(2))

                try:
                    percent = int(round((float(score) / float(possible))
                                        * 100))
                except:
                    percent = 0

                output[cat.category] = [score, possible, percent]

            else:  # if category has not been added yet, add it.
                self.score += cat.category + ",0,0,"
                output[cat.category] = [0, 0]

        if len(self.score) > len(score_before):
            # If a new category has been added, save changes.
            self.save()

        return output

    def update_score(self, question, score_to_add=0, possible_to_add=0):
        """
        Pass in question object, amount to increase score
        and max possible.
        Does not return anything.
        """
        category_test = Category.objects.filter(category=question.category)\
                                        .exists()

        if any([item is False for item in [category_test,
                                           score_to_add,
                                           possible_to_add,
                                           isinstance(score_to_add, int),
                                           isinstance(possible_to_add, int)]]):
            return _("error"), _("category does not exist or invalid score")

        to_find = re.escape(str(question.category)) +\
            r",(?P<score>\d+),(?P<possible>\d+),"

        match = re.search(to_find, self.score, re.IGNORECASE)

        if match:
            updated_score = int(match.group('score')) + abs(score_to_add)
            updated_possible = int(match.group('possible')) +\
                abs(possible_to_add)

            new_score = ",".join(
                [
                    str(question.category),
                    str(updated_score),
                    str(updated_possible), ""
                ])

            # swap old score for the new one
            self.score = self.score.replace(match.group(), new_score)
            self.save()

        else:
            #  if not present but existing, add with the points passed in
            self.score += ",".join(
                [
                    str(question.category),
                    str(score_to_add),
                    str(possible_to_add),
                    ""
                ])
            self.save()

    def show_exams(self):
        """
        Finds the previous quizzes marked as 'exam papers'.
        Returns a queryset of complete exams.
        """
        return Sitting.objects.filter(user=self.user, complete=True)

    def __str__(self):
        return self.user.username + ' - '  + self.score


class SittingManager(models.Manager):

    def new_sitting(self, user, quiz):
        if quiz.random_order is True:
            question_set = quiz.question_set.all() \
                                            .select_subclasses() \
                                            .order_by('?')
        else:
            question_set = quiz.question_set.all() \
                                            .select_subclasses()

        question_set = [item.id for item in question_set]

        if len(question_set) == 0:
            raise ImproperlyConfigured('Question set of the quiz is empty. '
                                       'Please configure questions properly')

        if quiz.max_questions and quiz.max_questions < len(question_set):
            question_set = question_set[:quiz.max_questions]

        questions = ",".join(map(str, question_set)) + ","

        new_sitting = self.create(user=user,
                                  quiz=quiz,
                                  question_order=questions,
                                  question_list=questions,
                                  incorrect_questions="",
                                  current_score=0,
                                  complete=False,
                                  user_answers='{}')
        return new_sitting

    def user_sitting(self, user, quiz):
        if quiz.single_attempt is True and self.filter(user=user,
                                                       quiz=quiz,
                                                       complete=True)\
                                               .exists():
            return False

        try:
            sitting = self.get(user=user, quiz=quiz, complete=False)
        except Sitting.DoesNotExist:
            sitting = self.new_sitting(user, quiz)
        except Sitting.MultipleObjectsReturned:
            sitting = self.filter(user=user, quiz=quiz, complete=False)[0]
        return sitting


class Sitting(models.Model):
    """
    Used to store the progress of logged in users sitting a quiz.
    Replaces the session system used by anon users.
    Question_order is a list of integer pks of all the questions in the
    quiz, in order.
    Question_list is a list of integers which represent id's of
    the unanswered questions in csv format.
    Incorrect_questions is a list in the same format.
    Sitting deleted when quiz finished unless quiz.exam_paper is true.
    User_answers is a json object in which the question PK is stored
    with the answer the user gave.
    """

    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE)

    quiz = models.ForeignKey(Quiz, verbose_name=_("Quiz"), on_delete=models.CASCADE)

    question_order = models.CharField(validators=[validate_comma_separated_integer_list],
        max_length=1024, verbose_name=_("Question Order"))

    question_list = models.CharField(validators=[validate_comma_separated_integer_list],
        max_length=1024, verbose_name=_("Question List"))

    incorrect_questions = models.CharField(validators=[validate_comma_separated_integer_list],
        max_length=1024, blank=True, verbose_name=_("Incorrect questions"))

    current_score = models.IntegerField(verbose_name=_("Current Score"))

    complete = models.BooleanField(default=False, blank=False,
                                   verbose_name=_("Complete"))

    user_answers = models.TextField(blank=True, default='{}',
                                    verbose_name=_("User Answers"))

    start = models.DateTimeField(auto_now_add=True,
                                 verbose_name=_("Start"))

    end = models.DateTimeField(null=True, blank=True, verbose_name=_("End"))

    objects = SittingManager()

    class Meta:
        permissions = (("view_sittings", _("Can see completed exams.")),)

    def get_first_question(self):
        """
        Returns the next question.
        If no question is found, returns False
        Does NOT remove the question from the front of the list.
        """
        if not self.question_list:
            return False

        first, _ = self.question_list.split(',', 1)
        question_id = int(first)
        return Question.objects.get_subclass(id=question_id)

    def remove_first_question(self):
        if not self.question_list:
            return

        _, others = self.question_list.split(',', 1)
        self.question_list = others
        self.save()

    def add_to_score(self, points):
        self.current_score += int(points)
        self.save()

    @property
    def get_current_score(self):
        return self.current_score

    def _question_ids(self):
        return [int(n) for n in self.question_order.split(',') if n]

    @property
    def get_percent_correct(self):
        dividend = float(self.current_score)
        divisor = len(self._question_ids())
        if divisor < 1:
            return 0            # prevent divide by zero error

        if dividend > divisor:
            return 100

        correct = int(round((dividend / divisor) * 100))

        if correct >= 1:
            return correct
        else:
            return 0

    def mark_quiz_complete(self):
        self.complete = True
        self.end = now()
        self.save()

    def add_incorrect_question(self, question):
        """
        Adds uid of incorrect question to the list.
        The question object must be passed in.
        """
        if len(self.incorrect_questions) > 0:
            self.incorrect_questions += ','
        self.incorrect_questions += str(question.id) + ","
        if self.complete:
            self.add_to_score(-1)
        self.save()

    @property
    def get_incorrect_questions(self):
        """
        Returns a list of non empty integers, representing the pk of
        questions
        """
        return [int(q) for q in self.incorrect_questions.split(',') if q]

    def remove_incorrect_question(self, question):
        current = self.get_incorrect_questions
        current.remove(question.id)
        self.incorrect_questions = ','.join(map(str, current))
        self.add_to_score(1)
        self.save()

    @property
    def check_if_passed(self):
        return self.get_percent_correct >= self.quiz.pass_mark

    @property
    def result_message(self):
        if self.check_if_passed:
            return self.quiz.success_text
        else:
            return self.quiz.fail_text

    def add_user_answer(self, question, guess):
        current = json.loads(self.user_answers)
        current[question.id] = guess
        self.user_answers = json.dumps(current)
        self.save()

    def get_questions(self, with_answers=False):
        question_ids = self._question_ids()
        questions = sorted(
            self.quiz.question_set.filter(id__in=question_ids)
                                  .select_subclasses(),
            key=lambda q: question_ids.index(q.id))

        if with_answers:
            user_answers = json.loads(self.user_answers)
            for question in questions:
                question.user_answer = user_answers[str(question.id)]

        return questions

    @property
    def questions_with_user_answers(self):
        return {
            q: q.user_answer for q in self.get_questions(with_answers=True)
        }

    @property
    def get_max_score(self):
        return len(self._question_ids())

    def progress(self):
        """
        Returns the number of questions answered so far and the total number of
        questions.
        """
        answered = len(json.loads(self.user_answers))
        total = self.get_max_score
        return answered, total


class Question(models.Model):
    """
    Base class for all question types.
    Shared properties placed here.
    """

    quiz = models.ManyToManyField(Quiz,
                                  verbose_name=_("Quiz"),
                                  blank=True)

    category = models.ForeignKey(Category,
                                 verbose_name=_("Category"),
                                 blank=True,
                                 null=True, on_delete=models.CASCADE)

    figure = models.ImageField(upload_to='uploads/%Y/%m/%d',
                               blank=True,
                               null=True,
                               verbose_name=_("Figure"))

    content = models.CharField(max_length=1000,
                               blank=False,
                               help_text=_("Enter the question text that "
                                           "you want displayed"),
                               verbose_name=_('Question'))

    explanation = models.TextField(max_length=2000,
                                   blank=True,
                                   help_text=_("Explanation to be shown "
                                               "after the question has "
                                               "been answered."),
                                   verbose_name=_('Explanation'))

    objects = InheritanceManager()

    class Meta:
        verbose_name = _("Question")
        verbose_name_plural = _("Questions")
        ordering = ['category']

    def __str__(self):
        return self.content


def upload_csv_file(instance, filename):
    qs = instance.__class__.objects.filter(user=instance.user)
    if qs.exists():
        num_ = qs.last().id + 1
    else:
        num_ = 1
    return f'csv/{num_}/{instance.user.username}/{filename}'