Example #1
0
class FormTemplateFont(Base):
    '''Represents a font specification for visual form templates.'''
    __tablename__ = "form_template_font"
    id = id_column(__tablename__)

    place = Column(UnicodeText, nullable=False)
    name = Column(UnicodeText, nullable=False)
    size = Column(Integer, nullable=False)
    bold = Column(Boolean, default=False)
    italic = Column(Boolean, default=False)

    template_id = Column(Integer, ForeignKey('form_template.id'))
    template = relationship(FormTemplate,
                            backref=backref('fonts', cascade='all'))

    __table_args__ = (UniqueConstraint(
        'template_id',
        'place',
        name='form_template_font_template_id_place_key'), {})

    def __unicode__(self):
        style = ""
        style += " bold " if self.bold else ""
        style += " italic " if self.italic else ""
        return "{0} {1} {2}".format(self.name, self.size, style)

    def __repr__(self):
        return "FormTemplateFont: {0} = {1}" \
            .format(self.place, self.__unicode__())
Example #2
0
class Entry(Base):
    '''Represents a form entry.
    *created* is the moment the entry was created
    *form_id* points to the corresponding form.
    *entry_number* is the number of an entry.
    '''
    __tablename__ = "entry"
    id = id_column(__tablename__)
    created = now_column()  # when was this record created
    entry_number = Column(Integer)
    new = Column(Boolean, default=True)

    form_id = Column(Integer, ForeignKey('form.id'), index=True)
    form = relationship(Form,
                        backref=backref('entries',
                                        order_by=(desc(created)),
                                        cascade='all'))

    collector_id = Column(Integer, ForeignKey('collector.id'), index=True)
    collector = relationship(Collector,
                             backref=backref('entries', order_by=id))

    def fields_data(self, field_idx="FIELD_ID", fields=[], request=None):
        url = request.application_url if request else ''
        if fields == []:
            # Get all text data
            if field_idx == "FIELD_ID":
                fields_data_list = [{
                    'id': f.id,
                    'data': f.value(self).format(url=url)
                } for f in self.form.fields]
            elif field_idx == "FIELD_LABEL":
                fields_data_list = {
                    'form_title':
                    self.form.name,
                    'entry_id':
                    self.id,
                    'entry_number':
                    self.entry_number,
                    'fields': [{
                        'position': f.position + 1,
                        'label': f.label,
                        'data': f.value(self).format(url=url),
                        'type': f.typ.name
                    } for f in self.form.fields]
                }
        return fields_data_list

    def delete_entry(self):
        sas.delete(self)
        sas.flush()
        return ()

    def to_dict(self):
        return {
            'entry_id': self.id,
            'entry_created': unicode(self.created)[:16],
            'entry_number': self.entry_number,
            'entry_new': self.new
        }
Example #3
0
class FieldOption(Base):
    '''Represents a field option.'''
    __tablename__ = "field_option"
    id = id_column(__tablename__)
    option = Column(UnicodeText)
    value = Column(UnicodeText)

    field_id = Column(Integer, ForeignKey('field.id'), nullable=False)
    field = relationship(Field,
                         backref=backref('options',
                                         cascade='all, delete-orphan',
                                         single_parent=True))

    def __repr__(self):
        return "[{}. {} = {}]".format(self.id, self.option, self.value)

    def copy(self):
        field_option_copy = FieldOption()

        # field option instance copy
        for attr in ('option', 'value'):
            field_option_copy.__setattr__(attr, self.__getattribute__(attr))

        sas.add(field_option_copy)

        return field_option_copy
Example #4
0
class TextData(Base):
    __tablename__ = "text_data"

    id = id_column(__tablename__)
    value = Column(UnicodeText, nullable=True)

    field_id = Column(Integer, ForeignKey('field.id'))
    field = relationship(Field, backref=backref('text_data', cascade='all'))

    entry_id = Column(Integer, ForeignKey('entry.id'))
    entry = relationship(Entry, backref=backref('text_data', cascade='all'))
