class ProgrammingLanguage(Base, models.Model): """Represents a programming language.""" ref = models.CharField(max_length=10, primary_key=True) name = models.CharField(max_length=140) comments = models.TextField(blank=True) @classmethod def get_language(cls, ref): """Return the programming language object from the given ref.""" try: ref = default_aliases.get(ref, ref) return cls.objects.get(ref=ref) except cls.DoesNotExist: name = default_languages.get(ref) if ref is None: raise else: return cls.objects.create(ref=ref, name=name) def save(self, *args, **kwargs): SourceFormat.setdefault(self.ref, name=self.name, comments=self.comments) super().save(*args, **kwargs)
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 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 Personalization(models.Model): """ Personalize a few cosmetic aspects of the activity. """ name = models.CharField(max_length=140) icon_src = models.CharField( _('activity icon'), max_length=50, blank=True, help_text=_( 'Optional icon name that can be used to personalize the activity. ' 'Material icons are available by using the "material:" namespace ' 'as in "material:menu".'), ) @property def material_icon(self): """ The material icon used in conjunction with the activity. """ if self.icon_src.startswith('material:'): return self.icon_src[9:] return self.default_material_icon @property def icon_html(self): """ A string of HTML source that points to the icon element fo the activity. """ return '<i class="material-icon">%s</i>' % self.material_icon
class ProgrammingLanguage(models.Model): """Represents a programming language.""" ref = models.CharField(max_length=10, primary_key=True) name = models.CharField(max_length=140) comments = models.TextField(blank=True) __populated = False @classmethod def from_ref(cls, ref): """Return the programming language object from the given ref.""" return cls.objects.get(ref=ref) @classmethod def populate(cls): if not cls.__populated: for line in supported_languages.strip().splitlines(): ref, _, name = line.partition(': ') cls.setdefault(ref, name) cls.__populated = True @classmethod def setdefault(cls, ref, *args, **kwds): """Create object if it does not exists.""" try: return cls.objects.get(ref=ref) except cls.DoesNotExist: new = cls(ref, *args, **kwds) new.save() return new def __str__(self): return '%s (%s)' % (self.ref, self.name)
class SourceFormat(Base, models.Model): """ Generalizes a programming language to other non-programming based file formats. """ # TODO: make it a base class for ProgrammingLanguage ref = models.CharField(max_length=10, primary_key=True) name = models.CharField(max_length=140) comments = models.TextField(blank=True)
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 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 TextQuestion(Question): """ A very simple question with a simple Text answer. """ class Meta: verbose_name = _('Text question') verbose_name_plural = _('Text questions') correct_answer = models.CharField( _('Correct answer'), help_text=_('The expected Text answer for question.'), max_length=100, ) label = models.CharField( _('Label'), max_length=100, default=_('Answer'), help_text=_( 'The label text that is displayed in the submission form.'), ) help_text = models.TextField( _('Help text'), blank=True, help_text=_( 'Additional explanation that is displayed under the input form.')) instant_autograde = True def get_form_class(self): class TextForm(forms.Form): value = forms.CharField(label=self.label, required=True, max_length=250) return TextForm def get_form(self, *args, **kwargs): return self.get_form_class()(*args, **kwargs) # Serving Pages template = 'questions/text/detail.jinja2' def get_context(self, request, **kwargs): ctx = super().get_context(request, **kwargs) ctx['form'] = self.get_form(request.POST) return ctx def get_submission_kwargs(self, request, kwargs): return {'value': str(kwargs.get('value', None) or 0)}
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 Group(models.Model): """ A group of students. """ name = models.CharField(max_length=100) users = models.ManyToManyField(models.User, related_name='+')
class FreeFormQuestion(Question): """ A free form question is *not* automatically graded. The student can submit a resource that can be a text, code, file, image, etc and a human has to analyse and grade it manually. """ Type = Type type = models.IntegerField( _('Text type'), choices=[ (Type.CODE.value, _('Code')), (Type.RICHTEXT.value, _('Rich text')), (Type.FILE.value, _('File')), (Type.PHYSICAL.value, _('Physical delivery')), ], default=Type.CODE, ) filter = models.CharField( _('filter'), max_length=30, blank=True, help_text=_( 'Filters the response by some criteria.' ), ) class Meta: autograde = False
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 FreeFormSubmission(QuestionSubmission): """ Submission object for free form questions. """ data = models.TextField(blank=True) metadata = models.CharField(blank=True, max_length=200)
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 Faculty(models.DescribablePage): """ Describes a faculty/department or any institution that is responsible for managing disciplines. """ location_coords = models.CharField( _('coordinates'), max_length=255, blank=True, null=True, help_text=_( 'Latitude and longitude coordinates for the faculty building. The ' 'coordinates are selected from a Google Maps widget.'), ) @property def courses(self): return apps.get_model( 'cs_core', 'Course').objects.filter(path__startswith=self.path) # Wagtail admin parent_page_types = ['wagtailcore.Page'] subpage_types = None subpage_types = ['Discipline'] content_panels = models.DescribablePage.content_panels + [ panels.MultiFieldPanel([ panels.FieldPanel('location_coords', classname="gmap"), ], heading=_('Location')), ]
class TimeSlot(models.Model): """ Represents the weekly time slot that can be assigned to lessons for a given course. """ class Meta: ordering = ('weekday', 'start') MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(7) WEEKDAY_CHOICES = [ (MONDAY, _('Monday')), (TUESDAY, _('Tuesday')), (WEDNESDAY, _('Wednesday')), (THURSDAY, _('Thursday')), (FRIDAY, _('Friday')), (SATURDAY, _('Saturday')), (SUNDAY, _('Sunday')) ] course = models.ParentalKey( 'Classroom', related_name='time_slots' ) weekday = models.IntegerField( _('weekday'), choices=WEEKDAY_CHOICES, help_text=_('Day of the week in which this class takes place.') ) start = models.TimeField( _('start'), blank=True, null=True, help_text=_('The time in which the class starts.'), ) end = models.TimeField( _('ends'), blank=True, null=True, help_text=_('The time in which the class ends.'), ) room = models.CharField( _('classroom'), max_length=100, blank=True, help_text=_('Name for the room in which this class takes place.'), ) # Wagtail admin panels = [ panels.FieldRowPanel([ panels.FieldPanel('weekday', classname='col6'), panels.FieldPanel('room', classname='col6'), ]), panels.FieldRowPanel([ panels.FieldPanel('start', classname='col6'), panels.FieldPanel('end', classname='col6'), ]), ]
class Discipline(models.TimeStampedModel): """ Represents an academic discipline. """ name = models.CharField(max_length=100) slug = models.SlugField(_('short name')) description = models.RichTextField(blank=True) syllabus = models.RichTextField(blank=True)
class FileFormat(models.Model): """ Represents a source file format. These can be programming languages or some specific data format. """ ref = models.CharField(max_length=10, unique=True) name = models.CharField(max_length=140) comments = models.TextField(blank=True) is_binary = models.BooleanField(default=False) is_language = models.BooleanField(default=False) is_supported = models.BooleanField(default=False) objects = models.Manager.from_queryset(SourceFormatQuerySet)() def __init__(self, *args, **kwargs): kwargs.setdefault('name', kwargs.get('ref', 'unnamed format').title()) super().__init__(*args, **kwargs) def ace_mode(self): """ Return the ace mode associated with the language. """ return ACE_ALIASES.get(self.ref) def pygments_mode(self): """ Returns the Pygments mode associated with the language. """ return self.ref def ejudge_ref(self): """ A valid reference for the language in the ejudge framework. """ return self.ref def __str__(self): return self.name
class Discipline(models.Model): """A discipline represents one abstract academic discipline. Each discipline can be associated with many courses.""" name = models.CharField(_('name'), max_length=200) abbreviation = AutoSlugField(populate_from='name') short_description = models.TextField(_('short description')) long_description = RichTextField(_('long description')) def __str__(self): return '%s (%s)' % (self.name, self.abbreviation)
class SocialPresenceSettings(BaseSetting): facebook = models.URLField(help_text='Your Facebook page URL') instagram = models.CharField( max_length=255, help_text='Your Instagram username, without the @') trip_advisor = models.URLField(help_text='Your Trip Advisor page URL') youtube = models.URLField( help_text='Your YouTube channel or user account URL') panels = [ panels.FieldPanel('facebook'), panels.FieldPanel('instagram'), panels.FieldPanel('trip_advisor'), ]
class Feature(models.Model): """ A Feature in a sentiment board. """ board = models.ParentalKey(SentimentBoard, related_name='features') name = models.CodeschoolNameField() description = models.CodeschoolDescriptionField(blank=True) image = models.ImageField(blank=True, null=True) icon = models.CharField(max_length=50) class Meta: unique_together = [('board', 'name')]
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 NumericQuestion(Question): """ A very simple question with a simple numeric answer. """ correct_answer = models.FloatField( _('Correct answer'), help_text=_('The expected numeric answer for question.')) tolerance = models.FloatField( _('Tolerance'), default=0, help_text=_('If tolerance is zero, the responses must be exact.'), ) label = models.CharField( _('Label'), max_length=100, default=_('Answer'), help_text=_( 'The label text that is displayed in the submission form.'), ) help_text = models.TextField( _('Help text'), blank=True, help_text=_( 'Additional explanation that is displayed under the input form.')) class Meta: verbose_name = _('Numeric question') verbose_name_plural = _('Numeric questions') def get_form_class(self): class NumericForm(forms.Form): value = forms.FloatField(label=self.label, required=True) return NumericForm def get_form(self, *args, **kwargs): return self.get_form_class()(*args, **kwargs) # Serving Pages template = 'questions/numeric/detail.jinja2' def get_context(self, request, **kwargs): ctx = super().get_context(request, **kwargs) ctx['form'] = self.get_form(request.POST) return ctx def get_submission_kwargs(self, request, kwargs): return {'value': float(kwargs.get('value', None) or 0)}
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 TimeSlot(models.Model): """Represents the weekly time slot that can be assigned to classes for a given course.""" class Meta: unique_together = ('course', 'weekday') weekday = models.IntegerField( choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')] ) start = models.TimeField() end = models.TimeField() course = models.ForeignKey(Course) room = models.CharField(max_length=100, blank=True)
class FreeAnswerQuestion(Question): DATA_FILE = 'file' DATA_IMAGE = 'image' DATA_PDF = 'pdf' DATA_PLAIN = 'plain' DATA_RICHTEXT = 'richtext' DATA_CHOICES = ( (DATA_FILE, _('Arbitary file')), (DATA_IMAGE, _('Image file')), (DATA_PDF, _('PDF file')), (DATA_RICHTEXT, _('Rich text input')), (DATA_RICHTEXT, _('Plain text input')), ) metadata = models.TextField() data_type = models.CharField(choices=DATA_CHOICES, max_length=10) data_file = models.FileField(blank=True, null=True)
class ResponseDataMixin(models.Model): """ Mixin that adds response data for several fields stored in a JSON dictionary. """ class Meta: abstract = True response_data = models.JSONField( null=True, blank=True, ) response_hash = models.CharField( max_length=32, blank=True, )
class FreeTextQuestion(Question): TYPE_CODE = 0 TYPE_RICHTEXT = 1 text_type = models.IntegerField( _('Text type'), choices=[ (TYPE_CODE, _('Code')), (TYPE_RICHTEXT, _('Rich text')), ], default=TYPE_CODE, ) syntax_highlight = models.CharField( choices=[x.split(':') for x in formats], default='python', help_text=_('Syntax highlight for code based questions.'), )