diff --git a/physics/filters.py b/physics/filters.py
new file mode 100644
index 00000000..e3d82b25
--- /dev/null
+++ b/physics/filters.py
@@ -0,0 +1,63 @@
+import django_filters
+
+from .models import Category, Question, CidUserAnswer, Exam
+
+#from generic.filters import UserAnswerFilter
+from django.contrib.auth.models import User
+
+
+def get_exams(request):
+ if request is None:
+ return Exam.objects.none()
+
+ if not request.user.groups.filter(name="physics_checker").exists():
+ queryset = Exam.objects.filter(author__id=request.user.id)
+ else:
+ queryset = Exam.objects.all()
+
+ return queryset
+
+def get_authors(request):
+ if request is None:
+ return User.objects.none()
+
+ if not request.user.groups.filter(name="physics_checker").exists():
+ queryset = User.objects.filter(id=request.user.id)
+ else:
+ queryset = User.objects.all()
+
+ return queryset
+
+
+class QuestionFilter(django_filters.FilterSet):
+ exams = django_filters.ModelMultipleChoiceFilter(queryset=get_exams, null_label="No exam")
+ author = django_filters.ModelMultipleChoiceFilter(queryset=get_authors, null_label="No author")
+ category = django_filters.ModelChoiceFilter(queryset=Category.objects.all(),null_label="Uncategorised")
+ class Meta:
+ model = Question
+ fields = ("exams", "created_date")
+
+ def __init__(self, data=None, queryset=None, prefix=None, strict=None, user=None, request=None):
+ if not request.user.groups.filter(name="physics_checker").exists():
+ queryset = queryset.filter(open_access=True) | queryset.filter(author__id=request.user.id)
+ super(QuestionFilter, self).__init__(data=data, queryset=queryset, prefix=prefix, request=request)
+ pass
+
+class UserAnswerFilter(django_filters.FilterSet):
+ class Meta:
+ model= CidUserAnswer
+ fields = (
+ "cid",
+ #"question",
+ "answer",
+ #"answer_compare",
+ "exam",
+ "created",
+ "updated",
+ )
+
+ def __init__(self, data=None, queryset=None, prefix=None, strict=None, user=None, request=None):
+ if not request.user.groups.filter(name="physics_checker").exists():
+ queryset = queryset.filter(open_access=True) | queryset.filter(author__id=request.user.id)
+ super(UserAnswerFilter, self).__init__(data=data, queryset=queryset, prefix=prefix, request=request)
+ pass
\ No newline at end of file
diff --git a/physics/forms.py b/physics/forms.py
new file mode 100755
index 00000000..f8baab48
--- /dev/null
+++ b/physics/forms.py
@@ -0,0 +1,34 @@
+from django.forms import (
+ Form,
+ ModelForm,
+ ModelMultipleChoiceField,
+ ModelChoiceField,
+ ChoiceField,
+ CharField,
+)
+from django.forms import inlineformset_factory
+
+from .models import (
+ Question,
+ CidUserAnswer,
+ Exam,
+)
+
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.forms.widgets import RadioSelect, TextInput, Textarea
+
+
+class CidUserAnswerForm(ModelForm):
+ class Meta:
+ model = CidUserAnswer
+ fields = ["answer"]
+
+ def __init__(self, *args, **kwargs):
+ super(CidUserAnswerForm, self).__init__(*args, **kwargs)
+ self.fields['answer'].required = False
+
+# This should be made generic?
+class ExamForm(ModelForm):
+ class Meta:
+ model = Exam
+ fields = ["name", "time_limit", "exam_mode", "active"]
\ No newline at end of file
diff --git a/physics/tables.py b/physics/tables.py
new file mode 100644
index 00000000..08bb5aad
--- /dev/null
+++ b/physics/tables.py
@@ -0,0 +1,84 @@
+from django.utils.safestring import mark_safe
+import django_tables2 as tables
+from django_tables2.utils import A
+
+from .models import Question, CidUserAnswer
+
+from django.utils.html import format_html
+
+#from easy_thumbnails.files import get_thumbnailer
+
+#from easy_thumbnails.exceptions import InvalidImageFormatError
+
+#from generic.tables import UserAnswerTable
+
+
+#class ImageColumn(tables.Column):
+# def render(self, value):
+# try:
+# thumbnailer = get_thumbnailer(value)
+# return format_html('
', thumbnailer["exam-list"])
+# except InvalidImageFormatError:
+# return format_html('Invalid image url', value)
+
+
+class QuestionTable(tables.Table):
+ #edit = tables.LinkColumn(
+ # "physics:physics_question_update", text="Edit", args=[A("pk")], orderable=False
+ #)
+ view = tables.LinkColumn(
+ "physics:question_detail", text="View", args=[A("pk")], orderable=False
+ )
+ #delete = tables.LinkColumn(
+ # "physics:question_delete", text="Delete", args=[A("pk")], orderable=False
+ #)
+ exams = tables.ManyToManyColumn(verbose_name="Exams")
+
+ selection = tables.CheckBoxColumn(accessor="pk", orderable=False)
+
+ stem = tables.Column()
+ answers = tables.TemplateColumn("- {{ record.a_answer|safe}}
- {{ record.b_answer|safe}}
- {{ record.c_answer|safe}}
- {{ record.d_answer|safe}}
- {{ record.e_answer|safe}}
")
+
+ class Meta:
+ model = Question
+ template_name = "django_tables2/bootstrap4.html"
+ fields = (
+ #"stem",
+ #"a_answer",
+ #"b_answer",
+ #"c_answer",
+ #"d_answer",
+ #"e_answer",
+ "best_answer",
+ "created_date",
+ "category",
+ "author",
+ )
+ sequence = ("view", "stem", "answers", "exams")
+
+ def render_stem(self, value):
+ return mark_safe(value)
+
+
+class UserAnswerTable(tables.Table):
+ select = tables.CheckBoxColumn(accessor=("pk"))
+ #delete = tables.LinkColumn(
+ # "physics:user_answer_delete", text="Delete", args=[A("pk")], orderable=False
+ #)
+ view = tables.LinkColumn('physics:user_answer_view',
+ text='View',
+ args=[A('pk')],
+ orderable=False)
+ class Meta:
+ model = CidUserAnswer
+ template_name = "django_tables2/bootstrap4.html"
+ fields = (
+ "cid",
+ "question",
+ "answer",
+ #"answer_compare",
+ "exam",
+ "created",
+ "updated",
+ )
+
diff --git a/physics/urls.py b/physics/urls.py
index 55108c4a..93228f27 100644
--- a/physics/urls.py
+++ b/physics/urls.py
@@ -7,6 +7,7 @@ urlpatterns = [
# path('', views.question_list, name='question_list'),
path("", views.PhysicsExamViews.index, name="index"),
#path("question/", views.QuestionView.as_view(), name="question_view"),
+ path("question/", views.QuestionView.as_view(), name="question_view"),
path("question//", views.question_detail, name="question_detail"),
#path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"),
#path("question//update", views.QuestionUpdate.as_view(), name="anatomy_question_update"),
@@ -25,4 +26,18 @@ urlpatterns = [
path("exam/available", views.active_exams, name="active_exams"),
#path("exam/json/", views.exam_json, name="exam_json"),
#path("exam/json//recreate", views.exam_json_recreate, name="exam_json_recreate"),
+ path("user_answers/", views.UserAnswerTableView.as_view(), name="user_answer_table_view"),
+ path(
+ "user_answers/", views.UserAnswerView.as_view(), name="user_answer_view"
+ ),
+ path(
+ "user_answers//delete",
+ views.UserAnswerDelete.as_view(),
+ name="user_answer_delete",
+ ),
+ path(
+ "user_answers/delete",
+ views.GenericViews.user_answer_delete_multiple,
+ name="user_answer_delete_multiple",
+ ),
]
diff --git a/physics/views.py b/physics/views.py
index 8289e027..10e93723 100644
--- a/physics/views.py
+++ b/physics/views.py
@@ -20,6 +20,7 @@ from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
+from reversion.views import RevisionMixin
from .models import Question, Category, Exam, CidUserAnswer
@@ -31,13 +32,15 @@ import json
import statistics
import plotly.express as px
-from generic.views import ExamViews
+from generic.views import ExamCloneMixin, ExamViews, GenericViewBase
from rest_framework import viewsets, permissions
from rest_framework.pagination import PageNumberPagination
from django.core.exceptions import PermissionDenied
+from .forms import ExamForm
+
class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
@@ -253,9 +256,9 @@ def loadJsonAnswer(answer):
return False, JsonResponse({"success": False, "error": "invalid"})
# The model should catch invalid data but this should be less intensive
- max_int = 10000000
- if answer["cid"] > max_int or answer["eid"] > max_int:
- return False, JsonResponse({"success": False})
+ #max_int = 10000000
+ #if answer["cid"] > max_int or answer["eid"] > max_int:
+ # return False, JsonResponse({"success": False})
exam = get_object_or_404(Exam, pk=answer["eid"])
@@ -303,4 +306,20 @@ def loadJsonAnswer(answer):
return True, None
-PhysicsExamViews = ExamViews(Exam, Question, "physics", "physics", loadJsonAnswer)
\ No newline at end of file
+PhysicsExamViews = ExamViews(Exam, Question, "physics", "physics", loadJsonAnswer)
+
+GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam)
+
+class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
+ model = Exam
+ form_class = ExamForm
+
+ def form_valid(self, form):
+ self.object = form.save(commit=False)
+ self.object.save()
+
+ form.instance.author.add(self.request.user.id)
+ return super().form_valid(form)
+
+class ExamClone(ExamCloneMixin, ExamCreate):
+ """Clone exam view"""
\ No newline at end of file
diff --git a/sbas/filters.py b/sbas/filters.py
index 60f259bd..bdd8be19 100644
--- a/sbas/filters.py
+++ b/sbas/filters.py
@@ -10,7 +10,7 @@ def get_exams(request):
if request is None:
return Exam.objects.none()
- if not request.user.groups.filter(name="anatomy_checker").exists():
+ if not request.user.groups.filter(name="sba_checker").exists():
queryset = Exam.objects.filter(author__id=request.user.id)
else:
queryset = Exam.objects.all()
diff --git a/sbas/urls.py b/sbas/urls.py
index 5b246a04..36e29648 100644
--- a/sbas/urls.py
+++ b/sbas/urls.py
@@ -39,7 +39,7 @@ urlpatterns = [
),
path(
"user_answers/delete",
- views.user_answer_delete_multiple,
+ views.GenericViews.user_answer_delete_multiple,
name="user_answer_delete_multiple",
),
]
diff --git a/sbas/views.py b/sbas/views.py
index d30bb30f..a04b2827 100644
--- a/sbas/views.py
+++ b/sbas/views.py
@@ -37,7 +37,7 @@ import json
import statistics
import plotly.express as px
-from generic.views import ExamViews
+from generic.views import ExamViews, GenericViewBase
from rest_framework import viewsets, permissions
from rest_framework.pagination import PageNumberPagination
@@ -387,15 +387,17 @@ class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
template_name = "user_answer_delete.html"
success_url = reverse_lazy("sbas:user_answer_table_view")
-@user_passes_test(lambda u: u.is_superuser)
-def user_answer_delete_multiple(request):
- if request.is_ajax():
- print(request.POST["answer_ids"])
- answer_ids = json.loads(request.POST["answer_ids"])
+#@user_passes_test(lambda u: u.is_superuser)
+#def user_answer_delete_multiple(request):
+# if request.is_ajax():
+# print(request.POST["answer_ids"])
+# answer_ids = json.loads(request.POST["answer_ids"])
+#
+# # We could probably delete them all at once....
+# for id in answer_ids:
+# CidUserAnswer.objects.get(pk=id).delete()
+# return JsonResponse({"status": "success"})
+# return JsonResponse({"status": "error"})
- # We could probably delete them all at once....
- for id in answer_ids:
- CidUserAnswer.objects.get(pk=id).delete()
- return JsonResponse({"status": "success"})
- return JsonResponse({"status": "error"})
+GenericViews = GenericViewBase("sbas", Question, CidUserAnswer, Exam)
\ No newline at end of file