Example #1
0
class AbstractTableStyleCommand(polymorphic.models.PolymorphicModel):
    table = models.ForeignKey(
        Table,
        on_delete=models.CASCADE,
        related_name='style_commands',
    )

    start_column = models.IntegerField(
        default=0,
        help_text='Координаты начала: столбец',
    )

    start_row = models.IntegerField(
        default=0,
        help_text='Координаты начала: строка',
    )

    stop_column = models.IntegerField(
        default=-1,
        help_text='Координаты конца: столбец',
    )

    stop_row = models.IntegerField(
        help_text='Координаты конца: строка',
        default=-1,
    )

    class Meta:
        verbose_name = 'table style command'

    @property
    def start(self):
        return self.start_column, self.start_row

    @property
    def stop(self):
        return self.stop_column, self.stop_row

    @property
    def params(self):
        # param can be dotted path to the nested attribute, i.e. "font.name".
        # In this case we need get self.font.name, so we split param by "." and apply
        # functools.reduce with getattr
        return [
            functools.reduce(getattr, [self] + param.split('.'))
            for param in self.command_params
        ]

    def __str__(self):
        return '%s[%s:%s].%s(%s)' % (
            self.table,
            self.start,
            self.stop,
            self.command_name,
            ', '.join(map(str, self.params)),
        )

    def get_reportlab_style_command(self):
        return (self.command_name, self.start, self.stop) + tuple(self.params)
Example #2
0
class InlineQuestionnaireBlockChild(models.Model):
    parent = models.ForeignKey(
        InlineQuestionnaireBlock,
        related_name='children',
        on_delete=models.CASCADE,
    )

    block = models.ForeignKey(
        # TODO (andgein): Maybe it should be foreign key
        # to AbstractQuestionnaireBlock, not to Question?
        # In future some blocks may nice fit in InlineQuestionnaireBlock.
        'AbstractQuestionnaireQuestion',
        related_name='+',
        on_delete=models.CASCADE,
        help_text='Вопрос, вложенный в InlineBlock')

    xs_width = models.IntegerField(validators=[
        validators.MinValueValidator(1),
        validators.MaxValueValidator(12)
    ],
                                   help_text='Размер на телефонах. От 1 до 12',
                                   default=12)

    sm_width = models.IntegerField(
        validators=[
            validators.MinValueValidator(1),
            validators.MaxValueValidator(12)
        ],
        help_text=
        'Размер на устройствах шириной от 768 до 992 пикселей. От 1 до 12',
        default=12)

    md_width = models.IntegerField(
        validators=[
            validators.MinValueValidator(1),
            validators.MaxValueValidator(12)
        ],
        help_text='Размер на остальных устройствах. От 1 до 12',
        default=6)

    def __str__(self):
        return str(self.block)

    def save(self, *args, **kwargs):
        if self.parent.questionnaire_id != self.block.questionnaire_id:
            raise IntegrityError('Selected questionnaire and block '
                                 'should belong to one school')
        super().save(*args, **kwargs)
Example #3
0
class ScoreByPlaceAdditionalScorer(AbstractAdditionalScorer):
    """ Additional scores to first solved task teams """
    place = models.PositiveIntegerField(
        help_text='I.e. 1 for team who first solved the task')

    points = models.IntegerField()

    def __str__(self):
        return '%d additional points for %d team' % (self.points, self.place)
Example #4
0
class Document(models.Model):
    name = models.CharField(
        max_length=255,
        help_text='Не показывается школьникам. Например, «Договор 2016 с '
        'Берендеевыми Полянами»',
    )

    page_size = models.CharField(max_length=20,
                                 choices=PageSize.choices,
                                 validators=[PageSize.validator])

    left_margin = models.IntegerField(default=0)

    right_margin = models.IntegerField(default=0)

    top_margin = models.IntegerField(default=0)

    bottom_margin = models.IntegerField(default=0)

    def __str__(self):
        return self.name
