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 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 CodePost(Post): """ Post some code. """ language = models.ForeignKey('core.ProgrammingLanguage', related_name='+') source = models.TextField()
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 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 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 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 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 FreeFormSubmission(QuestionSubmission): """ Submission object for free form questions. """ data = models.TextField(blank=True) metadata = models.CharField(blank=True, max_length=200)
class PageActivity(Activity): """ Students complete this activity by seeing the content of a webpage defined in markdown. This activity allows teachers to share arbitrary content with the students. """ body = models.TextField()
class StringMatchQuestion(Question): answer = models.TextField() is_regex = models.BooleanField(default=True) def grade(self, response): if self.is_regex: value = response.value else: return super().grade(response)
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 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 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 FileItem(models.ListItemModel): """A file item for the FileDownloadActivity.""" class Meta: root_field = 'activity' activity = models.ForeignKey('FileDownloadActivity') file = models.FileField(upload_to='file-activities/') name = models.TextField(blank=True) description = models.TextField(blank=True) # Derived properties size = property(lambda x: x.file.size) url = property(lambda x: x.file.url) open = property(lambda x: x.file.open) close = property(lambda x: x.file.close) save_file = property(lambda x: x.file.save) delete_file = property(lambda x: x.file.delete) def save(self, *args, **kwargs): if not self.name: self.name = os.path.basename(self.file.name) super().save(*args, **kwargs)
class TestState(models.TimeStampedModel): """ Register iospec expansions for a given question. """ class Meta: unique_together = [('question', 'hash')] question = models.ForeignKey('CodingIoQuestion') hash = models.models.CharField(max_length=32) uuid = models.UUIDField(default=uuid.uuid4, editable=False) pre_tests_source = models.TextField(blank=True) post_tests_source = models.TextField(blank=True) pre_tests_source_expansion = models.TextField(blank=True) post_tests_source_expansion = models.TextField(blank=True) @property def is_current(self): return self.hash == self.question.test_state_hash def __str__(self): status = 'current' if self.is_current else 'outdated' return 'TestState for %s (%s)' % (self.question, status)
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 CodeCarouselItem(models.Orderable): """ A simple state of the code in a SyncCodeActivity. """ activity = models.ParentalKey('cs_core.CodeCarouselActivity', related_name='items') text = models.TextField() timestamp = models.DateTimeField(auto_now=True) # Wagtail admin panels = [ panels.FieldPanel('text', widget=blocks.AceWidget()), ]
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)
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 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 StringMatchQuestion(Question): """ The student response is compared with an answer_key string either by simple string comparison or using a regular expression. """ answer_key = models.TextField() is_regex = models.BooleanField(default=True) def grade(self, response): if self.is_regex: value = response.value else: return super().grade(response)
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 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 SyncCodeEditItem(models.Model): """ A simple state of the code in a SyncCodeActivity. """ activity = models.ForeignKey(SyncCodeActivity, related_name='data') text = models.TextField() next = models.OneToOneField('self', blank=True, null=True, related_name='previous') timestamp = models.DateTimeField(auto_now=True) @property def prev(self): try: return self.previous except ObjectDoesNotExist: return None
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 LoginSettings(BaseSetting): username_as_school_id = models.BooleanField( default=False, help_text=_( 'If true, force the username be equal to the school id for all ' 'student accounts.')) school_id_regex = models.TextField( default='', blank=True, help_text=_( 'A regular expression for matching valid school ids. If blank, no' 'check will be performed on the validity of the given school ids'), ) panels = [ panels.MultiFieldPanel([ panels.FieldPanel('username_as_school_id'), panels.FieldPanel('school_id_regex'), ], heading=_('School id configuration')) ]
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 BadgeTrack(models.PolymorphicModel): """ A badge track represents a single type of action that can give several badges for different levels of accomplishment. """ name = models.CharField( _('name'), max_length=200, ) slug = models.SlugField(unique=True) description = models.TextField( _('description'), help_text=_('A detailed description of the badge track.'), ) extra = models.JSONField(default=dict) badge_class = None @classmethod def instance(cls): """ Returns the default instance for the given track. """ return cls() @classmethod def badge(cls, slug, name, description, message, **kwargs): """ Construct a badge instance in the current track. """ return cls.badge_class(track=cls.instance(), name=name, slug=slug, description=description, message=message, **kwargs)