Example #5
0
class NumberData(Base):
    __tablename__ = "number_data"

    id = id_column(__tablename__)
    value = Column(Float, nullable=True)

    field_id = Column(Integer, ForeignKey('field.id'))
    field = relationship(Field, backref=backref('number_data', cascade='all'))

    entry_id = Column(Integer, ForeignKey('entry.id'))
    entry = relationship(Entry, backref=backref('number_data', cascade='all'))
Example #6
0
class ListData(Base):
    __tablename__ = "list_data"
    id = id_column(__tablename__)

    value = Column(Integer, ForeignKey('list_option.id'), index=True)
    list_option = relationship(ListOption, backref='list_data')

    entry_id = Column(Integer, ForeignKey('entry.id'), index=True)
    entry = relationship(Entry, backref=backref('list_data'))

    field_id = Column(Integer, ForeignKey('field.id'), index=True)
    field = relationship(Field, backref=backref('list_data', cascade='all'))
Example #7
0
class FieldType(Base):
    '''Represents a kind of field that is possible in a form.
    *name* is the type's name.
    *description* is a brief explanation of the type.
    '''
    __tablename__ = "field_type"
    id = id_column(__tablename__)
    name = Column(UnicodeText, nullable=False)
    description = Column(UnicodeText, nullable=True)

    def __repr__(self):
        return 'FieldType(id={0}, name="{1}")'.format(self.id, self.name)
Example #8
0
class FormTemplate(Base):
    '''Represents a visual template of a form.'''
    __tablename__ = "form_template"
    id = id_column(__tablename__)

    # System templates have this not null.
    system_template_id = Column(Integer, unique=True, default=None)
    system_template_name = Column(UnicodeText(32))

    @property
    def system(self):
        '''Returns True if this template is a *system* template.'''
        return True if self.system_template_id else False

    def __repr__(self):
        return "FormTemplate (id = {0})".format(self.id)

    def __unicode__(self):
        return self.__repr__()

    def to_dict(self):
        colors = {}
        fonts = {}
        for c in self.colors:
            colors[c.place] = c.hexcode
        for f in self.fonts:
            fonts[f.place] = dict(name=f.name,
                                  size=f.size,
                                  bold=f.bold,
                                  italic=f.italic)
        return {
            'formtemplate_id': self.id,
            'system_template_id': self.system_template_id,
            'system_template_name': self.system_template_name,
            'colors': colors,
            'fonts': fonts
        }

    def css_template_dicts(self):
        fonts = dict()
        for ftf in self.fonts:
            fonts[ftf.place] = dict()
            for attr in ('name', 'size', 'bold', 'italic'):
                fonts[ftf.place][attr] = ftf.__getattribute__(attr)
        colors = dict()
        for ftc in self.colors:
            colors[ftc.place] = ftc.hexcode
        return fonts, colors
Example #9
0
class ListOption(Base):
    __tablename__ = "list_option"

    id = id_column(__tablename__)
    label = Column(UnicodeText())
    value = Column(UnicodeText())
    opt_default = Column(Boolean(), default=False)
    position = Column(Integer, default=0)
    status = Column(
        Enum('Approved',
             'Rejected',
             'Awaiting moderation',
             'Form owner',
             name='list_option_status'))

    field_id = Column(Integer, ForeignKey('field.id'), index=True)
    field = relationship(Field, backref=backref('list_options', cascade='all'))
Example #10
0
class FileUploadTempStore(Base):

    __tablename__ = "file_upload_temp_store"
    id = id_column(__tablename__)
    created = now_column()

    uid = Column(Unicode(10), nullable=False)
    mimetype = Column(Unicode(255)) #http://tools.ietf.org/html/rfc4288#section-4.2
    filename = Column(UnicodeText)
    size = Column(Integer)
    path = Column(UnicodeText)
    thumbnail_path = Column(UnicodeText)

    def to_dict(self):
        d = {k: getattr(self, k) for k in ('created', 'uid',
            'mimetype', 'filename', 'size', 'path', 'thumbnail_path')}
        return d
