class FormQuestion(Question): """ FormQuestion's defines a question with multiple fields that can be naturally represented in a web form. A FormQuestion thus expect a response """ form_data = models.StreamField( [ #('numeric', blocks.NumericAnswerBlock()), #('boolean', blocks.BooleanAnswerBlock()), #('string', blocks.StringAnswerBlock()), #('date', blocks.DateAnswerBlock()), #('file', blocks.TextFileAnswerBlock()), #('script', blocks.ScriptGraderAnswerBlock()), ( 'content', blocks.StreamBlock([ ('description', blocks.RichTextBlock()), #('code', blocks.CodeBlock()), #('markdown', blocks.MarkdownBlock()), ('image', blocks.ImageChooserBlock()), ('document', blocks.DocumentChooserBlock()), ('page', blocks.PageChooserBlock()), ])), ], verbose_name=_('Fields'), help_text=_( 'You can insert different types of fields for the student answers. ' 'This works as a simple form that accepts any combination of the' 'different types of answer fields.')) def clean(self): super().clean() data = list(self.form_values()) if not data: raise ValidationError({ 'body': _('At least one form entry is necessary.'), }) # Test if ref keys are unique: when we implement this correctly, there # will have a 1 in 10**19 chance of collision. So we wouldn't expect # this to ever fail. ref_set = {value['ref'] for value in data} if len(ref_set) < len(data): raise ValidationError({ 'body': _('Answer block ref keys are not unique.'), }) def submit(self, raw_data=None, **kwargs): # Transform all values received as strings and normalize them to the # correct python objects. if raw_data is not None: response_data = {} children = self.stream_children_map() for key, value in raw_data.items(): child = children[key] block = child.block blk_value = child.value response_data[key] = block.normalize_response(blk_value, value) kwargs['response_data'] = response_data return super().submit(**kwargs) def stream_children(self): """ Iterates over AnswerBlock based stream children. """ return (blk for blk in self.form_data if blk.block_type != 'content') def stream_items(self): """ Iterates over pairs of (key, stream_child) objects. """ return ((blk.value['ref'], blk) for blk in self.stream_children()) def form_values(self): """ Iterate over all values associated with the question AnswerBlocks. """ return (blk.value for blk in self.stream_children()) def form_blocks(self): """ Iterate over all AnswerBlock instances in the question. """ return (blk.block for blk in self.stream_children()) def stream_child(self, key, default=NOT_PROVIDED): """ Return the StreamChild instance associated with the given key. If key is not found, return the default value, if given, or raises a KeyError. """ for block in self.form_data: if block.block_type != 'content' and block.value['ref'] == key: return block if default is NOT_PROVIDED: raise KeyError(key) return default def form_block(self, key, default=NOT_PROVIDED): """ Return the AnswerBlock instance for the given key. """ try: return self.stream_child(key).block except KeyError: if default is NOT_PROVIDED: raise return default def form_value(self, ref, default=NOT_PROVIDED): """ Return the form data for the given key. """ try: return self.stream_child(key).value except KeyError: if default is NOT_PROVIDED: raise return default def stream_children_map(self): """ Return a dictionary mapping keys to the corresponding stream values. """ return {blk.value['ref']: blk for blk in self.form_data} # Serving pages and routing @srvice.route(r'^submit-response/$') def route_submit(self, client, fileData=None, **kwargs): """ Handles student responses via AJAX and a srvice program. """ data = {} file_data = fileData or {} for key, value in kwargs.items(): if key.startswith('response__') and value: key = key[10:] # strips the heading 'response__' data[key] = value # We check the stream child type and take additional measures # depending on the type stream_child = self.stream_child(key) if stream_child.block_type == 'file': data[key] = file_data.get(value[0], '') super().route_submit( client=client, response_context=kwargs['response_context'], raw_data=data, ) def get_response_form(self): block = self.form_data[0] return block.render() def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['form'] = self.get_response_form() return context # Wagtail admin content_panels = Question.content_panels[:] content_panels.insert(-1, panels.StreamFieldPanel('form_data'))
from codeschool import blocks RESOURCE_BLOCKS = [ ('paragraph', blocks.RichTextBlock()), ('image', blocks.ImageChooserBlock()), ('embed', blocks.EmbedBlock()), # ('markdown', blocks.MarkdownBlock()), ('url', blocks.URLBlock()), ('text', blocks.TextBlock()), ('char', blocks.CharBlock()), # ('ace', blocks.AceBlock()), ('bool', blocks.BooleanBlock()), ('doc', blocks.DocumentChooserBlock()), # ('snippet', blocks.SnippetChooserBlock(GradingMethod)), ('date', blocks.DateBlock()), ('time', blocks.TimeBlock()), ('stream', blocks.StreamBlock([('page', blocks.PageChooserBlock()), ('html', blocks.RawHTMLBlock())])), ]