diff --git a/application/courses_app/admin.py b/application/courses_app/admin.py index 144c447..912c9a6 100644 --- a/application/courses_app/admin.py +++ b/application/courses_app/admin.py @@ -6,7 +6,7 @@ Section, ClassmatesCheckedTask, TaskOption, StudentResult, Check, TaskWithTick, \ TaskWithTickStudentResult, TaskWithTeacherCheckCheck, TaskWithTeacherCheck, \ TaskWithKeyword, TaskWithTeacherCheckOption, TaskWithKeywordOption, \ - TaskWithTeacherCheckResult, TaskWithKeywordResult, TaskWithTickInStream, StudentInCourse, CourseNews, Badge, BadgeForUser, CourseFAQ + TaskWithTeacherCheckResult, TaskWithKeywordResult, TaskWithTickInStream, StudentInCourse, CourseNews, Badge, BadgeForUser, CourseFAQ, TaskWithTeacherCheckInStream admin.site.register(StudentGroup) @@ -36,7 +36,7 @@ admin.site.register(Badge) admin.site.register(BadgeForUser) admin.site.register(CourseFAQ) - +admin.site.register(TaskWithTeacherCheckInStream) UserAdmin.fieldsets += ('Custom fields set', {'fields': ('role', 'group')}), admin.site.register(User, UserAdmin) diff --git a/application/courses_app/models.py b/application/courses_app/models.py index e9a6bb6..bf34848 100644 --- a/application/courses_app/models.py +++ b/application/courses_app/models.py @@ -304,7 +304,7 @@ class TaskWithTeacherCheckResult(models.Model): option = models.ForeignKey(TaskWithTeacherCheckOption, on_delete=models.CASCADE, related_name = 'task_with_teacher_results') description = models.CharField(max_length=1024, blank = True, null=True) perform = models.BooleanField(default=False) - on_check = models.BooleanField(default=True) + on_check = models.BooleanField(default=False) # был true file = models.FileField(upload_to='task_with_teacher_result_files/', null=True, blank=True) diff --git a/application/courses_app/serializers.py b/application/courses_app/serializers.py index ddebd72..1459f69 100644 --- a/application/courses_app/serializers.py +++ b/application/courses_app/serializers.py @@ -121,6 +121,7 @@ class SectionInCourseSerializer(serializers.ModelSerializer): task_with_tick_in_section = TaskWithTickInSectionSerializer(many = True) task_with_teacher_check_in_section = TaskWithTeacherCheckInSectionSerializer(many = True) task_with_keyword_in_section = TaskWithKeywordCheckInSectionSerializer(many = True) + fixed_tests_for_section = FixedTestSerializer(many = True) class Meta: model = Section fields = '__all__' diff --git a/application/courses_app/views.py b/application/courses_app/views.py index c259461..e0e3afb 100644 --- a/application/courses_app/views.py +++ b/application/courses_app/views.py @@ -60,6 +60,7 @@ from stats.models import StudentFixedTest + User = get_user_model() @@ -155,6 +156,17 @@ class GroupInStreamNewDetailView(generics.RetrieveAPIView): serializer_class = StudentStreamListSerializer permission_classes = [permissions.AllowAny] + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(instance) + newdata = dict(serializer.data) + course = Course.objects.filter(streams_on_a_course__id=self.kwargs['pk']) + isJoined = StudentInCourse.objects.filter(user=self.request.user, course_id=course[0].id) + if isJoined: + newdata.update({"status": "2"}) + else: + newdata.update({"status": "1"}) + return Response(newdata) class GroupInStreamNewDeleteUpdateView(generics.RetrieveUpdateDestroyAPIView): queryset = StudentStream.objects.all() @@ -403,6 +415,13 @@ def list(self, request, **kwargs): if TaskWithKeywordResult.objects.filter(user = self.request.user, option__task_id = task["id"], perform = True): student_tasks +=1 + for task in section["task_with_teacher_check_in_section"]: + all_tasks +=1 + # if TaskWithTeacherCheckResult.objects.filter(user = self.request.user, option__task_id = task["id"], perform = True): + # student_tasks +=1 + + for task in section["fixed_tests_for_section"]: + all_tasks +=1 try: newdata.update({"status": StudentInCourse.objects.get(course = newdata["id"], user = self.request.user).status, "all_tasks": all_tasks, "student_tasks": student_tasks}) @@ -440,7 +459,7 @@ def retrieve(self, request, *args, **kwargs): try: instance = self.get_object() except (Course.DoesNotExist, KeyError): - return Response({"error": "Requested Movie does not exist"}, status=status.HTTP_404_NOT_FOUND) + return Response({"error": "Requested Course does not exist"}, status=status.HTTP_404_NOT_FOUND) serializer = self.get_serializer(instance) print ('her') newdata = dict(serializer.data) @@ -509,8 +528,12 @@ def retrieve(self, request, *args, **kwargs): for task in section["fixed_tests_for_section"]: try: - if StudentFixedTest.objects.filter(test = task["id"], student = self.request.user, is_success = True): - task.update({"status": "1"}) + results = StudentFixedTest.objects.filter(test = task["id"], student = self.request.user, is_success = True).values('correct_answers_percent') + if results: + resultsArr = [] + for item in results: + resultsArr.append(item['correct_answers_percent']) + task.update({"status": "1", "results": resultsArr }) else: task.update({"status": "0"}) except: @@ -1016,6 +1039,15 @@ class TaskWithTickStudentResultCreateView(generics.CreateAPIView): serializer_class = CreateTaskWithTickStudentResultSerializer permission_classes = [permissions.AllowAny] + # def create(self, request, *args, **kwargs): + # request.data.update({"user": request.user.id}) + # serializer = self.get_serializer(data=request.data) + # serializer.is_valid(raise_exception=True) + # self.perform_create(serializer) + # headers = self.get_success_headers(serializer.data) + + # return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + class TaskWithTickStudentResultRetrieveView(generics.RetrieveAPIView): queryset = TaskWithTickStudentResult.objects.all() diff --git a/application/online_notebook_project/settings.py b/application/online_notebook_project/settings.py index 1ff65ba..d8badde 100644 --- a/application/online_notebook_project/settings.py +++ b/application/online_notebook_project/settings.py @@ -11,6 +11,11 @@ """ import os +from os.path import join, dirname +from dotenv import load_dotenv + +dotenv_path = join(dirname(__file__), '.env') +load_dotenv(dotenv_path) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -204,15 +209,25 @@ # ] +# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +# EMAIL_HOST = 'smtp.gmail.com' +# EMAIL_USE_TLS = True +# EMAIL_PORT = 587 +# EMAIL_HOST_USER = 'learnsqlitmo@gmail.com' +# EMAIL_HOST_PASSWORD = '*' + +# for dev yande email test EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_HOST = 'smtp.gmail.com' -EMAIL_USE_TLS = True -EMAIL_PORT = 587 -EMAIL_HOST_USER = 'learnsqlitmo@gmail.com' -EMAIL_HOST_PASSWORD = '*' +EMAIL_HOST = 'smtp.yandex.ru' +EMAIL_USE_TLS = False +EMAIL_USE_SSL = True +EMAIL_PORT = 465 +EMAIL_HOST_USER = os.getenv('EMAIL_LOGIN') +EMAIL_HOST_PASSWORD = os.getenv('EMAIL_PASSWORD') +DEFAULT_FROM_EMAIL = os.getenv('EMAIL_ADDRESS') DJOSER = { - 'PASSWORD_RESET_CONFIRM_URL': 'password/reset/confirm/{uid}/{token}', + 'PASSWORD_RESET_CONFIRM_URL': 'restore-confirm/{uid}/{token}', 'USERNAME_RESET_CONFIRM_URL': '#/username/reset/confirm/{uid}/{token}', 'ACTIVATION_URL': '#/activate/{uid}/{token}', 'SEND_ACTIVATION_EMAIL': False, diff --git a/application/requirements.txt b/application/requirements.txt index dd98f32..18f2891 100644 --- a/application/requirements.txt +++ b/application/requirements.txt @@ -33,4 +33,4 @@ pillow==7.0.0 PyMySQL[rsa] cryptography drf-yasg - +python-dotenv diff --git a/application/stats/models.py b/application/stats/models.py index 2c1198b..fa1e2fb 100644 --- a/application/stats/models.py +++ b/application/stats/models.py @@ -57,7 +57,7 @@ def finish_test(self): class StudentFixedTest(StudentTest): - test = models.ForeignKey(FixedTest, on_delete=models.PROTECT) + test = models.ForeignKey(FixedTest, on_delete=models.CASCADE) class Meta: unique_together = ('test', 'student', 'number_of_try') @@ -85,7 +85,7 @@ class Meta: class StudentFixedTestQuestion(StudentTestQuestion): - student_test = models.ForeignKey(StudentFixedTest, on_delete=models.PROTECT) + student_test = models.ForeignKey(StudentFixedTest, on_delete=models.CASCADE) class Meta: unique_together = ('student_test', 'question') diff --git a/application/stats/views.py b/application/stats/views.py index 95012bb..4692cfc 100644 --- a/application/stats/views.py +++ b/application/stats/views.py @@ -3,6 +3,7 @@ from rest_framework import generics, views, exceptions, viewsets, filters, mixins from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework import permissions from tests_builder import models as tests_models from tests_builder import serializers as tests_serializers @@ -76,6 +77,9 @@ class StudentRandomTestQuestionSet(StudentTestQuestionSet): class StudentFixedTestQuestionSet(StudentTestQuestionSet): + # TODO: пермишн нужен на студента + # здесь студент отв на по вопросы (POST) + permission_classes = [permissions.AllowAny] queryset = stats_models.StudentFixedTestQuestion.objects.all() serializer_class = serializers.StudentFixedTestQuestionSerializer filter_backends = (django_filters.DjangoFilterBackend, filters.OrderingFilter) @@ -115,6 +119,7 @@ def get_students_stats(self, request): class StudentFixedTestSet(StudentTestSet): + permission_classes = [permissions.AllowAny] queryset = stats_models.StudentFixedTest.objects.all() serializer_class = serializers.StudentFixedTestSerializer filter_backends = (django_filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) diff --git a/application/tests_builder/models.py b/application/tests_builder/models.py index af135b3..a6615c0 100644 --- a/application/tests_builder/models.py +++ b/application/tests_builder/models.py @@ -50,7 +50,7 @@ def get_questions_by_tags(cls, tags: List, count: Optional[int] = None): class Answer(models.Model): question = models.ForeignKey(Question, related_name='answers', on_delete=models.CASCADE) - text_answer = models.CharField(max_length=255) + text_answer = models.CharField(max_length=255, blank=True) is_correct = models.BooleanField() def __str__(self): @@ -78,14 +78,14 @@ def __str__(self): class FixedTestQuestion(models.Model): test = models.ForeignKey('FixedTest', on_delete=models.CASCADE, related_name='test_to_question') - question = models.ForeignKey('Question', on_delete=models.CASCADE, related_name='question_to_test') + question = models.ForeignKey('Question', null=True, on_delete=models.CASCADE, related_name='question_to_test') position = models.PositiveSmallIntegerField() class Meta: unique_together = (('test', 'question'), ('test', 'position')) - def __str__(self): - return f'{self.question.text_question}' + # def __str__(self): + # return f'{self.question.text_question}' class FixedTest(Test): diff --git a/application/tests_builder/serializers.py b/application/tests_builder/serializers.py index a9b09dc..24afa68 100644 --- a/application/tests_builder/serializers.py +++ b/application/tests_builder/serializers.py @@ -67,7 +67,7 @@ def update(self, instance, validated_data): class FixedTestQuestionSerializer(serializers.ModelSerializer): - question = QuestionSerializer() + # question = QuestionSerializer() class Meta: model = models.FixedTestQuestion @@ -77,7 +77,7 @@ class Meta: class CreateFixedTestQuestionSerializer(serializers.ModelSerializer): class Meta: model = models.FixedTestQuestion - fields = ('position', 'question') + fields = ('id', 'position', 'question') class FixedTestSerializer(serializers.ModelSerializer): @@ -89,6 +89,11 @@ class Meta: fields = '__all__' read_only_fields = ('created',) + def to_representation(self, instance): + response = super().to_representation(instance) + response["questions"] = sorted(response["questions"], key=lambda x: x["position"]) + return response + @transaction.atomic def create(self, validated_data): questions_data = validated_data.pop('test_to_question') @@ -139,3 +144,37 @@ def create(self, validated_data): random_test.tags.add(tag_data) return random_test + + + + + +class StudentAnswerSerializer(serializers.ModelSerializer): + class Meta: + model = models.Answer + fields = ('id', 'text_answer') + +class StudentQuestionSerializer(serializers.ModelSerializer): + answers = StudentAnswerSerializer(many=True) + class Meta: + model = models.Question + fields = ('id', 'text_question', 'answers') + +class StudentTestQuestionSerializer(serializers.ModelSerializer): + question = StudentQuestionSerializer() + class Meta: + model = models.FixedTestQuestion + fields = ('id', 'position', 'question') + +class StudentTestSerializer1(serializers.ModelSerializer): + questions = StudentTestQuestionSerializer(source='test_to_question', many=True) + created_by = serializers.HiddenField(default=serializers.CurrentUserDefault()) + + class Meta: + model = models.FixedTest + fields = '__all__' + + def to_representation(self, instance): + response = super().to_representation(instance) + response["questions"] = sorted(response["questions"], key=lambda x: x["position"]) + return response diff --git a/application/tests_builder/urls.py b/application/tests_builder/urls.py index 2d5c787..4075cd8 100644 --- a/application/tests_builder/urls.py +++ b/application/tests_builder/urls.py @@ -16,5 +16,6 @@ urlpatterns = [ path('answers/', views.AnswerCreate.as_view()), path('answers//', views.AnswerDetail.as_view()), + path('student_fixed_test/detail/', views.StudentFixedTestSet.as_view({'get':'retrieve'})), url(r'', include(router.urls)), ] diff --git a/application/tests_builder/views.py b/application/tests_builder/views.py index 79e33fe..bb7ef52 100644 --- a/application/tests_builder/views.py +++ b/application/tests_builder/views.py @@ -1,5 +1,10 @@ from django_filters import rest_framework as django_filters -from rest_framework import viewsets, generics, filters +from django.shortcuts import get_object_or_404 +from django.http import Http404 +from rest_framework import permissions +from rest_framework import viewsets, generics, filters, status +from rest_framework.decorators import action +from rest_framework.response import Response from tests_builder import models from tests_builder import serializers @@ -9,9 +14,16 @@ class FixedTestSet(viewsets.ModelViewSet): queryset = models.FixedTest.objects.all() serializer_class = serializers.FixedTestSerializer filter_backends = (django_filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) + # ordering_fields = ['questions__position'] # filterset_fields = ('name', 'section') # search_fields = ('name', 'section__name', 'created_by__username') +class StudentFixedTestSet(viewsets.ReadOnlyModelViewSet): + """Получение теста с вопросами(с текстом вопроса) и ответами (без is_correct)""" + # TODO: возможно сделать другой пермишн + permission_classes = [permissions.AllowAny] + queryset = models.FixedTest.objects.all() + serializer_class = serializers.StudentTestSerializer1 class FixedTestQuestionSet(viewsets.ModelViewSet): queryset = models.FixedTestQuestion.objects.all() @@ -19,7 +31,18 @@ class FixedTestQuestionSet(viewsets.ModelViewSet): filter_backends = (django_filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # filterset_fields = ('test',) # search_fields = ('test__name', 'question__text_question') - + def destroy(self, request, *args, **kwargs): + question = self.get_object() + test = question.test + posStart = question.position + 1 + question.delete() + questionsByTest = models.FixedTestQuestion.objects.filter(test=test) + for question in questionsByTest: + if question.position >= posStart: + question.position -= 1 + question.save() + + return Response(status=status.HTTP_204_NO_CONTENT) class RandomTestSet(viewsets.ModelViewSet): queryset = models.RandomTest.objects.all() diff --git a/docker-compose.yml b/docker-compose.yml index 9838c22..6e597ea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,8 +42,8 @@ services: - REACT_APP_API_SCHEMA=http - REACT_APP_API_PORT=8005 - PORT=3000 -# depends_on: -# - backend + depends_on: + - backend volumes: node-modules: