示例#1
0
class Revisions(BaseMixin, db.Model):
    """
    Collection of revisions of content.
    """
    __tablename__ = 'revisions'
    #: All revisions of the content
    history = db.relationship(
        ContentRevision,
        primaryjoin='ContentRevision.parent_id == Revisions.id',
        order_by=ContentRevision.id.desc,
        backref=db.backref('parent', cascade='all'))
    #: Latest published revision
    published_id = db.Column(db.Integer,
                             db.ForeignKey('content_revision.id'),
                             nullable=True)
    published = db.relationship(ContentRevision,
                                post_update=True,
                                primaryjoin=published_id == ContentRevision.id)
    #: Latest draft revision
    draft_id = db.Column(db.Integer,
                         db.ForeignKey('content_revision.id'),
                         nullable=True)
    draft = db.relationship(ContentRevision,
                            post_update=True,
                            primaryjoin=draft_id == ContentRevision.id)
示例#2
0
class ParticipantList(ContentMixin, Node):
    __tablename__ = 'participant_list'

    source = db.Column(db.Unicode(80), nullable=False, default=u'')
    sourceid = db.Column(db.Unicode(80), nullable=False, default=u'')
    api_key = db.Column(db.Unicode(80), nullable=False, default=u'')
    participant_template = db.Column(db.Unicode(80),
                                     nullable=False,
                                     default=u'')

    def purge(self):
        """
        Discard all participants.
        """
        self.participants = []

    def has_user(self, user):
        return Participant.query.filter_by(participant_list=self).filter_by(
            user=user).first() is not None

    def url_for(self, action='view'):
        if action in ['sync', 'list']:
            return url_for('node_action',
                           website=self.folder.website.name,
                           folder=self.folder.name,
                           node=self.name,
                           action=action)
        else:
            return super(ParticipantList, self).url_for(action)
示例#3
0
class Data(NodeMixin, Node):
    __tablename__ = 'data'
    data = db.Column(JsonDict)

    def as_json(self):
        result = super(NodeMixin, self).as_json()
        result['data'] = self.data
        return result

    def import_from(self, data):
        super(NodeMixin, self).import_from(data)
        self.data = data['data']

    # Facilitate easier access to data

    def __len__(self):
        return len(self.data)

    def __iter__(self):
        return self.data.iterkeys()

    def __contains__(self, item):
        return item in self.data

    def __getitem__(self, key):
        return self.data[key]

    def __setitem__(self, key, value):
        self.data[key] = value

    def __delitem__(self, key):
        del self.data[key]
示例#4
0
class ContentRevision(BaseMixin, db.Model):
    """
    A single revision of any piece of content.
    """
    __tablename__ = 'content_revision'
    parent_id = db.Column(db.Integer,
                          db.ForeignKey('revisions.id',
                                        use_alter=True,
                                        name='fk_content_revision_parent_id'),
                          nullable=False)
    #: Previous revision
    previous_id = db.Column(db.Integer,
                            db.ForeignKey('content_revision.id'),
                            nullable=True)
    previous = db.relationship('ContentRevision',
                               remote_side='ContentRevision.id',
                               uselist=False)
    #: User who made this content revision
    user_id = db.Column(db.Integer,
                        db.ForeignKey('user.id'),
                        nullable=False,
                        default=default_user_id)
    user = db.relationship(User)
    #: Title of the current revision
    title = db.Column(db.Unicode(250), nullable=False)
    #: Abstract that is shown in summaries. Plain text.
    description = db.Column(db.UnicodeText, nullable=False, default=u'')
    #: Page content. Rich text.
    _content = db.Column('content',
                         db.UnicodeText,
                         nullable=False,
                         default=u'')
    #: Template with which this page will be rendered
    template = db.Column(db.Unicode(80), nullable=False, default=u'')

    def __init__(self, **kwargs):
        super(ContentRevision, self).__init__(**kwargs)
        if self.previous:
            # Copy over content from the previous revision
            if not self.user:
                self.user = self.previous.user
            if not self.title:
                self.title = self.previous.title
            if not self.description:
                self.description = self.previous.description
            if not self._content:
                self._content = self.previous._content
            if not self.template:
                self.template = self.previous.template

    @property
    def content(self):
        return Markup(self._content)

    @content.setter
    def content(self, value):
        self._content = value

    content = db.synonym('_content', descriptor=content)
示例#5
0
class Redirect(NodeMixin, Node):
    __tablename__ = 'redirect'
    redirect_url = db.Column(db.Unicode(250), nullable=False)

    def as_json(self):
        result = super(Redirect, self).as_json()
        result.update({'redirect_url': self.redirect_url})
        return result

    def import_from(self, data):
        super(Redirect, self).import_from(data)
        self.redirect_url = data['redirect_url']
