Beispiel #1
0
class Event(models.Model):
    """
    Represents an event that we want to confirm attendance.
    """

    sheet = models.ForeignKey(AttendanceSheet, related_name='events')
    date = models.DateField()
    created = models.DateTimeField()
    expires = models.DateTimeField()
    passphrase = models.CharField(
        _('Passphrase'),
        max_length=200,
        help_text=_(
            'The passphrase is case-insensitive. We tolerate small typing '
            'errors.'
        ),
    )

    def update(self, commit=True):
        """
        Regenerate passphrase and increases expiration time.
        """

        new = self.passphrase
        while new == self.passphrase:
            new = phrase()
        self.passphrase = new
        self.expires += self.sheet.expiration_interval
        if commit:
            self.save()
Beispiel #2
0
class Lesson(models.Orderable):
    """
    Intermediate model between a LessonPage and a Calendar.
    """

    calendar = models.ParentalKey(
        'CalendarPage',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='lessons',
    )
    page = models.OneToOneField('LessonPage',
                                null=True,
                                blank=True,
                                related_name='lesson')
    title = models.TextField(
        _('title'),
        help_text=_('A brief description for the lesson.'),
    )
    date = models.DateField(
        _('date'),
        null=True,
        blank=True,
        help_text=_('Date scheduled for this lesson.'),
    )

    panels = [
        panels.FieldPanel('title'),
        panels.FieldPanel('date'),
    ]
Beispiel #3
0
class Sprint(models.TimeStampedModel):
    """
    A sprint meta-data object that can be associated with many
    projects/contexts.
    """

    index = models.PositiveSmallIntegerField()
    start_date = models.DateField(_('Starts'), )
    due_date = models.DateField(_('Ends'), )
    name = models.CodeschoolNameField(blank=True)
    description = models.CodeschoolDescriptionField(blank=True)

    def next_sprint(self, name='', description=''):
        """
        Schedule a new sprint starting at the end of the current sprint and
        keeping the same time-frame.
        """

        raise NotImplementedError
class Lesson(models.ListItemModel):
    """A single lesson in a doubly linked list."""
    class Meta:
        verbose_name = _('Lesson')
        verbose_name_plural = _('Lessons')
        root_field = 'course'

    title = models.CharField(max_length=140, blank=True)
    description = models.TextField(blank=True)
    date = models.DateField(null=True, blank=True)
    course = models.ForeignKey(Course)

    def __str__(self):
        return self.title
Beispiel #5
0
class LessonInfo(models.Orderable):
    """
    Intermediate model between a LessonPage and a Calendar to make it
    orderable.
    """
    class Meta:
        verbose_name = _('Lesson')
        verbose_name_plural = _('Lessons')

    calendar = models.ParentalKey(
        Calendar,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='info',
    )
    page = models.OneToOneField(
        Lesson,
        null=True,
        blank=True,
        related_name='info',
    )
    title = models.TextField(
        _('title'),
        help_text=_('A brief description for the lesson.'),
    )
    date = models.DateField(
        _('date'),
        null=True,
        blank=True,
        help_text=_('Date scheduled for this lesson.'),
    )

    def save(self, *args, **kwargs):
        if self.pk is None and self.page is None:
            self.page = lesson_page = Lesson(
                title=self.title,
                slug=slugify(self.title),
            )
            lesson_page._created_for_lesson = self
            self.calendar.add_child(instance=lesson_page)
        super().save(*args, **kwargs)

    panels = [
        panels.FieldPanel('title'),
        panels.FieldPanel('date'),
    ]
Beispiel #6
0
class Event(models.Model):
    """
    Represents an event that we want to confirm attendance.
    """

    sheet = models.ForeignKey(AttendanceSheet, related_name='events')
    date = models.DateField()
    created = models.DateTimeField()
    expires = models.DateTimeField()
    passphrase = models.CharField(max_length=100)

    def update(self):
        """
        Regenerate passphrase and increases expiration time.
        """

        self.passphrase = new_random_passphrase()
        self.expires += self.sheet.expiration_interval
        self.save()