Example #5
0
class EntranceLevel(models.Model):
    """
    Уровень вступительной работы.
    Для каждой задачи могут быть указаны уровни, для которых она предназначена.
    Уровень школьника определяется с помощью EntranceLevelLimiter'ов (например,
    на основе тематической анкеты из модуля topics, класса в школе
    или учёбы в других параллелях в прошлые годы)
    """
    school = models.ForeignKey('schools.School', on_delete=models.CASCADE)

    short_name = models.CharField(
        max_length=100,
        help_text='Используется в урлах. '
        'Лучше обойтись латинскими буквами, цифрами и подчёркиванием')

    name = models.CharField(max_length=100)

    order = models.IntegerField(default=0)

    tasks = models.ManyToManyField(
        'EntranceExamTask',
        blank=True,
        related_name='entrance_levels',
    )

    def __str__(self):
        return 'Уровень «%s» для %s' % (self.name, self.school)

    def __gt__(self, other):
        return self.order > other.order

    def __lt__(self, other):
        return self.order < other.order

    def __ge__(self, other):
        return self.order >= other.order

    def __le__(self, other):
        return self.order <= other.order

    class Meta:
        ordering = ('school_id', 'order')
Example #6
0
class AbstractQuestionnaireBlock(polymorphic.models.PolymorphicModel):
    questionnaire = models.ForeignKey('Questionnaire',
                                      on_delete=models.CASCADE)

    short_name = models.CharField(
        max_length=100,
        help_text='Используется в урлах. Лучше обойтись латинскими буквами, '
        'цифрами и подчёркиванием')

    order = models.IntegerField(
        default=0, help_text='Блоки выстраиваются по возрастанию порядка')

    is_question = False

    def __str__(self):
        return '%s. %s' % (self.questionnaire, self.short_name)

    class Meta:
        verbose_name = 'questionnaire block'
        unique_together = [('short_name', 'questionnaire'),
                           ('questionnaire', 'order')]
        ordering = ('questionnaire_id', 'order')
Example #7
0
class AbstractQuestionnaireBlock(polymorphic.models.PolymorphicModel):
    questionnaire = models.ForeignKey(
        'Questionnaire',
        on_delete=models.CASCADE,
        related_name='blocks',
    )

    short_name = models.CharField(
        max_length=100,
        help_text='Используется в урлах. Лучше обойтись латинскими буквами, '
        'цифрами и подчёркиванием')

    order = models.IntegerField(
        default=0, help_text='Блоки выстраиваются по возрастанию порядка')

    # TODO (andgein): it may be better to make another model with storing
    # top-level blocks of questionnaire (and orders of them)
    is_top_level = models.BooleanField(
        help_text='True, если блок находится на верхнем уровне вложенности',
        default=True,
    )

    is_question = False

    class Meta:
        verbose_name = 'questionnaire block'
        unique_together = [('short_name', 'questionnaire'),
                           ('questionnaire', 'order')]
        ordering = ('questionnaire_id', 'order')

    def __str__(self):
        obj = self.get_real_instance()
        if hasattr(obj, 'text'):
            description = '%s (%s)' % (obj.text, self.short_name)
        else:
            description = self.short_name
        return '%s. %s' % (self.questionnaire, description)

    def copy_to_questionnaire(self, to_questionnaire):
        """
        Make a copy of this block in the specified questionnaire.

        In order for this method to work correctly for each particular subclass
        the subclass should override _copy_fields_to_instance.

        :param to_questionnaire: A questionnaire to copy the block to.
        """
        fields_diff = (set(self.__class__._meta.get_fields()) -
                       set(AbstractQuestionnaireBlock._meta.get_fields()))
        fields_to_copy = list(
            filter(
                lambda f: not f.auto_created and not f.is_relation,
                fields_diff,
            ))
        field_kwargs = {f.name: getattr(self, f.name) for f in fields_to_copy}
        new_instance = self.__class__(
            questionnaire=to_questionnaire,
            short_name=self.short_name,
            order=self.order,
            **field_kwargs,
        )

        self._copy_fields_to_instance(new_instance)
        new_instance.save()
        return new_instance

    def is_visible_to_user(self, user):
        """
        Return true if block is visible to the user. Includes only server-side conditions,
        which don't change on client side during editing.
        For now there is only one server-side show condition - `GroupMemberShowCondition`
        :param user: User
        :return: True if block is visible, False otherwise
        """
        group_member_conditions = (
            self.show_conditions_questionnaireblockgroupmembershowcondition.
            all())
        if not group_member_conditions.exists():
            return True

        for condition in group_member_conditions:
            if condition.is_satisfied(user):
                return True
        return False

    def copy_dependencies_to_instance(self, other_block):
        """
        Copies dependencies between blocks. This method is called
        when all blocks are copied
        :param other_block: Block from other questionnaire where
        dependencies should be copied

        Override this method in a subclass to copy any objects having a
        reference to this block and other blocks.

        The override must:
        - call super().copy_dependencies_to_instance(other),
        - make copies of the dependencies for the passed instance.
        """
        pass

    def _copy_fields_to_instance(self, other):
        """
        Subclasses must override this method if they define new relation fields
        or if some of their plain fields require non-trivial copying. The
        implementation should:
        - call super()._copy_fields_to_instance(other),
        - copy its field values to the passed instance.

        :param other: The instance to copy field values to.
        """
        pass

    @property
    def block_name(self):
        """
        Name of template file in `templates/questionnaire/blocks/`.
        Also part of css class for this type's blocks
        (i.e. `questionnaire__block__markdown` for MarkdownQuestionnaireBlock)
        """
        raise NotImplementedError("%s doesn't implement block_name property " %
                                  self.__class__.__name__)

    @cached_property
    def show_conditions(self):
        return (list(
            self.show_conditions_questionnaireblockgroupmembershowcondition.
            all()) + list(
                self.
                show_conditions_questionnaireblockvariantcheckedshowcondition.
                all()))