示例#6
0
class FunnelLink(ContentMixin, Node):
    __tablename__ = 'funnel_link'
    funnel_name = db.Column(db.Unicode(80), nullable=False)

    def as_json(self):
        result = super(FunnelLink, self).as_json()
        result.update({'funnel_name': self.funnel_name})
        return result

    def import_from(self, data):
        super(FunnelLink, self).import_from(data)
        self.funnel_name = data['funnel_name']

    def _data(self):
        if not hasattr(self, '_data_cached'):
            # Get JSON and cache locally
            try:
                r = requests.get('http://funnel.hasgeek.com/%s/json' %
                                 self.funnel_name)
                data = r.json() if callable(r.json) else r.json
                sectionmap = dict([(s['title'], s['name'])
                                   for s in data['sections']])
                for proposal in data['proposals']:
                    proposal['submitted'] = parse_isoformat(
                        proposal['submitted'])
                    proposal['section_name'] = sectionmap.get(
                        proposal['section'])
                    v = proposal['votes']
                    proposal['votes'] = '+%d' % v if v > 0 else '%d' % v
                self._data_cached = data
            except ConnectionError:
                self._data_cached = {
                    'proposals': [],
                    'sections': [],
                    'space': {},
                }
        return self._data_cached

    def proposals_mapping(self):
        if not hasattr(self, '_dict_cached'):
            self._dict_cached = dict([(p['id'], p) for p in self.proposals()])
        return self._dict_cached

    def sections(self):
        # Get data from Funnel and cache locally
        return self._data()['sections']

    def proposals(self):
        return self._data()['proposals']

    def confirmed(self):
        return [p for p in self._data()['proposals'] if p['confirmed']]
示例#7
0
class MapItem(BaseScopedNameMixin, db.Model):
    __tablename__ = 'map_item'
    map_id = db.Column(None, db.ForeignKey('map.id'), nullable=False)
    parent = db.synonym('map')
    url = db.Column(db.Unicode(250), nullable=True)
    latitude = db.Column(db.Numeric(7, 4), nullable=False)
    longitude = db.Column(db.Numeric(7, 4), nullable=False)
    zoomlevel = db.Column(db.Integer, nullable=True)
    marker = db.Column(db.Unicode(80), nullable=True)
    seq = db.Column(db.Integer, nullable=False)

    __table_args__ = (db.UniqueConstraint('map_id', 'name'),)

    @classmethod
    def get_or_new(cls, map, name=None):
        item = cls.query.filter_by(map=map, name=name).first()
        if item is None:
            item = cls(map=map, name=name)
            db.session.add(item)
        return item
示例#8
0
class ListItem(BaseMixin, db.Model):
    __tablename__ = 'list_item'
    list_id = db.Column(None, db.ForeignKey('list.id'), nullable=False)
    name = db.Column(db.Unicode(80), nullable=True)
    title = db.Column(db.Unicode(250), nullable=True)
    url = db.Column(db.Unicode(250), nullable=True)
    node_id = db.Column(None, db.ForeignKey('node.id'), nullable=True)
    node = db.relationship(Node,
                           backref=db.backref('lists',
                                              cascade='all, delete-orphan'))
    seq = db.Column(db.Integer, nullable=False)

    __table_args__ = (
        db.UniqueConstraint(
            'list_id',
            'name'),  # name, if present, must be unique in this list
        db.UniqueConstraint('list_id',
                            'node_id')  # A node can only be in a list once
    )

    @classmethod
    def get_or_new(cls, list, name=None, node=None):
        if name is None and node is None:
            return
        query = cls.query.filter_by(list=list)
        if name:
            query = query.filter_by(name=name)
        if node:
            query = query.filter_by(node=node)
        item = query.first()
        if item:
            return item
        else:
            item = cls(list=list, name=name, node=node)
            db.session.add(item)
        return item