Example #11
0
class FileData(Base):

    __tablename__ = "file_data"
    id = id_column(__tablename__)
    created = now_column()

    mimetype = Column(
        Unicode(255))  #http://tools.ietf.org/html/rfc4288#section-4.2
    filename = Column(UnicodeText)
    size = Column(Integer)
    path = Column(UnicodeText)
    thumbnail_path = Column(UnicodeText)

    field_id = Column(Integer, ForeignKey('field.id'))
    field = relationship(Field, backref=backref('file_data', cascade='all'))

    entry_id = Column(Integer, ForeignKey('entry.id'))
    entry = relationship(Entry, backref=backref('file_data', cascade='all'))
Example #12
0
class FormTemplateColor(Base):
    '''Represents a color specification for visual form templates.'''
    __tablename__ = "form_template_color"
    id = id_column(__tablename__)

    place = Column(UnicodeText, nullable=False)
    hexcode = Column(UnicodeText, nullable=False)

    template_id = Column(Integer, ForeignKey('form_template.id'))
    template = relationship(FormTemplate,
                            backref=backref('colors', cascade='all'))

    __table_args__ = (UniqueConstraint(
        'template_id',
        'place',
        name='form_template_color_template_id_place_key'), {})

    def __unicode__(self):
        return self.hexcode

    def __repr__(self):
        return "FormTemplateColor: {0} = {1}" \
            .format(self.place, self.__unicode__())
Example #13
0
class Field(Base):
    '''Represents a field of a form.
    *label* is the text that appears next to the field, identifying it.
    *description* is a brief explanation.
    *rich* may contain a rich text alternative to both *label* and
    *description* together.
    *use_rich* is a flag that determines which alternative is to be used.
    *help_text* is a long explanation.
    *title* is short content for a tooltip (HTML "title" attribute).
    *position* is an integer for ordering fields inside the form.
    *required* states whether filling in this field is mandatory.
    *form_id* points to the form that owns this field.
    '''
    __tablename__ = "field"
    id = id_column(__tablename__)
    label = Column(UnicodeText, nullable=False)
    description = Column(UnicodeText, nullable=True)
    rich = Column(UnicodeText, nullable=False, default='')
    use_rich = Column(Boolean, default=False)
    help_text = Column(UnicodeText, nullable=True)
    title = Column(UnicodeText, nullable=True)
    position = Column(Integer)
    required = Column(Boolean)

    typ_id = Column(ForeignKey('field_type.id'))  # TODO: index?
    typ = relationship(FieldType)

    form_id = Column(Integer, ForeignKey('form.id'), index=True)
    form = relationship(Form,
                        backref=backref('fields',
                                        order_by=position,
                                        cascade='all'))

    def __repr__(self):
        return '{} #{} "{}"{}'.format(self.typ.name, self.id, self.label,
                                      '*' if self.required else '')

    @reify
    def fieldtype(self):
        return fields_dict[self.typ.name](self)

    def to_dict(self, to_export=False):
        d = self.fieldtype.to_dict(to_export=to_export)
        return d

    def save_option(self, option, value):
        return self.fieldtype.save_option(option, value)

    def save_options(self, options_dict):
        return self.fieldtype.save_options(options_dict)

    def validate_and_save(self, props):
        return self.fieldtype.validate_and_save(props)

    def get_option(self, option):
        opt = sas.query(FieldOption)\
                    .filter(FieldOption.field_id == self.id) \
                    .filter(FieldOption.option == option).first()
        if opt:
            return opt.value
        else:
            return self.fieldtype.defaultValue[option]

    def value(self, entry):
        return self.fieldtype.value(entry)

    copy_props = 'label description rich use_rich help_text title position ' \
                 'required typ'.split()

    def copy(self):
        field_copy = Field()
        # field instance copy
        for attr in self.copy_props:
            field_copy.__setattr__(attr, self.__getattribute__(attr))
        # field options copy
        for o in self.options:
            field_copy.options.append(o.copy())
        # field specific options copy
        fieldtype = fields_dict[self.typ.name](field_copy)
        if getattr(fieldtype, 'copy', None):
            fieldtype.copy(self)
        sas.add(field_copy)
        return field_copy
