示例#1
0
class Sentiment(models.TimeStampedModel):
    """
    Represents the sentiment of a user towards a feature in a given instant
    of time.
    """

    STATUS_VERY_HAPPY, STATUS_HAPPY, STATUS_NEUTRAL, STATUS_SAD = range(4)
    STATUS_CHOICES = [
        (STATUS_VERY_HAPPY, _('Very happy')),
        (STATUS_HAPPY, _('Happy')),
        (STATUS_NEUTRAL, _('Neutral')),
        (STATUS_SAD, _('Sad')),
    ]

    status = models.SmallIntegerField(
        _('status'),
        choices=STATUS_CHOICES,
    )
    user = models.ForeignKey(models.User, related_name='sentiments')
    feature = models.ForeignKey(
        Feature,
        related_name='sentiments',
    )
    board = models.ForeignKey(
        SentimentBoard,
        related_name='sentiments',
    )
示例#2
0
class Task(models.TimeStampedModel):
    """
    Represents a task in a Kanban activity.
    """

    STATUS_TODO, STATUS_DOING, STATUS_DONE = range(3)
    STATUS_CHOICES = [
        (STATUS_TODO, _('To do')),
        (STATUS_DOING, _('Doing')),
        (STATUS_DONE, _('Done')),
    ]

    name = models.CodeschoolNameField()
    description = models.CodeschoolDescriptionField()
    status = models.SmallIntegerField(
        _('status'),
        choices=STATUS_CHOICES,
    )
    members = models.ManyToManyField(
        models.User,
        related_name='kanban_tasks',
    )
    created_by = models.ForeignKey(
        models.User,
        related_name='created_tasks',
        null=True,
        blank=True,
    )
    assigned_to = models.ManyToManyField(
        models.User,
        related_name='assigned_tasks',
    )
    estimated_duration_hours = models.PositiveSmallIntegerField(
        _('Estimated duration (hours)'),
        default=0,
    )

    objects = TaskQuerySet.as_manager()
示例#3
0
文件: models.py 项目: cslms/cs-server
class AttendanceCheck(models.Model):
    """
    Confirms attendance by an user.
    """

    user = models.ForeignKey(models.User)
    event = models.ForeignKey(Event)
    has_attended = models.BooleanField(default=bool)
    attempts = models.SmallIntegerField(default=int)

    def update(self, phrase):
        """
        Update check with the given passphrase.
        """

        sheet = self.event.sheet
        if self.attempts > sheet.max_attempts:
            return
        if string_distance(phrase,
                           self.event.passphrase) <= sheet.max_string_distance:
            self.has_attended = True
        self.attempts += 1
        self.save()
示例#4
0
class Profile(UserenaBaseProfile):
    """
    Userena profile.

    Social information about our users.
    """
    user = models.OneToOneField(
        models.User,
        unique=True,
        verbose_name=_('user'),
        related_name='profile',
    )
    nickname = models.CharField(max_length=50, blank=True, null=True)
    phone = models.CharField(max_length=20, blank=True, null=True)
    gender = models.SmallIntegerField(_('gender'),
                                      choices=[(0, _('male')),
                                               (1, _('female'))],
                                      blank=True,
                                      null=True)
    date_of_birth = models.DateField(_('date of birth'), blank=True, null=True)
    website = models.URLField(blank=True, null=True)
    about_me = models.TextField(blank=True, null=True)

    @property
    def age(self):
        if self.date_of_birth is None:
            return None
        today = timezone.now().date()
        return int(round((today - self.date_of_birth).days / 365.25))

    @property
    def contact_classes(self):
        user = self.user

        try:
            friends = user.friends
            colleagues = user.staff_contacts
            staff = user.colleagues
        except AttributeError as ex:
            raise RuntimeError(ex)

        return [friends, colleagues, staff]

    def custom_fields(self, flat=False):
        """Return a dictionary with all custom fields"""

        D = {}
        for value in self.custom_field_values.all():
            category = value.category_name
            field_name = value.field_name
            data = value.data
            if flat:
                D[(category, field_name)] = data
            else:
                category_dict = D.setdefault(category, {})
                category_dict[field_name] = data

        return D

    class Meta:
        permissions = (
            ('student', _('Can access/modify data visible to student\'s')),
            ('teacher',
             _('Can access/modify data visible only to Teacher\'s')),
        )

    def __str__(self):
        if self.user is None:
            return __('unbound profile')
        return __('%(name)s\'s profile') % {'name': self.user.get_full_name()}

    def __repr__(self):
        return str(self.contact_classes)
