.
This commit is contained in:
+1
-1
@@ -1891,7 +1891,7 @@ class GenericViewBase:
|
|||||||
"rapids": "rapid_checker",
|
"rapids": "rapid_checker",
|
||||||
"anatomy": "anatomy_checker",
|
"anatomy": "anatomy_checker",
|
||||||
"longs": "longs_checker",
|
"longs": "longs_checker",
|
||||||
"sbas": "sbas_checker",
|
"sbas": "sba_checker",
|
||||||
"physics": "physics_checker",
|
"physics": "physics_checker",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+117
@@ -14,11 +14,14 @@ from .models import (
|
|||||||
Question,
|
Question,
|
||||||
CidUserAnswer,
|
CidUserAnswer,
|
||||||
Exam,
|
Exam,
|
||||||
|
Category
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||||
|
|
||||||
|
from tinymce.widgets import TinyMCE
|
||||||
|
|
||||||
|
|
||||||
class CidUserAnswerForm(ModelForm):
|
class CidUserAnswerForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -37,3 +40,117 @@ class ExamForm(ExamFormMixin, ModelForm):
|
|||||||
class ExamAuthorForm(ExamAuthorFormMixin):
|
class ExamAuthorForm(ExamAuthorFormMixin):
|
||||||
class Meta(ExamAuthorFormMixin.Meta):
|
class Meta(ExamAuthorFormMixin.Meta):
|
||||||
model = Exam
|
model = Exam
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionForm(ModelForm):
|
||||||
|
|
||||||
|
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all())
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
# Django also includes a few javascript files necessary
|
||||||
|
# for the operation of this form element. You need to
|
||||||
|
# include <script src="/admin/jsi18n"></script>
|
||||||
|
# in the template.
|
||||||
|
css = {
|
||||||
|
"all": ["css/widgets.css"],
|
||||||
|
}
|
||||||
|
# Adding this javascript is crucial
|
||||||
|
js = ["jsi18n.js", "tesseract.min.js"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.user = kwargs.pop(
|
||||||
|
"user"
|
||||||
|
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
||||||
|
if kwargs.get("instance"):
|
||||||
|
# We get the 'initial' keyword argument or initialize it
|
||||||
|
# as a dict if it didn't exist.
|
||||||
|
initial = kwargs.setdefault("initial", {})
|
||||||
|
# The widget for a ModelMultipleChoiceField expects
|
||||||
|
# a list of primary key for the selected data.
|
||||||
|
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
||||||
|
|
||||||
|
ModelForm.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
super(QuestionForm, self).__init__(*args, **kwargs)
|
||||||
|
# self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100}
|
||||||
|
# self.fields['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100}
|
||||||
|
self.fields["category"] = ModelChoiceField(
|
||||||
|
required=True,
|
||||||
|
queryset=Category.objects.all(),
|
||||||
|
initial=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if self.user.groups.filter(name="sba_checker").exists():
|
||||||
|
exam_queryset = Exam.objects.all()
|
||||||
|
else:
|
||||||
|
exam_queryset = Exam.objects.filter(
|
||||||
|
author__id=self.user.id
|
||||||
|
) | Exam.objects.filter(open_access=True)
|
||||||
|
|
||||||
|
self.fields["exams"] = ModelMultipleChoiceField(
|
||||||
|
required=False,
|
||||||
|
queryset=exam_queryset,
|
||||||
|
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
# Get the unsaved Pizza instance
|
||||||
|
instance = ModelForm.save(self, False)
|
||||||
|
instance.save()
|
||||||
|
|
||||||
|
old_exams = instance.exams.all()
|
||||||
|
|
||||||
|
new_exams = self.cleaned_data["exams"]
|
||||||
|
|
||||||
|
for exam in old_exams:
|
||||||
|
if exam not in new_exams:
|
||||||
|
exam.exam_questions.remove(instance)
|
||||||
|
|
||||||
|
for exam in new_exams:
|
||||||
|
exam.exam_questions.add(instance)
|
||||||
|
|
||||||
|
self.save_m2m()
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Question
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
"stem",
|
||||||
|
"feedback",
|
||||||
|
"category",
|
||||||
|
"a_answer",
|
||||||
|
"a_feedback",
|
||||||
|
"b_answer",
|
||||||
|
"b_feedback",
|
||||||
|
"c_answer",
|
||||||
|
"c_feedback",
|
||||||
|
"d_answer",
|
||||||
|
"d_feedback",
|
||||||
|
"e_answer",
|
||||||
|
"e_feedback",
|
||||||
|
]
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
# "normal": RadioSelect(
|
||||||
|
# choices=[(True, 'Yes'),
|
||||||
|
# (False, 'No')])
|
||||||
|
"stem" : TinyMCE(attrs={'cols': 80, 'rows': 30}),
|
||||||
|
"feedback" : TinyMCE(attrs={'cols': 80, 'rows': 30}),
|
||||||
|
"a_answer" : TinyMCE(attrs={'cols': 80, 'rows': 5}, mce_attrs={'height': 140}),
|
||||||
|
"a_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 5}, mce_attrs={'height': 140}),
|
||||||
|
"b_answer" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
|
||||||
|
"c_answer" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
|
||||||
|
"d_answer" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
|
||||||
|
"e_answer" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
|
||||||
|
"b_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
|
||||||
|
"c_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
|
||||||
|
"d_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
|
||||||
|
"e_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
|
||||||
|
}
|
||||||
|
|
||||||
|
#widgets = {
|
||||||
|
# "structure": autocomplete.ModelSelect2(url="anatomy:structure-autocomplete")
|
||||||
|
#}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
Sbas:
|
Sbas:
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<a href="{% url 'sbas:exam_list' %}">Exams</a> /
|
<a href="{% url 'sbas:exam_list' %}">Exams</a> /
|
||||||
<a href="{% url 'sbas:question_view' %}">Questions</a>
|
<a href="{% url 'sbas:exam_create' %}" title="Create a new exam">Create Exam</a> /
|
||||||
|
<a href="{% url 'sbas:question_view' %}">Questions</a> /
|
||||||
|
<a href="{% url 'sbas:question_create' %}" title="Create a new question">Create Question</a>
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
/ <a href="{% url 'sbas:user_answer_table_view' %}" title="User answers">Answers</a>
|
/ <a href="{% url 'sbas:user_answer_table_view' %}" title="User answers">Answers</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="question">
|
<div class="question">
|
||||||
|
<a href="{% url 'sbas:question_update' question.id %}" title="Edit the Question">Edit</a>
|
||||||
|
<a href="{% url 'sbas:question_clone' question.id %}" title="Clone the Question">Clone</a>
|
||||||
|
<a href="{% url 'sbas:question_delete' pk=question.pk %}" title="Delete the Question">Delete</a>
|
||||||
<a href="{% url 'admin:sbas_question_change' question.id %}"
|
<a href="{% url 'admin:sbas_question_change' question.id %}"
|
||||||
title="Edit the Question using the admin interface">Admin Edit</a>
|
title="Edit the Question using the admin interface">Admin Edit</a>
|
||||||
<a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='sbas' pk=question.pk %}')"> Add Note</a>
|
<a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='sbas' pk=question.pk %}')"> Add Note</a>
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends "sbas/base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ form.media }}
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Submit Question</h2>
|
||||||
|
<form action="" method="post" enctype="multipart/form-data" id="anatomyquestion-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" class="submit-button" value="Submit" name="submit">
|
||||||
|
<input type="submit" class="submit-button" value="Submit and Clone" name="submit-clone">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
+15
-1
@@ -10,7 +10,20 @@ app_name = "sbas"
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("question/", views.QuestionView.as_view(), name="question_view"),
|
path("question/", views.QuestionView.as_view(), name="question_view"),
|
||||||
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
||||||
# path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"),
|
path("question/create/", views.QuestionCreate.as_view(), name="question_create"),
|
||||||
|
path(
|
||||||
|
"question/<int:pk>/update",
|
||||||
|
views.QuestionUpdate.as_view(),
|
||||||
|
name="question_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"question/<int:pk>/clone", views.QuestionClone.as_view(), name="question_clone"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"question/<int:pk>/delete",
|
||||||
|
views.QuestionDelete.as_view(),
|
||||||
|
name="question_delete",
|
||||||
|
),
|
||||||
# path("question/<int:pk>/update", views.QuestionUpdate.as_view(), name="anatomy_question_update"),
|
# path("question/<int:pk>/update", views.QuestionUpdate.as_view(), name="anatomy_question_update"),
|
||||||
# path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
|
# path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
|
||||||
# path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"),
|
# path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"),
|
||||||
@@ -28,6 +41,7 @@ urlpatterns = [
|
|||||||
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
|
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
|
||||||
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
|
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
|
||||||
path("exam/available", views.active_exams, name="active_exams"),
|
path("exam/available", views.active_exams, name="active_exams"),
|
||||||
|
path("exam/create", views.ExamUpdate.as_view(), name="exam_create"),
|
||||||
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
||||||
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
||||||
# path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
|
# path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
|
||||||
|
|||||||
+109
-3
@@ -7,6 +7,7 @@ from django.shortcuts import render, get_object_or_404, redirect
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.forms.models import model_to_dict
|
||||||
|
|
||||||
# from django.contrib.auth.models import User
|
# from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||||
@@ -50,11 +51,16 @@ from django.core.exceptions import PermissionDenied
|
|||||||
from .tables import QuestionTable, UserAnswerTable
|
from .tables import QuestionTable, UserAnswerTable
|
||||||
from .filters import QuestionFilter, UserAnswerFilter
|
from .filters import QuestionFilter, UserAnswerFilter
|
||||||
|
|
||||||
|
from .forms import (
|
||||||
|
QuestionForm,
|
||||||
|
ExamForm,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AuthorOrCheckerRequiredMixin(object):
|
class AuthorOrCheckerRequiredMixin(object):
|
||||||
def get_object(self, *args, **kwargs):
|
def get_object(self, *args, **kwargs):
|
||||||
obj = super().get_object(*args, **kwargs)
|
obj = super().get_object(*args, **kwargs)
|
||||||
if self.request.user.groups.filter(name="sbas_checker").exists():
|
if self.request.user.groups.filter(name="sba_checker").exists():
|
||||||
return obj
|
return obj
|
||||||
if self.request.user not in obj.author.all():
|
if self.request.user not in obj.author.all():
|
||||||
raise PermissionDenied() # or Http404
|
raise PermissionDenied() # or Http404
|
||||||
@@ -299,9 +305,109 @@ class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["app_name"] = "anatomy"
|
context["app_name"] = "sbas"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
class QuestionCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
|
||||||
|
model = Question
|
||||||
|
form_class = QuestionForm
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super(QuestionCreateBase, self).get_form_kwargs()
|
||||||
|
kwargs.update({"user": self.request.user})
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(QuestionCreateBase, self).get_context_data(**kwargs)
|
||||||
|
#if self.request.POST:
|
||||||
|
# context["answer_formset"] = AnswerFormSet(
|
||||||
|
# self.request.POST, self.request.FILES
|
||||||
|
# )
|
||||||
|
#else:
|
||||||
|
# context["answer_formset"] = AnswerFormSet()
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
|
||||||
|
self.object = form.save(commit=False)
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
form.instance.author.add(self.request.user.id)
|
||||||
|
|
||||||
|
context = self.get_context_data(form=form)
|
||||||
|
formset = context["answer_formset"]
|
||||||
|
|
||||||
|
if formset.is_valid():
|
||||||
|
response = super().form_valid(form)
|
||||||
|
formset.instance = self.object
|
||||||
|
formset.save()
|
||||||
|
# If the normal submit button is pressed we save as normal
|
||||||
|
if "submit" in self.request.POST:
|
||||||
|
return response
|
||||||
|
# else we redirect to the clone url
|
||||||
|
else:
|
||||||
|
return redirect("sbas:question_clone", pk=self.object.pk)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
class QuestionCreate(QuestionCreateBase):
|
||||||
|
|
||||||
|
# initial = {'laterality': AnatomyQuestion.NONE}
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
if "pk" in self.kwargs:
|
||||||
|
initial = super().get_initial()
|
||||||
|
exam = get_object_or_404(Exam, pk=self.kwargs["pk"])
|
||||||
|
|
||||||
|
initial["exams"] = [exam.id]
|
||||||
|
|
||||||
|
return initial
|
||||||
|
|
||||||
|
class QuestionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
||||||
|
model = Question
|
||||||
|
form_class = QuestionForm
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super(QuestionUpdate, self).get_form_kwargs()
|
||||||
|
kwargs.update({"user": self.request.user})
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(QuestionUpdate, self).get_context_data(**kwargs)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
|
||||||
|
# save exam orders (there must be a better way to do this)
|
||||||
|
#exam_orders = {}
|
||||||
|
#for exam in self.object.exams.all():
|
||||||
|
# exam_orders[exam] = list(exam.exam_questions.all())
|
||||||
|
# print(exam_orders[exam])
|
||||||
|
|
||||||
|
self.object = form.save(commit=False)
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
form.instance.author.add(self.request.user.id)
|
||||||
|
|
||||||
|
context = self.get_context_data(form=form)
|
||||||
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
class QuestionClone(QuestionCreateBase):
|
||||||
|
"""Clones a existing question"""
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
# print(self.request)
|
||||||
|
old_object = get_object_or_404(Question, pk=self.kwargs["pk"])
|
||||||
|
initial_data = model_to_dict(old_object, exclude=["id"])
|
||||||
|
|
||||||
|
# We don't want to clone the exam details
|
||||||
|
# exams = old_object.exams.all().values_list("id", flat=True)
|
||||||
|
|
||||||
|
# initial_data["exams"] = list(exams)
|
||||||
|
|
||||||
|
return initial_data
|
||||||
|
|
||||||
class UserAnswerView(LoginRequiredMixin, DetailView):
|
class UserAnswerView(LoginRequiredMixin, DetailView):
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
@@ -364,7 +470,7 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
|||||||
# This view should return a list exams available to a user.
|
# This view should return a list exams available to a user.
|
||||||
# """
|
# """
|
||||||
# user = self.request.user
|
# user = self.request.user
|
||||||
# if user.groups.filter(name="sbas_checker").exists():
|
# if user.groups.filter(name="sba_checker").exists():
|
||||||
# return Exam.objects.all()
|
# return Exam.objects.all()
|
||||||
#
|
#
|
||||||
# return Exam.objects.filter(author__id=user.id)
|
# return Exam.objects.filter(author__id=user.id)
|
||||||
|
|||||||
Reference in New Issue
Block a user