示例#9
0
class ContentMixin(NodeMixin):
    is_published = db.Column('published',
                             db.Boolean,
                             default=False,
                             nullable=False,
                             index=True)

    @declared_attr
    def revisions_id(cls):
        return db.Column(db.Integer,
                         db.ForeignKey('revisions.id'),
                         nullable=False)

    @declared_attr
    def revisions(cls):
        return db.relationship(Revisions, uselist=False)

    def __init__(self, **kwargs):
        self.revisions = Revisions()
        super(ContentMixin, self).__init__(**kwargs)

    def last_revision(self):
        revision = self.revisions.draft or self.revisions.published
        if revision is None:
            revision = ContentRevision.query.filter_by(
                parent=self.revisions).order_by(
                    db.desc('id')).limit(1).first()
        return revision

    def publish(self, revision=None):
        """
        Publish the given revision, or the latest draft.
        """
        if revision is not None and revision.parent is not self.revisions:
            raise ValueError("This revision isn't from this content document.")
        if revision is None:
            revision = self.revisions.draft
        if revision is not None:
            if not self.revisions.published:
                # TODO: Expose this for user editing. Don't assume current
                self.published_at = datetime.utcnow()
            self.revisions.published = revision
            self.is_published = True
            # Update node title from the revision
            self.title = revision.title
            # If this was the latest draft, there's no longer a latest draft
            if revision is self.revisions.draft:
                self.revisions.draft = None

    def unpublish(self):
        """
        Withdraw the published version and go back to being a draft.
        """
        self.revisions.published = None
        revision = ContentRevision.query.filter_by(
            parent=self.revisions).order_by(db.desc('id')).limit(1).first()
        self.revisions.draft = revision
        self.title = revision.title
        self.is_published = False

    def revise(self, revision=None):
        """Create and return a new revision."""
        if revision and revision.parent is not self.revisions:
            raise ValueError("This revision isn't from this content document.")
        # Make and return a new revision based on the given revision or the latest draft
        if revision is None:
            # If parent is still None after this, there was no parent
            revision = self.last_revision()
        new_revision = ContentRevision(parent=self.revisions,
                                       previous=revision)
        self.revisions.draft = new_revision
        db.session.add(new_revision)
        return new_revision

    @property
    def template(self):
        if self.revisions.published:
            return self.revisions.published.template
        else:
            return self.revisions.draft.template

    @property
    def description(self):
        # TODO: Only show drafts to a logged in, authorized user
        if self.is_published:
            return self.revisions.published.description
        else:
            return self.revisions.draft.description

    @property
    def content(self):
        # TODO: Only show drafts to a logged in, authorized user
        if self.is_published:
            return self.revisions.published.content
        else:
            return self.revisions.draft.content

    def __repr__(self):
        return u'<%s %s/%s "%s" at %s>' % (
            self.__tablename__.title(), self.folder.name, self.name
            or '(index)', self.title, self.folder.website.name)

    def as_json(self):
        result = super(ContentMixin, self).as_json()
        revision = self.revisions.draft or self.revisions.published
        if not revision:
            return result

        result.update({
            'title': revision.title,
            'description': revision.description,
            'content': revision.content,
            'template': revision.template,
            'revision_created_at': revision.created_at.isoformat() + 'Z',
            'revision_updated_at': revision.updated_at.isoformat() + 'Z',
            'is_published': self.is_published,
        })
        return result

    def import_from(self, data):
        super(ContentMixin, self).import_from(data)
        last = self.last_revision()
        if last and last.updated_at >= parse_isoformat(
                data['revision_updated_at']):
            # Don't import if data is older than or the same as the last revision
            return
        revision = self.revise()
        revision.title = data['title']
        revision.description = data['description']
        revision.content = data['content']
        revision.template = data['template']

    def url_for(self, action='view'):
        if action == 'publish':
            return url_for('node_publish',
                           website=self.folder.website.name,
                           folder=self.folder.name,
                           node=self.name)
        elif action == 'unpublish':
            return url_for('node_unpublish',
                           website=self.folder.website.name,
                           folder=self.folder.name,
                           node=self.name)
        else:
            return super(ContentMixin, self).url_for(action)
示例#10
0
 def revisions_id(cls):
     return db.Column(db.Integer,
                      db.ForeignKey('revisions.id'),
                      nullable=False)
示例#11
0
class Participant(BaseMixin, db.Model):
    __tablename__ = 'participant'

    #: List that this participant is in
    participant_list_id = db.Column(None,
                                    db.ForeignKey('participant_list.id'),
                                    nullable=False)
    #: Datetime when this participant's record was created upstream
    datetime = db.Column(db.DateTime, nullable=True)
    #: Ticket no, the reference key
    ticket = db.Column(db.Unicode(80), nullable=True, unique=True)
    #: Participant's name
    fullname = db.Column(db.Unicode(80), nullable=True)
    #: Unvalidated email address
    email = db.Column(db.Unicode(80), nullable=True)
    #: Unvalidated phone number
    phone = db.Column(db.Unicode(80), nullable=True)
    #: Unvalidated Twitter id
    twitter = db.Column(db.Unicode(80), nullable=True)
    #: Ticket type, if the registration system had multiple tickets
    ticket_type = db.Column(db.Unicode(80), nullable=True)
    #: Job title
    jobtitle = db.Column(db.Unicode(80), nullable=True)
    #: Company
    company = db.Column(db.Unicode(80), nullable=True)
    #: Participant's city
    city = db.Column(db.Unicode(80), nullable=True)
    #: T-shirt size
    tshirt_size = db.Column(db.Unicode(4), nullable=True)
    #: Link to the user record
    user_id = db.Column(None, db.ForeignKey('user.id'), nullable=True)
    user = db.relationship(User)
    #: Access key for connecting to the user record (nulled when linked)
    access_key = db.Column(db.Unicode(44),
                           nullable=True,
                           default=newsecret,
                           unique=True)
    #: Is listed in the public directory
    is_listed = db.Column(db.Boolean, default=False, nullable=False)
    #: Data fields the participant has chosen to reveal in public
    fields_directory = db.Column(db.Unicode(250),
                                 nullable=False,
                                 default=u'fullname company')
    #: Data fields the participant has chosen to reveal via ContactPoint
    fields_contactpoint = db.Column(
        db.Unicode(250),
        nullable=False,
        default=u'fullname email phone twitter jobtitle company city')

    participant_list = db.relationship(ParticipantList,
                                       backref=db.backref(
                                           'participants',
                                           order_by=fullname,
                                           cascade='all, delete-orphan'))

    parent = db.synonym('participant_list')