示例#5
0
文件: models.py 项目: cslms/cs-server
class AttendanceSheet(models.Model):
    """
    Controls student attendance by generating a new public passphrase under 
    teacher request. Students confirm attendance by typing the secret phrase
    in a small interval. 
    """

    max_attempts = models.SmallIntegerField(default=3)
    expiration_minutes = models.SmallIntegerField(default=5)
    owner = models.ForeignKey(models.User)
    last_event = models.ForeignKey('Event', blank=True, null=True)
    max_string_distance = models.SmallIntegerField(default=0)
    max_number_of_absence = models.IntegerField(blank=True, null=True)

    @property
    def expiration_interval(self):
        return datetime.timedelta(minutes=self.expiration_minutes)

    @property
    def attendance_checks(self):
        return AttendanceCheck.objects.filter(event__sheet=self)

    def new_event(self):
        """
        Create a new event in attendance sheet.
        """

        current_time = now()
        new = self.events.create(passphrase=new_random_passphrase(),
                                 date=current_time.date(),
                                 created=current_time,
                                 expires=current_time +
                                 self.expiration_interval)
        self.last_event = new
        self.save(update_fields=['last_event'])
        return new

    def get_today_event(self):
        """
        Return the last event created for today
        """

        if self.last_event.date() == now().date():
            return self.last_event
        else:
            return self.new_event()

    def number_of_absences(self, user):
        """
        Return the total number of absence for user.
        """

        return self.attendance_checks.filter(user=user,
                                             has_attended=False).count()

    def absence_table(self, users=None, method='fraction'):
        """
        Return a mapping between users and their respective absence rate. 

        Args:
            users:
                A queryset of users.
            method:
                One of 'fraction' (default), 'number', 'attendance' or 
                'attendance-fraction'
        """

        try:
            get_value_from_absence = {
                'fraction': lambda x: x / num_events,
                'number': lambda x: x,
                'attendance': lambda x: num_events - x,
                'attendance-fraction': lambda x: (num_events - x) / num_events
            }[method]
        except KeyError:
            raise ValueError('invalid method: %r' % method)

        num_events = self.events.count()
        if users is None:
            users = models.User.objects.all()

        result = collections.OrderedDict()
        for user in users:
            absence = self.user_absence(user)
            result[user] = get_value_from_absence(absence)
        return result

    def render_dialog(self, request):
        """
        Renders attendance dialog based on request.
        """

        context = {
            'passphrase': self.passphrase,
            'is_expired': self.is_expired(),
            'minutes_left': self.minutes_left(raises=False)
        }
        user = request.user
        if user == self.owner:
            template = 'attendance/edit.jinja2'
        else:
            template = 'attendance/view.jinja2'
            context['attempts'] = self.user_attempts(user)
        return render_to_string(template, request=request, context=context)

    def user_attempts(self, user):
        """
        Return the number of user attempts in the last attendance event.
        """

        if self.last_event is None:
            return 0

        qs = self.attendance_checks.filter(user=user, event=self.last_event)
        return qs.count()

    def minutes_left(self, raises=True):
        """
        Return how many minutes left for expiration.
        """

        if self.last_event:
            time = now()
            if self.last_event.expires < time:
                return 0.0
            else:
                dt = self.last_event.expires - time
                return dt.minutes
        if raises:
            raise ValueError('last event is not defined')
        else:
            return None

    def is_expired(self):
        """
        Return True if last_event has already expired.
        """

        if not self.last_event:
            return False
        return self.last_event.expires < now()