Beispiel #7
0
class Deadline(models.Model):
    """
    Describes a deadline of an activity.

    Users may define soft/hard deadlines.
    """

    name = models.CharField(
        _('name'),
        max_length=140,
        blank=True,
        help_text=_(
            'A unique string identifier. Useful for creating human-friendly '
            'references to the deadline object.'))
    start = models.DateField(
        _('start'),
        blank=True,
        null=True,
    )
    deadline = models.DateTimeField(
        _('deadline'),
        blank=True,
        null=True,
    )
    hard_deadline = models.DateTimeField(
        _('hard deadline'),
        blank=True,
        null=True,
        help_text=_(
            'If set, responses submitted after the deadline will be accepted '
            'with a penalty.'))
    penalty = models.DecimalField(
        _('delay penalty'),
        default=25,
        decimal_places=2,
        max_digits=6,
        help_text=_(
            'Sets the percentage of the total grade that will be lost due to '
            'delayed responses.'),
    )

    def get_status(self):
        """
        Return one of the strings depending on how the current time relates to
        the deadline:

        closed:
            Activity has not opened yet.
        valid:
            Current time is within the deadline.
        expired:
            Hard deadline has expired. Users cannot submit to the activity.
        penalty:
            Official deadline has expired, but users can still submit with a
            penalty.
        """

        now = timezone.now()
        if self.start is not None and now < self.start:
            return 'closed'
        elif ((self.hard_deadline is not None and now > self.hard_deadline)
              or (self.hard_deadline is None and self.deadline is not None
                  and now > self.deadline)):
            return 'expired'
        elif (self.hard_deadline is not None and now < self.hard_deadline
              and self.deadline is not None and now > self.deadline):
            return 'penalty'
        else:
            return 'valid'

    def get_penalty(self):
        """
        Return the penalty value
        """

        status = self.get_status()

        if status == 'expired':
            return Decimal(100)
        elif status == 'penalty':
            return self.penalty
        elif status == 'valid':
            return Decimal(0)
        else:
            raise RuntimeError('cannot get penalty of closed activity')

    def revise_grade(self, grade):
        """
        Return the update grade considering any possible delay penalty.
        """

        return (100 - self.get_penalty()) * Decimal(grade) / 100
Beispiel #8
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)
Beispiel #9
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'),
    ]
Beispiel #10
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'),
    ]
Beispiel #11
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'),
    ]
Beispiel #12
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()
Beispiel #13
0
class Course(models.DateFramedModel, models.TimeStampedModel):
    """One specific occurrence of a course for a given teacher in a given
    period."""

    # Fields
    discipline = models.ForeignKey(
        Discipline,
        related_name='courses'
    )
    teacher = models.ForeignKey(
        models.User,
        related_name='owned_courses'
    )
    students = models.ManyToManyField(
        models.User,
        related_name='enrolled_courses',
        blank=True,
    )
    staff = models.ManyToManyField(
        models.User,
        related_name='courses_as_staff',
        blank=True,
    )
    current_lesson_index = models.PositiveIntegerField(default=0, blank=True)
    current_lesson_start = models.DateField(blank=True, null=True)
    is_active = models.BooleanField(_('is active'), default=False)

    # Managers
    @property
    def past_activities(self):
        return (
            self.activities.filter(status=Activity.STATUS.concluded) |
            self.activities.filter(end__lt=timezone.now())
        ).select_subclasses()

    @property
    def open_activities(self):
        return (self.activities.timeframed.all() &
                self.activities.filter(status=Activity.STATUS.open)).select_subclasses()

    @property
    def pending_activities(self):
        return (self.activities.filter(status=Activity.STATUS.draft) |
                (self.activities.filter(status=Activity.STATUS.open) &
                 self.activities.filter(end__lt=timezone.now()))).select_subclasses()

    name = property(lambda s: s.discipline.name)
    short_description = property(lambda s: s.discipline.short_description)
    long_description = property(lambda s: s.discipline.long_description)

    def __str__(self):
        return '%s (%s)' % (self.discipline.name, self.teacher.first_name)

    def get_absolute_url(self):
        return url_reverse('course-detail', args=(self.pk,))

    def user_activities(self, user):
        """Return a list of all activities that are valid for the given user"""

        return self.activities.select_subclasses()

    def activity_duration(self):
        """Return the default duration for an activity starting from now."""

        return 120

    def next_time_slot(self):
        """Return the start and end times for the next class in the course.

        If a time slot is currently open, return it."""

        now = timezone.now()
        return now, now + timezone.timedelta(self.activity_duration())

    def next_date(self, date=None):
        """Return the date of the next available time slot."""