Example #8
0
class EntranceStatus(models.Model):
    class Status(djchoices.DjangoChoices):
        NOT_PARTICIPATED = djchoices.ChoiceItem(1, 'Не участвовал в конкурсе')
        AUTO_REJECTED = djchoices.ChoiceItem(2, 'Автоматический отказ')
        NOT_ENROLLED = djchoices.ChoiceItem(3, 'Не прошёл по конкурсу')
        ENROLLED = djchoices.ChoiceItem(4, 'Поступил')
        PARTICIPATING = djchoices.ChoiceItem(5, 'Подал заявку')
        IN_RESERVE_LIST = djchoices.ChoiceItem(6, 'В резервном списке')

    school = models.ForeignKey(
        'schools.School',
        on_delete=models.CASCADE,
        related_name='entrance_statuses',
    )

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='entrance_statuses',
    )

    # created_by=None means system's auto creating
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='+',
        blank=True,
        null=True,
        default=None,
    )

    public_comment = models.TextField(
        help_text='Публичный комментарий. Может быть виден поступающему',
        blank=True,
    )

    private_comment = models.TextField(
        help_text='Приватный комментарий. Виден только админам вступительной',
        blank=True,
    )

    is_status_visible = models.BooleanField(default=False)

    status = models.IntegerField(choices=Status.choices,
                                 validators=[Status.validator])

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

    is_approved = models.BooleanField(
        help_text=
        'Подтверждено ли участие пользователем. Имеет смысл, только если статус = «Поступил»',
        default=False)

    approved_at = models.DateTimeField(null=True, blank=True, default=None)

    @property
    def is_enrolled(self):
        return self.status == self.Status.ENROLLED

    @property
    def is_in_reserve_list(self):
        return self.status == self.Status.IN_RESERVE_LIST

    def approve(self):
        self.is_approved = True
        self.approved_at = django.utils.timezone.now()
        self.save()

    def remove_approving(self):
        self.is_approved = False
        self.approved_at = None
        self.save()

    @classmethod
    def create_or_update(cls, school, user, status, **kwargs):
        with transaction.atomic():
            current = cls.objects.filter(school=school, user=user).first()
            if current is None:
                current = cls(school=school,
                              user=user,
                              status=status,
                              **kwargs)
            else:
                current.status = status
                for key, value in current:
                    setattr(current, key, value)
            current.save()

    @classmethod
    def get_visible_status(cls, school, user):
        return cls.objects.filter(school=school,
                                  user=user,
                                  is_status_visible=True).first()

    def __str__(self):
        return '%s %s' % (self.user, self.Status.values[self.status])

    class Meta:
        verbose_name_plural = 'User entrance statuses'
        unique_together = ('school', 'user')