Example #14
0
class Form(Base):
    '''Represents a form as created by a user.
    *name* is the text that appears at the top of the form, identifying it.
    *description* is a brief explanation.
    *rich* may contain a rich text alternative to both *name* and
    *description* together.
    *use_rich* is a flag that determines which alternative is to be used.
    '''
    __tablename__ = "form"
    id = id_column(__tablename__)
    created = now_column()  # when was this record created
    modified = now_column()  # when was this form saved
    name = Column(UnicodeText(255), nullable=False)
    description = Column(UnicodeText)
    rich = Column(UnicodeText, nullable=False, default='')
    use_rich = Column(Boolean, default=False)
    submit_label = Column(UnicodeText(255))
    # Incremented number of the last entry. Serves as a counter of the entries
    last_entry_number = Column(Integer, default=0)
    # How many new entries since last visit
    new_entries = Column(Integer, default=0)

    # TODO: Create an index here when categories make their triumphant comeback
    category_id = Column(Integer, ForeignKey('form_category.id'))
    category = relationship(FormCategory,
                            backref=backref('forms', order_by=name))

    # TODO: Create an index here if we ever query from the parent
    template_id = Column(Integer, ForeignKey('form_template.id'))
    template = relationship(FormTemplate,
                            backref=backref('forms', cascade='all'))

    user_id = Column(Integer, ForeignKey('user.id'), index=True)
    user = relationship(User,
                        backref=backref('forms', order_by=name, cascade='all'))

    STATUS_EDITION = _("edition")
    STATUS_PENDING = _("pending")
    STATUS_PUBLISHED = _("published")
    STATUS_CLOSED = _("closed")

    @property
    def status(self):
        from mootiro_form.models.collector import Collector
        collectors_status = (Collector.STATUS_DURING, Collector.STATUS_BEFORE,
                             Collector.STATUS_AFTER)
        classifier = {}
        for cs in collectors_status:
            classifier[cs] = []
        for c in self.collectors:
            classifier[c.status].append(c)

        if classifier[Collector.STATUS_DURING]:
            status = self.STATUS_PUBLISHED
            num_colls_in_status = len(classifier[Collector.STATUS_DURING])
        elif classifier[Collector.STATUS_BEFORE]:
            status = self.STATUS_PENDING
            num_colls_in_status = len(classifier[Collector.STATUS_BEFORE])
        elif classifier[Collector.STATUS_AFTER]:
            status = self.STATUS_CLOSED
            num_colls_in_status = len(classifier[Collector.STATUS_AFTER])
        else:
            status = self.STATUS_EDITION
            num_colls_in_status = 0

        return (status, num_colls_in_status)

    def __unicode__(self):
        return self.name

    def __repr__(self):
        return '{0}. {1}'.format(self.id, self.name)

    @property
    def num_entries(self):
        num_entries = sas.query(Entry).filter(Entry.form_id == self.id).count()
        return num_entries

    def to_dict(self):
        return {'form_id': self.id,
                'form_name': self.name or _('Untitled form'),
                'form_entries': self.num_entries,
                'form_new_entries': self.new_entries,
                'form_description': self.description,
                'form_rich': self.rich,
                'form_use_rich': self.use_rich,
                'form_created': unicode(self.created)[:16],
                'form_modified': unicode(self.modified)[:16],
                'form_status': self.status[0],
                'form_status_num': self.status[1],
                'form_questions': sas.query(Field) \
                    .filter(Field.form_id == self.id).count(),
        }

    def export_json(self):
        form_dict = dict(form_name=self.name or _('Untitled form'),
                         form_description=self.description,
                         fields=[f.to_dict(to_export=True) \
                                 for f in self.fields])
        for field in form_dict['fields']:
            field.pop('field_id')
        return json.dumps(form_dict, indent=4)

    copy_props = 'user category rich use_rich name template description ' \
                 'submit_label'.split()

    def copy(self):
        form_copy = Form()
        # form instance copy
        for attr in self.copy_props:
            setattr(form_copy, attr, getattr(self, attr))
        # fields copy
        for f in self.fields:
            form_copy.fields.append(f.copy())
        sas.add(form_copy)
        return form_copy
