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()
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'), ]
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
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'), ]
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()
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
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)
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'), ]
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'), ]
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'), ]
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()
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."""
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