示例#6
0
class Profile(UserenaBaseProfile, models.CodeschoolPage):
    """
    Social information about users.
    """
    class Meta:
        permissions = (
            ('student', _('Can access/modify data visible to student\'s')),
            ('teacher',
             _('Can access/modify data visible only to Teacher\'s')),
        )

    username = delegate_to('user', True)
    first_name = delegate_to('user')
    last_name = delegate_to('user')
    email = delegate_to('user')

    @property
    def short_description(self):
        return '%s (id: %s)' % (self.get_full_name_or_username(),
                                self.school_id)

    @property
    def age(self):
        if self.date_of_birth is None:
            return None
        today = timezone.now().date()
        return int(round((today - self.date_of_birth).years))

    user = models.OneToOneField(
        models.User,
        unique=True,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name=_('user'),
        related_name='_profile',
    )
    school_id = models.CharField(
        _('school id'),
        help_text=_('Identification number in your school issued id card.'),
        max_length=50,
        blank=True,
        null=True)
    nickname = models.CharField(max_length=50, blank=True, null=True)
    phone = models.CharField(max_length=20, blank=True, null=True)
    gender = models.SmallIntegerField(_('gender'),
                                      choices=[(0, _('male')),
                                               (1, _('female'))],
                                      blank=True,
                                      null=True)
    date_of_birth = models.DateField(_('date of birth'), blank=True, null=True)
    website = models.URLField(blank=True, null=True)
    about_me = models.RichTextField(blank=True, null=True)
    objects = ProfileManager.from_queryset(models.PageManager)()

    def __init__(self, *args, **kwargs):
        if 'user' in kwargs and 'id' not in kwargs:
            kwargs.setdefault('parent_page', profile_root())
        super().__init__(*args, **kwargs)

        if self.pk is None and self.user is not None:
            user = self.user
            self.title = self.title or __("%(name)s's profile") % {
                'name': user.get_full_name() or user.username
            }
            self.slug = self.slug or user.username.replace('.', '-')

    def __str__(self):
        if self.user is None:
            return __('Unbound profile')
        full_name = self.user.get_full_name() or self.user.username
        return __('%(name)s\'s profile') % {'name': full_name}

    def get_full_name_or_username(self):
        name = self.user.get_full_name()
        if name:
            return name
        else:
            return self.user.username

    # Wagtail admin
    parent_page_types = ['cs_core.ProfileRoot']
    content_panels = models.CodeschoolPage.content_panels + [
        panels.MultiFieldPanel([
            panels.FieldPanel('school_id'),
        ],
                               heading='Required information'),
        panels.MultiFieldPanel([
            panels.FieldPanel('nickname'),
            panels.FieldPanel('phone'),
            panels.FieldPanel('gender'),
            panels.FieldPanel('date_of_birth'),
        ],
                               heading=_('Personal Info')),
        panels.MultiFieldPanel([
            panels.FieldPanel('website'),
        ],
                               heading=_('Web presence')),
        panels.RichTextFieldPanel('about_me'),
    ]
示例#7
0
class FormEntry(models.Model):
    """
    An entry in a form.
    """

    TYPE_STRING, TYPE_TEXT, TYPE_BOOLEAN, TYPE_INT, TYPE_FLOAT, TYPE_DATE = \
        range(5)

    TYPE_CHOICES = [
        (TYPE_STRING, _('String')),
        (TYPE_TEXT, _('Text')),
        (TYPE_BOOLEAN, _('Boolean')),
        (TYPE_INT, _('Integer')),
        (TYPE_FLOAT, _('Numeric')),
        (TYPE_DATE, _('Date')),
    ]
    TYPE_MAP = {
        TYPE_STRING: str,
        TYPE_TEXT: str,
        TYPE_BOOLEAN: bool,
        TYPE_INT: int,
        TYPE_FLOAT: float,
        TYPE_DATE: date,
    }

    form = models.ForeignKey(
        FormQuestion,
        related_name='entries',
        on_delete=models.CASCADE,
    )
    name = models.CharField(max_length=30)
    label = models.CharField(max_length=40)
    help = models.CharField(max_length=140, help_text=True)
    type = models.SmallIntegerField(choices=TYPE_CHOICES)
    default = models.CharField(max_length=40, blank=True)
    placeholder = models.CharField(max_length=40, blank=True)
    grader_json = models.JSONField()

    class Meta:
        unique_together = [('name', 'form')]

    @property
    def py_type(self):
        return self.TYPE_MAP[self.type]

    @lazy
    def grader(self):
        return answer_from_json(self.grader_json)

    def clean(self):
        if self.default and not self._can_have_default():
            raise ValidationError({
                'default':
                _('cannot define default values for this type of value.')
            })
        self._validate_default()

        if self.placeholder and not self._can_have_placeholder():
            raise ValidationError({
                'placeholder':
                _('cannot define a placeholder for this type of value.')
            })

    def _can_placeholder(self):
        return self.py_type != bool

    def _can_have_default(self):
        return self.py_type in {str, int, float, date}

    def _validate_default(self):
        tt = self.py_type
        data = self.default

        try:
            if tt in {int, float}:
                tt(data)
            elif tt == date:
                yy, dd, aa = map(int, date.split('/'))
            elif tt == bool:
                if data not in {'true', 'false'}:
                    raise ValueError
        except (ValueError, IndexError) as ex:
            raise ValidationError({'default': 'invalid default value'})
        return self
