from django.contrib.auth.models import User from django.db import models from django.dispatch import receiver from django.db.models.signals import post_delete from os.path import sep, join, exists from os import remove from Main.commands import shell from Sprint.settings import MEDIA_ROOT from django.core.exceptions import ObjectDoesNotExist from json import loads base_dir = 'data' class ThreadSafe(models.Model): key = models.CharField(max_length=80, unique=True) class Course(models.Model): name = models.TextField() @property def teachers(self): return [UserInfo.objects.get(user=s.user) for s in Subscribe.objects.filter(user__is_staff=1, course=self)] @property def subscribes(self): return sorted(Subscribe.objects.filter(course=self), key=lambda s: s.user.email) @property def students(self): userinfo = lambda sub: sub.user.userinfo return sorted(Subscribe.objects.filter(course=self, is_assistant=False, user__is_staff=False), key=lambda s: userinfo(s).surname + userinfo(s).name + userinfo(s).middle_name) def __str__(self): return self.name class Block(models.Model): name = models.TextField() course = models.ForeignKey(Course, on_delete=models.CASCADE) time_start = models.DateTimeField() time_end = models.DateTimeField() opened = models.BooleanField(default=False) show_rating = models.BooleanField(default=True) priority = models.IntegerField(default=5) cheating_checking = models.BooleanField(default=False) @property def messages(self): return Message.objects.filter(task__block=self) def __str__(self): return self.name @property def tasks(self): return Task.objects.filter(block=self) @property def time_start_chrome(self): return self.time_start.strftime("%Y-%m-%dT%H:%M") @property def time_end_chrome(self): return self.time_end.strftime("%Y-%m-%dT%H:%M") @property def is_opened(self): return 'checked' if self.opened else '' @property def solutions(self): return reversed(Solution.objects.filter(task__block=self)) @property def subscribed_users(self): return [UserInfo.objects.get(user=s.user) for s in Subscribe.objects.filter(course=self.course)] @property def cheating_results_path(self): return join(MEDIA_ROOT, 'cheating_results', str(self.id)) @property def cheating_checked(self): return self.cheating_results != {} @property def cheating_results(self): return loads(open(self.cheating_results_path, 'r').read()) if exists(self.cheating_results_path) else {} @property def cheating_status(self): if self.cheating_checking: return 'Идет проверка' if not exists(self.cheating_results_path): return 'Еще не проверено' return 'Проверка завершена' class Restore(models.Model): code = models.TextField() user = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return self.user.username class Subscribe(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) course = models.ForeignKey(Course, on_delete=models.CASCADE) is_assistant = models.BooleanField(default=False) def __str__(self): return self.user.username + '|' + self.course.name @property def userinfo(self): return UserInfo.objects.get(user=self.user) @property def role(self): if self.user.is_superuser: return 'Администратор' if self.user.is_staff: return 'Преподаватель' return 'Ассистент' if self.is_assistant else 'Студент' class Task(models.Model): name = models.TextField() block = models.ForeignKey(Block, on_delete=models.CASCADE) legend = models.TextField(default='') input = models.TextField(default='') output = models.TextField(default='') specifications = models.TextField(default='') time_limit = models.IntegerField(default=10000) weight = models.FloatField(default=1.0) max_mark = models.IntegerField(default=10) max_solutions_count = models.IntegerField(default=10) show_result = models.BooleanField(default=True) show_details = models.BooleanField(default=False) full_solution = models.BooleanField(default=False) mark_formula = models.TextField(default='None') priority = models.IntegerField(default=5) @property def students_solutions(self): students = [sub.user for sub in Subscribe.objects.filter(course=self.block.course)] solutions = Solution.objects.filter(task=self) return [sol for sol in solutions if sol.user in students] @property def correct_count(self): solutions = self.students_solutions count = 0 for sol in solutions: res = sol.result.split('/') if len(res) == 2 and res[0] == res[1]: count += 1 return count @property def solutions_count(self): return len(self.students_solutions) @property def partially_passed(self): solutions = self.students_solutions count = 0 for sol in solutions: res = sol.result.split('/') if len(res) == 2 and res[0] != res[1]: count += 1 return count @property def solutions_with_error(self): return self.solutions_count - self.correct_count - self.partially_passed @property def samples(self): return [{ 'input': file, 'output': file.answer } for file in ExtraFile.objects.filter(task=self, sample=True).order_by('filename')] def __hash__(self): return self.id @property def showable(self): return 'checked' if self.show_details else '' def __str__(self): return self.name def tests_path(self): return join(base_dir, 'tests', str(self.id) + '.cs') @property def tests_text(self): try: return open(self.tests_path(), 'r').read() except FileNotFoundError: with open(self.tests_path(), 'w') as fs: pass return '' @property def tests_uploaded(self): from os.path import exists return exists(self.tests_path()) @property def files(self): return ExtraFile.objects.filter(task=self).order_by('filename') @property def files_for_compilation(self): return ExtraFile.objects.filter(task=self, for_compilation=True) @property def is_full_solution(self): return 'checked' if self.full_solution else '' class UserInfo(models.Model): surname = models.TextField() name = models.TextField() middle_name = models.TextField() group = models.TextField() user = models.OneToOneField(User, on_delete=models.CASCADE, null=True) def __eq__(self, obj): return str(self) == str(obj) def __hash__(self): return self.id def __str__(self): return "{} {} {}".format(self.surname, self.name, self.middle_name) class Solution(models.Model): task = models.ForeignKey(Task, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) result = models.TextField() details = models.TextField(default='') time_sent = models.DateTimeField(null=True) mark = models.IntegerField(null=True) comment = models.TextField(default='') def set_result(self, result): self.result = result if len(result.split('/')) != 1: result = int(result.split('/')[0]) try: self.mark = eval(self.task.mark_formula) except: self.mark = None self.save() def __str__(self): return str(self.id) def path(self): return join(base_dir, 'solutions', str(self.id)) def write_log(self, text): with self.log_fs as fs: fs.write(bytes(text + '\n', 'cp866')) @property def log_file(self): return join(MEDIA_ROOT, 'logs', str(self.id) + '.log') @property def log_text(self): try: return open(self.log_file, 'rb').read().decode('cp866') except FileNotFoundError: return '' @property def log_fs(self): return open(self.log_file, 'ab') @property def userinfo(self): return UserInfo.objects.get(user=self.user) @property def mark_property(self): return str(self.mark) if self.mark is not None else 'нет оценки' @property def mark_select(self): line = '' if self.mark: line += '' else: line += '' for mark in range(self.task.max_mark + 1): if mark == self.mark: line += ''.format(mark, mark) else: line += ''.format(mark, mark) return line @property def comment_property(self): return self.comment if self.comment else 'нет комментария' @staticmethod def get_files(path): from os import listdir from os.path import isfile, join files_dict = {} for file in listdir(path): if file == '__MACOSX' or file == 'test_folder' or file == 'bin' or file == 'obj' or file == '.vs': continue current_file = join(path, file) if isfile(current_file): if not current_file.endswith('.csproj') and not current_file.endswith('.sln'): try: files_dict[sep.join(current_file.split('solutions' + sep)[1].split(sep)[1:])] \ = open(current_file, 'rb').read().decode('UTF-8') except UnicodeDecodeError: pass else: files_dict = {**files_dict, **Solution.get_files(current_file)} return files_dict @property def files(self): return Solution.get_files(self.path()) @property def user_files(self): f = {} comp_files = [ef.filename for ef in ExtraFile.objects.filter(task=self.task, for_compilation=True)] for fi in self.files.keys(): if not fi in comp_files: f[fi] = self.files[fi] return f @property def passed_all_tests(self): spl = self.result.split('/') return len(spl) == 2 and spl[0] == spl[1] class System(models.Model): key = models.TextField() value = models.TextField() def __str__(self): return self.key class ExtraFile(models.Model): task = models.ForeignKey(Task, on_delete=models.CASCADE) filename = models.TextField() for_compilation = models.BooleanField(default=False) sample = models.BooleanField(default=False) @property def answer(self): try: return ExtraFile.objects.get(task=self.task, filename=self.filename + '.a') except ObjectDoesNotExist: return None @property def num(self): try: return int(self.filename.split('.')[0]) except ValueError: return '' @property def is_for_compilation(self): return 'checked' if self.for_compilation else '' @property def is_sample(self): return 'checked' if self.sample else '' @property def can_be_sample(self): try: int(self.filename) except: return False try: ans = ExtraFile.objects.get(task=self.task, filename=self.filename + '.a') except ObjectDoesNotExist: return False return self.readable and ans.readable @property def path(self): return join(MEDIA_ROOT, 'extra_files', str(self.id)) @property def readable(self): try: open(self.path, 'rb').read().decode('UTF-8') return True except UnicodeDecodeError: return False @property def text(self): return open(self.path, 'rb').read().decode('UTF-8') def __str__(self): return self.filename def write(self, data): with open(self.path, 'wb') as fs: fs.write(data) class Message(models.Model): task = models.ForeignKey(Task, on_delete=models.CASCADE) sender = models.ForeignKey(User, on_delete=models.CASCADE, null=True) reply_to = models.ForeignKey('Message', on_delete=models.CASCADE, null=True) for_all = models.BooleanField() text = models.TextField() @receiver(post_delete, sender=Task) def delete_task_hook(sender, instance, using, **kwargs): if exists(instance.tests_path()): from os import remove remove(instance.tests_path()) @receiver(post_delete, sender=Solution) def delete_solution_hook(sender, instance, using, **kwargs): if exists(instance.path()): from shutil import rmtree rmtree(instance.path()) shell('docker rm --force solution_container_{}'.format(instance.id)) shell('docker image rm solution_{}'.format(instance.id)) @receiver(post_delete, sender=ExtraFile) def delete_file_hook(sender, instance, using, **kwargs): try: if exists(instance.path): remove(instance.path) except ValueError: pass if instance.filename.endswith('.a'): try: t = ExtraFile.objects.get(task=instance.task, filename=instance.filename[:-2]) except ObjectDoesNotExist: return t.sample = False t.save()