Django-CMS 3.0.3 发布页面从插件django-cms-saq复制数据

Django-CMS 3.0.3 Publishing a page duplicates data from plugin django-cms-saq

Django-cms-saq 已针对 2.4.x 进行测试。我正在尝试更新程序以使用 3.0.X.

到目前为止,我已经更新了所有导入,但遇到了异常错误。当我向页面添加问题(插件)并点击发布时,它会在数据库中创建问题的两个副本(可通过管理站点查看)。删除任一副本都会从已发布的页面中删除这两个副本,但会使问题处于编辑模式。

我该如何解决这个问题?

我将在此处包括一些文件。如果您需要任何其他文件,请告诉我。

请注意,我正在尝试添加一道选择题。

来自 models.py:

from django.db import models
from django.db.models import Max, Sum

from cms.models import CMSPlugin, Page, Placeholder
from cms.models.fields import PageField
from taggit.managers import TaggableManager

from djangocms_text_ckeditor.models import AbstractText

...

class Question(CMSPlugin):
    QUESTION_TYPES = [
        ('S', 'Single-choice question'),
        ('M', 'Multi-choice question'),
        ('F', 'Free-text question'),
    ]

    slug = models.SlugField(
        help_text="A slug for identifying answers to this specific question "
        "(allows multiple only for multiple languages)")
    tags = TaggableManager(blank=True)
    label = models.CharField(max_length=512, blank=True)
    help_text = models.CharField(max_length=512, blank=True)
    question_type = models.CharField(max_length=1, choices=QUESTION_TYPES)
    optional = models.BooleanField(
        default=False,
        help_text="Only applies to free text questions",
    )

    depends_on_answer = models.ForeignKey(
        Answer, null=True, blank=True, related_name='trigger_questions')

    def copy_relations(self, oldinstance):
        for answer in oldinstance.answers.all():
            answer.pk = None
            answer.question = self
            answer.save()

        self.depends_on_answer = oldinstance.depends_on_answer

    @staticmethod
    def all_in_tree(page):
        root = page.get_root()
        # Remember that there might be questions on the root page as well!
        tree = root.get_descendants() | Page.objects.filter(id=root.id)
        placeholders = Placeholder.objects.filter(page__in=tree)
        return Question.objects.filter(placeholder__in=placeholders)

    @staticmethod
    def all_in_page(page):
        placeholders = Placeholder.objects.filter(page=page)
        return Question.objects.filter(placeholder__in=placeholders)

    def score(self, answers):
        if self.question_type == 'F':
            return 0
        elif self.question_type == 'S':
            return self.answers.get(slug=answers).score
        elif self.question_type == 'M':
            answers_list = answers.split(',')
            return sum([self.answers.get(slug=a).score for a in answers_list])

    @property
    def max_score(self):
        if not hasattr(self, '_max_score'):
            if self.question_type == "S":
                self._max_score = self.answers.aggregate(
                    Max('score'))['score__max']
            elif self.question_type == "M":
                self._max_score = self.answers.aggregate(
                    Sum('score'))['score__sum']
            else:
                self._max_score = None  # don't score free-text answers
        return self._max_score

    def percent_score_for_user(self, user):
        if self.max_score:
            try:
                score = Submission.objects.get(
                    question=self.slug,
                    user=user,
                ).score
            except Submission.DoesNotExist:
                return 0
            return 100.0 * score / self.max_score
        else:
            return None

    def __unicode__(self):
        return self.slug

...

来自cms_plugins.py

import itertools
import operator

from django.contrib import admin
from django.utils.translation import ugettext as _

from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool

from cms_saq.models import Question, Answer, GroupedAnswer, Submission, \
        FormNav, ProgressBar, SectionedScoring, ScoreSection, BulkAnswer, \
        QuestionnaireText, SubmissionSetReview


from djangocms_text_ckeditor.cms_plugins import TextPlugin
from djangocms_text_ckeditor.models import Text

from bs4 import BeautifulSoup

...

class QuestionPlugin(CMSPluginBase):
    model = Question
    module = "SAQ"
    inlines = [AnswerAdmin]
    exclude = ('question_type',)

    def render(self, context, instance, placeholder):
        user = context['request'].user

        submission_set = None

        triggered = True
        depends_on = None
        if instance.depends_on_answer:
            depends_on = instance.depends_on_answer.pk
            try:
                Submission.objects.get(
                    user=user,
                    question=instance.depends_on_answer.question.slug,
                    answer=instance.depends_on_answer.slug,
                    submission_set=submission_set,
                )
                triggered = True
            except:
                triggered = False

        extra = {
            'question': instance,
            'answers': instance.answers.all(),
            'triggered': triggered,
            'depends_on': depends_on,
        }

        if user.is_authenticated():
            try:
                extra['submission'] = Submission.objects.get(
                    user=user,
                    question=instance.slug,
                    submission_set=submission_set,
                )
            except Submission.DoesNotExist:
                pass

        context.update(extra)
        return context

    def save_model(self, request, obj, form, change):
        obj.question_type = self.question_type
        super(QuestionPlugin, self).save_model(request, obj, form, change)


...


