def clean(self): """ Validate the iospec_source field. """ super().clean() # We first should check if the iospec_source has been changed and thus # requires a possibly expensive validation. source = self.iospec_source iospec_hash = md5hash(source) if self.iospec_hash != iospec_hash: try: self.iospec = iospec.parse_string(self.iospec_source) except Exception: raise ValidationError( {'iospec_source': _('invalid iospec syntax')}) else: self.iospec_hash = iospec_hash if self.pk is None: self.is_usable = self.iospec.is_simple self.is_consistent = True else: self.is_usable = self._is_usable(self.iospec) self.is_consistent = self._is_consistent(self.iospec)
def parent_hash(self): """ Return the iospec hash from the question current iospec/iospec_size. """ parent = self.question return md5hash(parent.iospec_source + str(parent.iospec_size))
def get_response_hash(cls, response_data): """ Computes a hash for the response_data attribute. """ if response_data: data = json.dumps(response_data, default=json_default) return md5hash(data) return ''
def update(self, commit=True): """ Update the internal iospec source and hash keys to match the given parent iospec value. It raises a ValidationError if the source code is invalid. """ iospec = self.question.iospec result = self._update_state(iospec, self.source, self.language) self.iospec_source = result.source() self.source_hash = md5hash(self.source) self.iospec_hash = self.parent_hash() if commit: self.save()
def clean(self): super().clean() if self.question is None: return # We only have to update if the parent's hash is incompatible with the # current hash and the source field is defined. We make this test to # perform the expensive code re-evaluation only when strictly necessary parent_hash = self.parent_hash() source_hash = md5hash(self.source) if parent_hash != self.iospec_hash or source_hash != self.source_hash: iospec = self.question.iospec result = self._update_state(iospec, self.source, self.language) self.iospec_source = result.source() self.source_hash = source_hash self.iospec_hash = parent_hash
def has_changed_source(self): """ Return True if source is not consistent with its hash. """ return self.source_hash != md5hash(self.source)
def save(self, *args, **kwargs): self.source_hash = md5hash(self.source) super().save(*args, **kwargs)
class AnswerKey(models.Model): """ Represents an answer to some question given in some specific computer language plus the placeholder text that should be displayed. """ NULL_SOURCE_HASH = md5hash('') class ValidationError(Exception): pass class Meta: verbose_name = _('answer key') verbose_name_plural = _('answer keys') unique_together = [('question', 'language')] question = models.ParentalKey( CodingIoQuestion, related_name='answers' ) language = models.ForeignKey( ProgrammingLanguage, related_name='+', ) source = models.TextField( _('answer source code'), blank=True, help_text=_( 'Source code for the correct answer in the given programming ' 'language.' ), ) placeholder = models.TextField( _('placeholder source code'), blank=True, help_text=_( 'This optional field controls which code should be placed in ' 'the source code editor when a question is opened. This is ' 'useful to put boilerplate or even a full program that the ' 'student should modify. It is possible to configure a global ' 'per-language boilerplate and leave this field blank.' ), ) source_hash = models.CharField( max_length=32, default=NULL_SOURCE_HASH, help_text=_('Hash computed from the reference source'), ) error_message = models.TextField( _('error message'), blank=True, help_text=_( 'If an error is found on post-validation, an error message is ' 'stored in here.' ) ) def __repr__(self): return '<AnswerKey: %s>' % self def __str__(self): try: title = self.question.title except: title = '<untitled>' return '%s (%s)' % (title, self.language) def save(self, *args, **kwargs): self.source_hash = md5hash(self.source) super().save(*args, **kwargs) def clean(self): try: check_syntax(self.source, lang=self.language.ejudge_ref()) except SyntaxError as ex: msg = _('Invalid syntax: %(msg)') % {'msg': str(ex)} raise ValidationError({'source': msg}) super().clean() # Validation is async: # # We first run basic validations in the foreground and later attempt # at more detailed validations that requires us to run source code (and # thus possibly wait a long time). # # If this later validation step encounters errors, it saves them on # the model instance. The next time the model runs, we can re-raise # them on the interface. The user has an option to bypass these checks. # Changing the code or the iospec entries should expire these # errors. if self.error_message and not self.is_ignoring_validation_errors(): raise ValidationError({'source': mark_safe(self.error_message)}) def is_ignoring_validation_errors(self): """ True to ignore errors found in post-validation. """ return self.question.ignore_validation_errors def set_error_message(self, message): """ Saves error message. """ try: self.error_message = message.__html__() except AttributeError: self.error_message = escape(message) def has_changed_source(self): """ Return True if source is not consistent with its hash. """ return self.source_hash != md5hash(self.source) # # # def single_reference(self): """ Return True if it is the only answer key in the set that defines a source attribute. """ if not self.source: return False try: return self.question.answers.has_program().get() == self except self.DoesNotExist: return False # Wagtail admin panels = [ panels.FieldPanel('language'), panels.FieldPanel('source'), panels.FieldPanel('placeholder'), ]
def compute_hash(self): return md5hash(self.source + self.language.ref)
def compute_hash(self): return md5hash(self.choice_id)