示例#8
0
class Profile(UserenaBaseProfile, models.Page):
    """
    Social information about users.
    """
    class Meta:
        permissions = (
            ('student', _('Can access/modify data visible to student\'s')),
            ('teacher',
             _('Can access/modify data visible only to Teacher\'s')),
        )

    user = models.OneToOneField(
        User,
        unique=True,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name=_('user'),
        related_name='profile',
    )
    school_id = models.CharField(
        _('school id'),
        help_text=_('Identification number in your school issued id card.'),
        max_length=50,
        blank=True,
        null=True)
    nickname = models.CharField(max_length=50, blank=True, null=True)
    phone = models.CharField(max_length=20, blank=True, null=True)
    gender = models.SmallIntegerField(_('gender'),
                                      choices=[(0, _('male')),
                                               (1, _('female'))],
                                      blank=True,
                                      null=True)
    date_of_birth = models.DateField(_('date of birth'), blank=True, null=True)
    website = models.URLField(blank=True, null=True)
    about_me = models.RichTextField(blank=True, null=True)
    objects = ProfileManager()

    # Delegates and properties
    username = delegate_to('user', True)
    first_name = delegate_to('user')
    last_name = delegate_to('user')
    email = delegate_to('user')

    @property
    def short_description(self):
        return '%s (id: %s)' % (self.get_full_name_or_username(),
                                self.school_id)

    @property
    def age(self):
        if self.date_of_birth is None:
            return None
        today = timezone.now().date()
        birthday = self.date_of_birth
        years = today.year - birthday.year
        birthday = datetime.date(today.year, birthday.month, birthday.day)
        if birthday > today:
            return years - 1
        else:
            return years

    def __str__(self):
        if self.user is None:
            return __('Unbound profile')
        full_name = self.user.get_full_name() or self.user.username
        return __('%(name)s\'s profile') % {'name': full_name}

    def save(self, *args, **kwargs):
        user = self.user
        if not self.title:
            self.title = self.title or __("%(name)s's profile") % {
                'name': user.get_full_name() or user.username
            }
        if not self.slug:
            self.slug = user.username.replace('.', '-')

        # Set parent page, if necessary
        if not self.path:
            root = ProfileList.objects.instance()
            root.add_child(instance=self)
        else:
            super().save(*args, **kwargs)

    def get_full_name_or_username(self):
        name = self.user.get_full_name()
        if name:
            return name
        else:
            return self.user.username

    # Serving pages
    template = 'cs_auth/profile-detail.jinja2'

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        context['profile'] = self
        return context

    # Wagtail admin
    parent_page_types = ['ProfileList']
    content_panels = models.Page.content_panels + [
        panels.MultiFieldPanel([
            panels.FieldPanel('school_id'),
        ],
                               heading='Required information'),
        panels.MultiFieldPanel([
            panels.FieldPanel('nickname'),
            panels.FieldPanel('phone'),
            panels.FieldPanel('gender'),
            panels.FieldPanel('date_of_birth'),
        ],
                               heading=_('Personal Info')),
        panels.MultiFieldPanel([
            panels.FieldPanel('website'),
        ],
                               heading=_('Web presence')),
        panels.RichTextFieldPanel('about_me'),
    ]