class MultiChoiceQuestionPlugin(QuestionPlugin):
    name = "Multi Choice Question"
    render_template = "cms_saq/multi_choice_question.html"
    question_type = "M"
    exclude = ('question_type', 'help_text')

...

plugin_pool.register_plugin(SingleChoiceQuestionPlugin)
plugin_pool.register_plugin(MultiChoiceQuestionPlugin)
plugin_pool.register_plugin(DropDownQuestionPlugin)
plugin_pool.register_plugin(GroupedDropDownQuestionPlugin)
plugin_pool.register_plugin(FreeTextQuestionPlugin)
plugin_pool.register_plugin(FreeNumberQuestionPlugin)
plugin_pool.register_plugin(FormNavPlugin)
plugin_pool.register_plugin(SubmissionSetReviewPlugin)
plugin_pool.register_plugin(SectionedScoringPlugin)
plugin_pool.register_plugin(ProgressBarPlugin)
plugin_pool.register_plugin(BulkAnswerPlugin)
plugin_pool.register_plugin(SessionDefinition)
plugin_pool.register_plugin(QuestionnaireTextPlugin)
plugin_pool.register_plugin(TranslatedTextPlugin)

来自cms_app.py:

from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _

class CMSSaq(CMSApp):
    name = _("Self Assessment")
    urls = ["cms_saq.urls"]

apphook_pool.register(CMSSaq)

附加信息:

当试图通过其 slug Question.objects.get(slug=question_slug) 获取问题对象时,这种重复观察会产生问题。这样的查询应该只有return一个问题。我们在这里得到的是两个问题 returned.

import re

from django.http import HttpResponse, HttpResponseBadRequest
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST, require_GET
from django.views.decorators.cache import never_cache
from django.utils import simplejson, datastructures
from django.conf import settings

from cms_saq.models import Question, Answer, Submission, SubmissionSet

ANSWER_RE = re.compile(r'^[\w-]+(,[\w-]+)*$')


@require_POST
def _submit(request):

    post_data = datastructures.MultiValueDict(request.POST)
    submission_set_tag = post_data.pop('submission_set_tag', '')

    for question_slug, answers in post_data.iteritems():

        # validate the question
        try:
            question = Question.objects.get(
                slug=question_slug,
                #placeholder__page__publisher_is_draft=False,
            )
        except Question.DoesNotExist:
            return HttpResponseBadRequest(
                "Invalid question '%s'" % question_slug,
            )

        # check answers is a list of slugs
        if question.question_type != 'F' and not ANSWER_RE.match(answers):
            return HttpResponseBadRequest("Invalid answers: %s" % answers)
        # validate and score the answer
        try:
            score = question.score(answers)
        except Answer.DoesNotExist:
            return HttpResponseBadRequest(
                "Invalid answer '%s:%s'" % (question_slug, answers)
            )

        # save, but don't update submissions belonging to an existing set
        filter_attrs = {
            'user': request.user.id,
            'question': question_slug,
            'submission_set': None,
        }

        attrs = {'answer': answers, 'score': score}

        rows = Submission.objects.filter(**filter_attrs).update(**attrs)

        if not rows:
            attrs.update(filter_attrs)
            Submission.objects.create(**attrs)

    # Create submission set if requested
    if submission_set_tag:
        submission_set_tag = submission_set_tag[0]

        if submission_set_tag:
            _create_submission_set(
                request, submission_set_tag
            )

    return HttpResponse("OK")

如果您在 CMS 中创建一个页面,并向其添加插件,一旦您点击发布,CMS 会及时创建每个插件的副本。这为该页面提供了一个实时版本,并使您能够对其进行更改,这些更改在草稿模式下进行,直到您再次点击发布。

这使您可以拥有网站的草稿版本,可以在其中制作 edits/changes 并在制作 public 之前完成定稿。

因此,每个插件有两个副本不是问题。

附带说明一下,如果您正在开发 CMS 网站,我强烈建议您加入 Google Plus 上的 CMS 用户组; https://plus.google.com/communities/107689498573071376044

更新

好的,所以 CMS 中的插件附加到页面上的占位符,并且该页面本身有两个版本。因为每个插件都附加到一个页面,所以您可以使用该关系来过滤您的插件。

如果您向管理员注册了您的插件,或者拒绝对其调用,您只是在查看存储在您的 table 中的内容,正如我所说,包括插件的草稿和实时版本。

因此,当您查询插件时,请执行此操作;

questions = Question.objects.filter(placeholder__page__publisher_is_draft=True)

这将为您提供附在草稿页上的所有问题。

唯一的缺点是插件保证附加到 Page() 对象,除非插件设置为 page_only = True 但我只对附加到页面的插件感兴趣所以它对我有用。有关详细信息,请参阅 docs

此外,如果您曾经向任何插件添加 CMS 页面链接,请不要忘记向您的模型字段添加类似的参数,以确保将页面对象限制为仅草稿;

page_link = models.ForeignKey(
    Page,
    limit_choices_to={'publisher_is_draft': True},
    help_text=_("Link to another page on the site."),
    on_delete=models.SET_NULL,
    related_name='myapp_page_link'
)