class FriendshipStatus(models.StatusModel): """ Defines the friendship status between two users. """ STATUS_PENDING = 'pending' STATUS_FRIEND = 'friend' STATUS_UNFRIEND = 'unfriend' STATUS_COLLEAGUE = 'colleague' STATUS = models.Choices( (STATUS_PENDING, _('pending')), (STATUS_FRIEND, _('friend')), (STATUS_UNFRIEND, _('unfriend')), (STATUS_COLLEAGUE, _('colleague'))) owner = models.ForeignKey(models.User, related_name='related_users') other = models.ForeignKey(models.User, related_name='related_users_as_other') class Meta: unique_together = ('owner', 'other'), def save(self, *args, **kwds): super().save(*args, **kwds) try: FriendshipStatus.objects.get(owner=self.other, other=self.owner) except FriendshipStatus.DoesNotExist: reciprocal = FriendshipStatus(owner=self.other, other=self.owner) if self.status == self.STATUS_COLLEAGUE: reciprocal.status = self.STATUS_COLLEAGUE else: reciprocal.status = self.STATUS_PENDING reciprocal.save()
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', )
class SourceItem(models.ListItemModel): """A file item for the FileDownloadActivity.""" class Meta: root_field = 'activity' activity = models.ForeignKey('SourceCodeActivity') format = models.ForeignKey( 'cs_core.models.fileformat.FileFormat', verbose_name=_('format'), default='txt', help_text=_('The file format for the source code.'), ) name = models.CharField( _('name'), max_length=140, help_text='A short description of the given source code fragment') description = models.TextField( _('description'), blank=True, help_text=_( 'A detailed description of the source code fragment. This field ' 'accepts Markdown.')) source = models.TextField(_('source'), help_text=_('The source code fragment.')) visible = models.BooleanField( _('is visible'), default=True, help_text=_( 'Non-visible source items are available for download, but are not ' 'included in the main page'), ) def __str__(self): return self.name
class GivenBadge(models.TimeStampedModel): """ Implements a M2M relationship between badges and users. """ user = models.ForeignKey(models.User, related_name='+') badge = models.ForeignKey(Badge, related_name='+')
class Pair(TeamABC): """ A pair of students. """ first_user = models.ForeignKey(models.User) second_user = models.ForeignKey(models.User) objects = PairQuerySet.as_manager()
class QuizActivityItem(models.ListItemModel): """ A question in a quiz. """ class Meta: root_field = 'quiz' quiz = models.ForeignKey('QuizActivity') question = models.ForeignKey(Question)
class GivenBadge(models.TimeStampedModel): """ Associate users with badges. """ badge = models.ForeignKey(Badge) user = models.ForeignKey(models.User) # Delegate attributes track = delegate_to('badge') name = delegate_to('badge') image = delegate_to('badge') description = delegate_to('badge') details = delegate_to('badge')
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 BadgeTrack(models.Model): """ A badge track represents a single type of action that can give an increasing number of badges for different levels of accomplishment. """ name = models.CharField(_('name'), max_length=200) entry_point = models.ForeignKey(models.Page, related_name='badge_tracks') @lazy def badges_list(self): """ A list of all badges in the track sorted by difficulty. """ badges = list(self.badges.all()) badges.sort(key=lambda x: x.value) return badges def issue_badges(self, user, **kwargs): """ Issue all badges for the given """ for badge in self.badges_list: badge.update_for_user(user, **kwargs)
class Badge(models.Model): """ Represents an abstract badge. Instances of these class are not associated to specific users. GivenBadge makes the association between badges and users. """ track = models.ForeignKey(BadgeTrack, related_name='badges') name = models.CharField(max_length=200) image = models.ImageField( upload_to='gamification/badges/', blank=True, null=True, ) required_points = models.PositiveIntegerField(default=0) required_score = models.PositiveIntegerField(default=0) required_stars = models.PositiveIntegerField(default=0) description = models.TextField() details = models.RichTextField(blank=True) @property def value(self): """ A sortable element that describes the overall badge difficulty. """ return self.required_stars, self.required_points, self.required_score @classmethod def update_for_user(cls, user, **kwargs): """
class Sprint(models.Model): """ A sprint """ project = models.ForeignKey(ScrumProject, related_name='sprints') description = models.RichTextField(blank=True) start_date = models.DateTimeField() due_date = models.DateTimeField() duration_weeks = models.PositiveIntegerField(default=1, validators=[non_null]) def next_start_date(self, date=None): """ Return the next valid date that the sprint could start after the given. If no arguments are given, consider the current time. """ date = date or now() return date def attach(self, project, commit=True): """ Associate sprint to project, updating required values. """ date = project.finish_date() self.project = project self.start_date = self.next_start_date(date) self.due_date = self.start_date + one_week * self.duration_weeks if commit: self.save()
class CodePost(Post): """ Post some code. """ language = models.ForeignKey('core.ProgrammingLanguage', related_name='+') source = models.TextField()
class CustomFieldDefinition(models.Model): """ Define a custom site-specific field for the user profile. """ name = models.CharField(max_length=40) description = models.CharField(max_length=140) category = models.ForeignKey(CustomFieldCategory) enabled = models.BooleanField( _('enabled'), help_text=_('Enable or disable a custom field'), default=True, ) type = models.CharField(default='text', blank=True, max_length=10, choices=[('text', _('text')), ('int', _('int')), ('float', _('float')), ('date', _('date')), ('datetime', _('datetime'))]) from_db_conversions = { 'text': lambda x: x, 'int': int, 'float': float, 'date': lambda x: datetime.date(*map(int, x.split('-'))), 'datetime': lambda x: strptime("%Y-%m-%dT%H:%M:%S.%f%z", x), } to_db_conversions = { 'date': lambda x: x.isoformat(), 'datetime': lambda x: x.strftime("%Y-%m-%dT%H:%M:%S.%f%z"), }
class ExtraEmail(models.Model): """ Extra e-mails assigned to a Codeschool account. """ user = models.ForeignKey(User) email = models.EmailField(unique=True)
class Pairing(models.Model): """ A single pairing done in a sprint. """ sprint = models.ForeignKey(Sprint, related_name='pairings') members = models.ManyToManyField(models.User, related_name='+')
class ExpectedUsername(models.Model): """A string of an allowed value for e.g., white listing user names that can enroll in a specific course/activity/event etc. This class is used to create white lists of users that might not exist yet in the database. If you are sure that your users exist, maybe it is more convenient to create a regular Group.""" username = models.CharField(max_length=100, ) listener_id = models.IntegerField( null=True, blank=True, ) listener_type = models.ForeignKey( models.ContentType, null=True, blank=True, ) listener_action = models.CharField( max_length=30, blank=True, ) @property def exists(self): return models.User.objects.filter(username=self.username).size() == 1 @property def is_active(self): try: return models.User.objects.get(username=self.username).is_active except models.User.DoesNotExist: return False @property def listener(self): ctype = models.ContentType.objects.get(pk=self.listener_type) cls = ctype.model_class() try: return cls.objects.get(pk=self.listener_id) except cls.DoesNotExist: return None @property def user(self): return models.User.objects.get(username=self.username) def notify(self, user=None): """ Notify that user with the given username was created. """ if self.action: listener = self.listener if listener is not None: callback = getattr(listener, action) callback(user or self.user) def __str__(self): return self.username
class CodingIoResponse(QuestionResponse): source = models.TextField(blank=True) language = models.ForeignKey(ProgrammingLanguage) # Feedback properties feedback = property(lambda x: x.feedback_data) feedback_title = property(lambda x: x.feedback_data.title) testcase = property(lambda x: x.feedback_data.testcase) answer_key = property(lambda x: x.feedback_data.answer_key) is_correct = property(lambda x: x.feedback_data.is_correct) def autograde_compute(self): self.feedback_data = self.question.grade(self) return self.feedback_data.grade * 100 def html_feedback(self): if self.is_done: return render_html( self.feedback, template_name='cs_questions/render/feedback.jinja2') else: return super().html_feedback() @classmethod def _recompute_all_responses(cls): for r in cls.objects.all(): r.grade = r.get_grade_from_feedback() r.save()
class QuizItem(models.Orderable): """ A question in a quiz. """ quiz = models.ParentalKey( 'cs_questions.Quiz', related_name='quiz_items', ) question = models.ForeignKey( 'wagtailcore.Page', related_name='+', ) weight = models.FloatField( _('value'), default=1.0, help_text=_( 'The non-normalized weight of this item in the total quiz grade.'), ) # Wagtail admin panels = [ panels.PageChooserPanel('question', [ 'cs_questions.CodingIoQuestion', 'cs_questions.FormQuestion', ]), panels.FieldPanel('weight'), ]
class Response(models.InheritableModel, models.TimeStampedStatusModel): """ Represents a student's response to some activity. The student may submit many responses for the same object. It is also possible to submit different responses with different students. """ STATUS_PENDING = 'pending' STATUS_WAITING = 'waiting' STATUS_INVALID = 'invalid' STATUS_DONE = 'done' STATUS = models.Choices( (STATUS_PENDING, _('pending')), (STATUS_WAITING, _('waiting')), (STATUS_DONE, _('done')), ) activity = models.ForeignKey(Activity, blank=True, null=True) user = models.ForeignKey(models.User) grade = models.DecimalField( 'Percentage of maximum grade', max_digits=6, decimal_places=3, blank=True, null=True, ) data = models.PickledObjectField(blank=True, null=True) # # Visualization # ok_message = '*Contratulations!* Your response is correct!' wrong_message = 'I\'m sorry, your response is wrong.' partial_message = 'Your answer is partially correct: you made %(grade)d%% of the total grade.' def as_html(self): data = {'grade': self.grade * 100} if self.grade == 1: return markdown(self.ok_message) elif self.grade == 0: return markdown(self.wrong_message) else: return markdown(self.partial_message % data) def __str__(self): tname = type(self).__name__ return '%s(%s, grade=%s)' % (tname, self.activity, self.grade)
class ResponseModel(models.Model): class Meta: abstract = True language = models.ForeignKey( ProgrammingLanguage, verbose_name=_('Programming language'), help_text=_('The programming language for your code'))
class FriendshipStatus(models.StatusModel): STATUS = models.Choices(('pending', _('pending')), ('friend', _('friend')), ('acquaintance', _('acquaintance')), ('unfriend', _('unfriend'))) owner = models.ForeignKey(models.User, related_name='associated') other = models.ForeignKey(models.User, related_name='associated_as_other') class Meta: unique_together = ('owner', 'other'), def save(self, *args, **kwds): super().save(*args, **kwds) try: FriendshipStatus.objects.get(owner=self.other, other=self.owner) except FriendshipStatus.DoesNotExist: FriendshipStatus(owner=self.other, other=self.owner, status='pending').save()
class UrlItem(models.ListItemModel): """ An URL item for the UrlActivity. """ class Meta: root_field = 'activity' activity = models.ForeignKey('UrlActivity') url = models.URLField() name = models.CharField(max_length=50, blank=True) alt = models.CharField(max_length=50, blank=True)
class Gradebook(models.CodeschoolPage): """ The gradebook page for each student. Each student have a gradebook object for each of its enrolled courses. """ user = models.ForeignKey( 'auth.User', verbose_name=_('Student name'), on_delete=models.PROTECT, ) course = models.ForeignKey( 'cs_core.Course', on_delete=models.SET_NULL, null=True, ) # Wagtail admin parent_page_types = ['cs_core.Profile']
class CodingIoSubmission(QuestionSubmission): """ A response proxy class specialized in CodingIoQuestion responses. """ source = models.TextField(blank=True) language = models.ForeignKey(ProgrammingLanguage) objects = manager_instance(QuestionSubmission, CodingIoSubmissionQuerySet, use_for_related_fields=True)
class CustomFieldValue(models.Model): """ Represents a value of a custom field. Since custom fields can have many different types, we store all of them as a TextField and provide serializers for each type in order to convert each field to the correct type. The ``value`` attribute is always converted to the correct type. """ definition = models.ForeignKey(CustomFieldDefinition) profile = models.ForeignKey(Profile, related_name='custom_field_values') db_value = models.TextField() @property def value(self): tt = self.definition.type if tt == 'text': return self.db_value elif tt == 'float': return float(self.db_value)
class ExhibitEntry(models.ClusterableModel): """ Each user submission """ class Meta: unique_together = [('user', 'exhibit')] exhibit = models.ParentalKey(CodeExhibit, related_name='entries') user = models.ForeignKey(models.User, related_name='+', on_delete=models.CASCADE) name = models.CharField(max_length=200) source = models.TextField() image = models.ImageField(upload_to='images/code_exhibit/') # image = models.FileField(upload_to='images/code_exhibit/') votes_from = models.ManyToManyField(models.User, related_name='+') num_votes = models.IntegerField(default=int) objects = ExhibitEntryQuerySet.as_manager() def vote(self, user): """ Register a vote from user. """ if not self.votes_from.filter(id=user.id).count(): self.votes_from.add(user) self.num_votes += 1 self.save(update_fields=['num_votes']) def unvote(self, user): """ Remove a vote from user. """ if self.votes_from.filter(id=user.id).count(): self.votes_from.remove(user) self.num_votes -= 1 self.save(update_fields=['num_votes']) def icon_for_user(self, user): if user in self.votes_from.all(): return 'star' return 'start_border' # Wagtail admin panels = [ panels.FieldPanel('user'), panels.FieldPanel('source'), panels.FieldPanel('image'), panels.FieldPanel('num_votes'), ]
class Badge(models.TimeStampedModel, models.PolymorphicModel): """ An abstract badge that marks an accomplishment in a given badge track. """ track = models.ForeignKey(BadgeTrack, related_name='badges') name = models.CharField( _('name'), max_length=200, ) slug = models.CharField(unique=True) description = models.TextField( _('description'), help_text=_( 'A detailed description of the accomplishment required to receive ' 'the badge.'), ) message = models.TextField( _('message'), help_text=_( 'The message displayed when users receive the given badge')) image = models.ImageField( upload_to='gamification/badges/', blank=True, null=True, ) required_achievement = models.PositiveIntegerField( default=0, help_text=_( 'Abstract quantity that associated with linear badge tracks.'), ) level = models.PositiveIntegerField( _('Badge level'), help_text=_( 'The badge level: for linear badge tracks, it defines the ordering' 'between different badges.'), ) extra = models.JSONField(default=dict, ) users = models.ManyToManyField( models.User, through='GivenBadge', related_name='badges', ) def issue_badge(self, user): """ Issue badge for the given user. """ self.users.add(user)
class Task(models.Model): """ A task that can be on the backlog or on a sprint. """ STATUS_BACKLOG = 0 STATUS_TODO = 1 STATUS_DOING = 2 STATUS_DONE = 3 STATUS = models.Choices( (STATUS_BACKLOG, 'backlog'), (STATUS_TODO, 'todo'), (STATUS_DOING, 'doing'), (STATUS_DONE, 'done'), ) sprint = models.ForeignKey(Sprint, related_name='tasks') project = models.ForeignKey(ScrumProject, related_name='tasks') status = models.StatusField() created_by = models.ForeignKey(models.User, related_name='+') assigned_to = models.ManyToManyField(models.User, related_name='+') description = models.RichTextField() duration_hours = models.IntegerField() objects = TaskQuerySet.as_manager()
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()
class CodingIoSubmission(QuestionSubmission): """ A response proxy class specialized in CodingIoQuestion responses. """ source = models.TextField(blank=True) language = models.ForeignKey('core.ProgrammingLanguage') objects = manager_instance(QuestionSubmission, CodingIoSubmissionQuerySet, use_for_related_fields=True) def compute_hash(self): return md5hash(self.source + self.language.ref)