示例#9
0
class Profile(UserenaBaseProfile):
    """
    Social information about users.
    """
    class Meta:
        permissions = (
            ('student', _('Can access/modify data visible to student\'s')),
            ('teacher',
             _('Can access/modify data visible only to Teacher\'s')),
        )

    GENDER_MALE, GENDER_FEMALE = 0, 1

    user = models.OneToOneField(
        User,
        verbose_name=_('user'),
        unique=True,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='profile',
    )
    school_id = models.CharField(
        _('school id'),
        max_length=50,
        blank=True,
        null=True,
        help_text=_('Identification number in your school issued id card.'),
    )
    is_teacher = models.BooleanField(default=False)
    nickname = models.CharField(max_length=50, blank=True, null=True)
    phone = models.CharField(max_length=20, blank=True, null=True)
    gender = models.SmallIntegerField(_('gender'),
                                      choices=[(GENDER_MALE, _('Male')),
                                               (GENDER_FEMALE, _('Female'))],
                                      blank=True,
                                      null=True)
    date_of_birth = models.DateField(_('date of birth'), blank=True, null=True)
    website = models.URLField(blank=True, null=True)
    about_me = models.RichTextField(blank=True, null=True)

    # Delegates and properties
    username = delegate_to('user', True)
    first_name = delegate_to('user')
    last_name = delegate_to('user')
    email = delegate_to('user')

    @property
    def age(self):
        if self.date_of_birth is None:
            return None
        today = timezone.now().date()
        birthday = self.date_of_birth
        years = today.year - birthday.year
        birthday = datetime.date(today.year, birthday.month, birthday.day)
        if birthday > today:
            return years - 1
        else:
            return years

    def __str__(self):
        if self.user is None:
            return __('Unbound profile')
        full_name = self.user.get_full_name() or self.user.username
        return __('%(name)s\'s profile') % {'name': full_name}

    def get_full_name_or_username(self):
        name = self.user.get_full_name()
        if name:
            return name
        else:
            return self.user.username

    def get_absolute_url(self):
        return reverse('auth:profile-detail',
                       kwargs={'username': self.user.username})

    # Serving pages
    template = 'cs_auth/profile-detail.jinja2'

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        context['profile'] = self
        return context

    # Wagtail admin
    panels = [
        panels.MultiFieldPanel([
            panels.FieldPanel('school_id'),
        ],
                               heading='Required information'),
        panels.MultiFieldPanel([
            panels.FieldPanel('nickname'),
            panels.FieldPanel('phone'),
            panels.FieldPanel('gender'),
            panels.FieldPanel('date_of_birth'),
        ],
                               heading=_('Personal Info')),
        panels.MultiFieldPanel([
            panels.FieldPanel('website'),
        ],
                               heading=_('Web presence')),
        panels.RichTextFieldPanel('about_me'),
    ]
示例#10
0
class Profile(models.TimeStampedModel):
    """
    Social information about users.
    """

    GENDER_MALE, GENDER_FEMALE, GENDER_OTHER = 0, 1, 2
    GENDER_CHOICES = [
        (GENDER_MALE, _('Male')),
        (GENDER_FEMALE, _('Female')),
        (GENDER_OTHER, _('Other')),
    ]

    VISIBILITY_PUBLIC, VISIBILITY_FRIENDS, VISIBILITY_HIDDEN = range(3)
    VISIBILITY_CHOICES = enumerate(
        [_('Any Codeschool user'),
         _('Only friends'),
         _('Private')])

    visibility = models.IntegerField(
        _('Visibility'),
        choices=VISIBILITY_CHOICES,
        default=VISIBILITY_FRIENDS,
        help_text=_('Who do you want to share information in your profile?'))
    user = models.OneToOneField(
        User,
        verbose_name=_('user'),
        related_name='profile_ref',
    )
    phone = models.CharField(
        _('Phone'),
        max_length=20,
        blank=True,
        null=True,
    )
    gender = models.SmallIntegerField(
        _('gender'),
        choices=GENDER_CHOICES,
        blank=True,
        null=True,
    )
    date_of_birth = models.DateField(
        _('date of birth'),
        blank=True,
        null=True,
    )
    website = models.URLField(
        _('Website'),
        blank=True,
        null=True,
        help_text=_('A website that is shown publicly in your profile.'))
    about_me = models.RichTextField(
        _('About me'),
        blank=True,
        help_text=_('A small description about yourself.'))

    # Delegates and properties
    username = delegate_to('user', True)
    name = delegate_to('user')
    email = delegate_to('user')

    class Meta:
        permissions = (
            ('student', _('Can access/modify data visible to student\'s')),
            ('teacher',
             _('Can access/modify data visible only to Teacher\'s')),
        )

    @property
    def age(self):
        if self.date_of_birth is None:
            return None
        today = timezone.now().date()
        birthday = self.date_of_birth
        years = today.year - birthday.year
        birthday = datetime.date(today.year, birthday.month, birthday.day)
        if birthday > today:
            return years - 1
        else:
            return years

    def __str__(self):
        if self.user is None:
            return __('Unbound profile')
        full_name = self.user.get_full_name() or self.user.username
        return __('%(name)s\'s profile') % {'name': full_name}

    def get_absolute_url(self):
        self.user.get_absolute_url()