Example #9
0
class EntranceExamTask(polymorphic.models.PolymorphicModel):
    title = models.CharField(max_length=100, help_text='Название')

    text = models.TextField(help_text='Формулировка задания')

    exam = models.ForeignKey(
        'EntranceExam',
        on_delete=models.CASCADE,
        related_name='%(class)s',
    )

    category = models.ForeignKey(
        'EntranceExamTaskCategory',
        on_delete=models.CASCADE,
        verbose_name='категория',
        related_name='tasks',
    )

    help_text = models.CharField(
        max_length=100,
        help_text='Дополнительная информация, например, сведения о формате '
        'ответа',
        blank=True)

    order = models.IntegerField(
        help_text='Задачи выстраиваются по возрастанию порядка', default=0)

    max_score = models.PositiveIntegerField()

    custom_description = models.TextField(
        help_text='Текст с описанием типа задачи. Оставьте пустым, тогда будет '
        'использован текст по умолчанию для данного вида задач. '
        'В этом тексте можно указать, например, '
        'для кого эта задача предназначена.\n'
        'Поддерживается Markdown',
        blank=True,
    )

    def __str__(self):
        return "{}: {}".format(self.exam.school.name, self.title)

    def save(self, *args, **kwargs):
        if self.category.exam_id != self.exam_id:
            raise IntegrityError(
                "{}.{}: task's category should belong to the same exam as the "
                "task itself".format(self.__module__, self.__class__.__name__))
        super().save(*args, **kwargs)

    def is_accepted_for_user(self, user):
        # Always not accepted by default. Override when subclassing.
        return False

    def is_solved_by_user(self, user):
        # Always not solved by default. Override when subclassing.
        return False

    @property
    def template_file(self):
        """
        Return template file name in folder templates/entrance/exam/
        """
        raise NotImplementedError('Child should define property template_file')

    @property
    def type_title(self):
        """
        Return title of blocks with these tasks
        """
        raise NotImplementedError('Child should define property type_title')

    def get_form_for_user(self, user, *args, **kwargs):
        """
        Return form for this task for the specified user
        """
        raise NotImplementedError('Child should define get_form_for_user()')

    @property
    def solution_class(self):
        raise NotImplementedError(
            'Child should define property solution_class')
Example #10
0
class EntranceExamTaskCategory(models.Model):
    """
    Tasks are displayed in these categories on the exam page.
    """
    exam = models.ForeignKey(
        'EntranceExam',
        on_delete=models.CASCADE,
        verbose_name="экзамен",
        related_name='task_categories',
    )

    short_name = models.SlugField(
        help_text="Может состоять только из букв, цифр, знака подчеркивания и "
        "дефиса.", )

    title = models.CharField(
        verbose_name="заголовок",
        max_length=100,
        help_text="Заголовок категории, например «Практические задачи:»",
    )

    order = models.IntegerField(
        verbose_name="порядок",
        help_text="Категории задач отображаются в заданном порядке",
    )

    is_mandatory = models.BooleanField(
        verbose_name="обязательная категория",
        default=True,
        help_text="Обязательно ли решать задачи в этой категории, чтобы "
        "вступительная считалась выполненной?",
    )

    available_from_time = models.ForeignKey(
        'dates.KeyDate',
        on_delete=models.CASCADE,
        verbose_name="доступна c",
        related_name='+',
        null=True,
        blank=True,
        default=None,
        help_text="Момент времени, начиная с которого задачи этой категории "
        "показываются пользователям. Оставьте пустым, если задачи "
        "должны быть доступны с начала вступительной работы.",
    )

    available_to_time = models.ForeignKey(
        'dates.KeyDate',
        on_delete=models.CASCADE,
        verbose_name="доступна до",
        related_name='+',
        null=True,
        blank=True,
        default=None,
        help_text="Момент времени, после которого возможность послать решения "
        "по задачам этой категории будет закрыта. Оставьте пустым, "
        "если задачи должны быть доступны до конца вступительной "
        "работы.",
    )

    text_after_closing = models.TextField(
        blank=True,
        verbose_name="текст после закрытия",
        help_text="Текст, который показывается на странице задачи после "
        "закрытия задач этой категории, но до конца вступительной "
        "работы.\n"
        "Поддерживается Markdown.",
    )

    class Meta:
        unique_together = [('exam', 'short_name'), ('exam', 'order')]
        verbose_name = _('task category')
        verbose_name_plural = _('task categories')

    def __str__(self):
        return 'Категория задач «{}» для «{}»'.format(self.title, self.exam)

    def is_started_for_user(self, user):
        if self.available_from_time is None:
            return True
        return self.available_from_time.passed_for_user(user)

    def is_finished_for_user(self, user):
        if self.available_to_time is None:
            return False
        return self.available_to_time.passed_for_user(user)