Beispiel #14
0
class Course(models.DateFramedModel, models.TimeStampedModel):
    """One specific occurrence of a course for a given teacher in a given
    period."""

    discipline = models.ForeignKey(Discipline, related_name='courses')
    teacher = models.ForeignKey(models.User, related_name='owned_courses')
    students = models.ManyToManyField(
        models.User,
        related_name='enrolled_courses',
        blank=True,
    )
    staff = models.ManyToManyField(
        models.User,
        related_name='courses_as_staff',
        blank=True,
    )
    current_lesson_index = models.PositiveIntegerField(default=0, blank=True)
    current_lesson_start = models.DateField(blank=True, null=True)
    is_active = models.BooleanField(_('is active'), default=False)
    objects = CourseQueryset.as_manager()

    # Discipline properties
    name = property(lambda x: x.discipline.name)
    short_description = property(lambda x: x.discipline.short_description)
    long_description = property(lambda x: x.discipline.long_description)
    short_description_html = property(
        lambda x: x.discipline.short_description_html)
    long_description_html = property(
        lambda x: x.discipline.long_description_html)

    # Other properties
    owner = property(lambda x: x.teacher)

    def __str__(self):
        return '%s (%s)' % (self.discipline.name, self.teacher.first_name)

    def to_file(self):
        """Serialize object in a Markdown format."""

    @classmethod
    def from_file(self, file):
        """Load course from file."""

    def register_student(self, student):
        """
        Register a new student in the course.
        """

        self.students.add(student)
        self.update_friendship_status(student)

    def update_friendship_status(self, student=None):
        """
        Recompute the friendship status for a single student by marking it as
        a colleague of all participants in the course..

        If no student is given, update the status of all enrolled students.
        """

        update = self._update_friendship_status
        if student is None:
            for student in self.students.all():
                update(student)
        else:
            update(student)

    def _update_friendship_status(self, student):
        # Worker function for update_friendship_status
        colleague_status = FriendshipStatus.STATUS_COLLEAGUE
        for colleague in self.students.all():
            if colleague != student:
                try:
                    FriendshipStatus.objects.get(owner=student,
                                                 other=colleague)
                except FriendshipStatus.DoesNotExist:
                    FriendshipStatus.objects.create(owner=student,
                                                    other=colleague,
                                                    status=colleague_status)

    # Managers
    @property
    def past_activities(self):
        return (self.activities.filter(status=Activity.STATUS_CLOSED)
                | self.activities.filter(
                    end__lt=timezone.now())).select_subclasses()

    @property
    def open_activities(self):
        return (self.activities.timeframed.all() & self.activities.filter(
            status=Activity.STATUS_OPEN)).select_subclasses()

    @property
    def pending_activities(self):
        return (self.activities.filter(status=Activity.STATUS_DRAFT) |
                (self.activities.filter(status=Activity.STATUS_OPEN)
                 & self.activities.filter(end__lt=timezone.now()))
                ).select_subclasses()

    def get_absolute_url(self):
        return url_reverse('course-detail', args=(self.pk, ))

    def get_user_role(self, user):
        """Return a string describing the most priviledged role the user
        as in the course. The possible values are:

        teacher:
            Owns the course and can do any kind of administrative tasks in
            the course.
        staff:
            Teacher assistants. May have some privileges granted by the teacher.
        student:
            Enrolled students.
        visitor:
            Have no relation to the course. If course is marked as public,
            visitors can access the course contents.
        """

        if user == self.teacher:
            return 'teacher'
        if user in self.staff.all():
            return 'staff'
        if user in self.students.all():
            return 'student'
        return 'visitor'

    def get_user_activities(self, user):
        """
        Return a sequence of all activities that are still open for the user.
        """

        activities = self.activities.filter(status=Activity.STATUS_OPEN)
        return activities.select_subclasses()

    def activity_duration(self):
        """
        Return the default duration (in minutes) for an activity starting from
        now.
        """

        return 120

    def next_time_slot(self):
        """Return the start and end times for the next class in the course.

        If a time slot is currently open, return it."""

        now = timezone.now()
        return now, now + timezone.timedelta(self.activity_duration())

    def next_date(self, date=None):
        """Return the date of the next available time slot."""

    def can_view(self, user):
        return True

    def can_edit(self, user):
        return user == self.teacher