Example #15
0
class Collector(Base):
    '''Represents a collector to collect form entries.'''
    __tablename__ = "collector"
    id = id_column(__tablename__)

    # Inheritance configuration
    typ = Column('type', UnicodeText(50))
    __mapper_args__ = {'polymorphic_on': typ}

    name = Column(UnicodeText(255), nullable=False)

    # When an entry is received, we can either display a thanks message,
    # or redirect to some URL. 3 columns are needed for this:
    thanks_message = Column(UnicodeText)
    thanks_url = Column(UnicodeText(2000))

    # We define on_completion as a property to validate its possible values:
    ON_COMPLETION_VALUES = ('msg', 'url')
    _on_completion = Column('on_completion', Unicode(3))

    @hybrid_property
    def on_completion(self):
        return self._on_completion

    @on_completion.setter
    def on_completion(self, val):
        if val not in self.ON_COMPLETION_VALUES:
            raise ValueError \
                ('Invalid value for on_completion: "{0}"'.format(val))
        self._on_completion = val

    email_each_entry = Column(Boolean, default=False)
    limit_by_date = Column(Boolean, default=False)
    start_date = Column(DateTime)
    end_date = Column(DateTime)
    message_after_end = Column(UnicodeText)
    message_before_start = Column(UnicodeText)

    # When an instance is persisted, it automatically gets a slug,
    slug = Column(
        UnicodeText(10),
        nullable=False,  # a part of the URL.
        index=True,
        default=lambda: random_word(10))

    form_id = Column(Integer, ForeignKey('form.id'), index=True)
    form = relationship(Form,
                        backref=backref('collectors',
                                        order_by=id,
                                        cascade='all'))

    def __unicode__(self):
        return self.name

    def __repr__(self):
        return 'Collector(id={0}, name="{1}")'.format(self.id, self.name)

    def to_dict(self, translator=None):
        d = {
            k: getattr(self, k)
            for k in ('id', 'name', 'thanks_message', 'thanks_url',
                      'on_completion', 'message_before_start',
                      'message_after_end', 'email_each_entry', 'limit_by_date',
                      'slug', 'status')
        }
        d['start_date'] = unicode(self.start_date)[:16] \
            if self.start_date else ''
        d['end_date'] = unicode(self.end_date)[:16] if self.end_date else ''
        d['type'] = self.typ
        d['display_type'] = self.typ.replace("_", " ").capitalize()
        d['translated_status'] = \
            translator(d['status']) if translator else d['status']
        return d

    STATUS_BEFORE = _('pending')  # before start date
    STATUS_DURING = _('published')  # entries may be created
    STATUS_AFTER = _('closed')  # after end date

    @property
    def status(self):
        '''Returns a status code.'''
        if (self.start_date and datetime.utcnow() < self.start_date
                and self.limit_by_date):
            return self.STATUS_BEFORE
        if (self.end_date and datetime.utcnow() > self.end_date
                and self.limit_by_date):
            return self.STATUS_AFTER
        return self.STATUS_DURING