示例#11
0
class AttendanceSheet(models.Model):
    """
    Controls student attendance by generating a new public pass-phrase under
    teacher request. Students confirm attendance by typing the secret phrase
    within a small interval after the teacher starts checking the attendance.
    """

    max_attempts = models.SmallIntegerField(
        _('Maximum number of attempts'),
        default=3,
        help_text=_(
            'How many times a student can attempt to prove attendance. A '
            'maximum is necessary to avoid a brute force attack.'
        ),
    )
    expiration_minutes = models.SmallIntegerField(
        _('Expiration time'),
        default=5,
        help_text=_(
            'Time (in minutes) before attendance session expires.'
        )
    )
    owner = models.ForeignKey(models.User)
    last_event = models.ForeignKey('Event', blank=True, null=True)
    max_string_distance = models.SmallIntegerField(
        _('Fuzzyness'),
        default=1,
        help_text=_(
            'Maximum number of wrong characters that is considered acceptable '
            'when comparing the expected passphrase with the one given by the'
            'student.'
        ),
    )
    max_number_of_absence = models.IntegerField(blank=True, null=True)

    # Properties
    expiration_interval = property(
        lambda self: datetime.timedelta(minutes=self.expiration_minutes))
    attendance_checks = property(
        lambda self: AttendanceCheck.objects.filter(event__sheet=self)
    )

    def __str__(self):
        try:
            return self.attendancepage_set.first().title
        except models.ObjectDoesNotExist:
            user = self.owner.get_full_name() or self.owner.username
            return _('Attendance sheet (%s)' % user)

    def new_event(self, commit=True):
        """
        Create a new event in attendance sheet.
        """

        current_time = now()
        event = Event(
            passphrase=phrase(),
            date=current_time.date(),
            created=current_time,
            expires=current_time + self.expiration_interval,
            sheet=self,
        )
        self.last_event = event
        if commit:
            event.save()
            self.save(update_fields=['last_event'])
        return event

    def current_passphrase(self):
        """
        Return the current passphrase.
        """
        return self.current_event().passphrase

    def current_event(self):
        """
        Return the last event created for today.

        If no event is found, create a new one.
        """

        if self.last_event and self.last_event.date == now().date():
            return self.last_event
        else:
            return self.new_event()

    def number_of_absences(self, user):
        """
        Return the total number of absence for user.
        """

        return self.attendance_checks.filter(user=user,
                                             has_attended=False).count()

    def absence_table(self, users=None, method='fraction'):
        """
        Return a mapping between users and their respective absence rate. 

        Args:
            users:
                A queryset of users.
            method:
                One of 'fraction' (default), 'number', 'attendance' or 
                'attendance-fraction'
        """

        try:
            get_value_from_absence = {
                'fraction': lambda x: x / num_events,
                'number': lambda x: x,
                'attendance': lambda x: num_events - x,
                'attendance-fraction': lambda x: (num_events - x) / num_events
            }[method]
        except KeyError:
            raise ValueError('invalid method: %r' % method)

        num_events = self.events.count()
        if users is None:
            users = models.User.objects.all()

        result = collections.OrderedDict()
        for user in users:
            absence = self.user_absence(user)
            result[user] = get_value_from_absence(absence)
        return result

    def user_attempts(self, user):
        """
        Return the number of user attempts in the last attendance event.
        """

        if self.last_event is None:
            return 0

        qs = self.attendance_checks.filter(user=user, event=self.last_event)
        return qs.count()

    def minutes_left(self, raises=True):
        """
        Return how many minutes left for expiration.
        """

        if self.last_event:
            time = now()
            if self.last_event.expires < time:
                return 0.0
            else:
                dt = self.last_event.expires - time
                return dt.total_seconds() / 60.
        if raises:
            raise ValueError('last event is not defined')
        else:
            return None

    def is_expired(self):
        """
        Return True if last_event has already expired.
        """

        if not self.last_event:
            return False
        return self.last_event.expires < now()

    def is_valid(self, passphrase):
        """
        Check if passphrase is valid.
        """
        if self.is_expired():
            return False
        distance = string_distance(passphrase, self.current_passphrase())
        return distance <= self.max_string_distance