コード例 #1
0
class ImportedPost(Post):
    """
    A Post that originated outside of the Assembl system (was imported from elsewhere).
    """
    __tablename__ = "imported_post"
    __table_args__ = (UniqueConstraint('source_post_id', 'source_id'), )
    id = Column(Integer,
                ForeignKey('post.id', ondelete='CASCADE', onupdate='CASCADE'),
                primary_key=True)

    import_date = Column(DateTime, nullable=False, default=datetime.utcnow)

    source_post_id = Column(
        CoerceUnicode(),
        nullable=False,
        doc=
        "The source-specific unique id of the imported post.  A listener keeps the message_id in the post class in sync"
    )

    source_id = Column('source_id',
                       Integer,
                       ForeignKey('post_source.id', ondelete='CASCADE'),
                       nullable=False,
                       info={'rdf': QuadMapPatternS(None, ASSEMBL.has_origin)})

    source = relationship("PostSource", backref=backref('contents'))

    body_mime_type = Column(
        CoerceUnicode(),
        nullable=False,
        doc=
        "The mime type of the body of the imported content.  See Content::get_body_mime_type() for allowed values."
    )

    imported_blob = deferred(Column(Binary), group='raw_details')

    __mapper_args__ = {
        'polymorphic_identity': 'imported_post',
    }

    def get_body_mime_type(self):
        return self.body_mime_type

    def unique_query(self):
        query, _ = super(ImportedPost, self).unique_query()
        source_id = self.source_id or self.source.id
        return query.filter_by(source_id=source_id,
                               source_post_id=self.source_post_id), True
コード例 #2
0
def upgrade(pyramid_env):
    with context.begin_transaction():
        op.create_table(
            'facebook_account',
            sa.Column('id',
                      sa.Integer,
                      sa.ForeignKey('idprovider_agent_account.id',
                                    onupdate='CASCADE',
                                    ondelete='CASCADE'),
                      primary_key=True), sa.Column('app_id', sa.String(512)))

        op.create_table(
            'facebook_source',
            sa.Column('id',
                      sa.Integer,
                      sa.ForeignKey('post_source.id',
                                    onupdate='CASCADE',
                                    ondelete='CASCADE'),
                      primary_key=True),
            sa.Column('fb_source_id', sa.String(512), nullable=False),
            sa.Column('url_path', sa.String(1024)),
            sa.Column(
                'creator_id', sa.Integer,
                sa.ForeignKey('facebook_account.id',
                              onupdate='CASCADE',
                              ondelete='CASCADE')))

        op.create_table(
            'facebook_post',
            sa.Column('id',
                      sa.Integer,
                      sa.ForeignKey('imported_post.id',
                                    onupdate='CASCADE',
                                    ondelete='CASCADE'),
                      primary_key=True),
            sa.Column('attachment', sa.String(1024)),
            sa.Column('link_name', CoerceUnicode(1024)),
            sa.Column('post_type', sa.String(20)))

        op.create_table(
            'facebook_access_token',
            sa.Column('id', sa.Integer, primary_key=True),
            sa.Column(
                'fb_account_id', sa.Integer,
                sa.ForeignKey('facebook_account.id',
                              onupdate='CASCADE',
                              ondelete='CASCADE')),
            sa.Column('token', sa.String(512), unique=True),
            sa.Column('expiration', sa.DateTime),
            sa.Column('token_type', sa.String(50)),
            sa.Column('object_name', sa.String(512)),
            sa.Column('object_fb_id', sa.String(512)))

    # Do stuff with the app's models here.
    from assembl import models as m
    db = m.get_session_maker()()
    with transaction.manager:
        pass
コード例 #3
0
ファイル: mail.py プロジェクト: rmoorman/assembl
class AbstractFilesystemMailbox(AbstractMailbox):
    """
    A Mailbox refers to an Email inbox that is stored the server's filesystem.
    """
    __tablename__ = "source_filesystemmailbox"
    id = Column(Integer,
                ForeignKey('mailbox.id',
                           ondelete='CASCADE',
                           onupdate='CASCADE'),
                primary_key=True)

    filesystem_path = Column(CoerceUnicode(), nullable=False)

    __mapper_args__ = {
        'polymorphic_identity': 'source_filesystemmailbox',
    }
コード例 #4
0
class Attachment(DiscussionBoundBase):
    """
    Represents a Document or file, local to the database or (more typically)
    a remote document
    """
    __tablename__ = "attachment"
    id = Column(Integer, primary_key=True)

    creation_date = Column(
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        info={'rdf': QuadMapPatternS(None, DCTERMS.created)})
    discussion_id = Column(
        Integer,
        ForeignKey(
            'discussion.id',
            ondelete='CASCADE',
            onupdate='CASCADE',
        ),
        nullable=False,
    )

    discussion = relationship(
        "Discussion",
        backref=backref('attachments', cascade="all, delete-orphan"),
    )

    document_id = Column(
        Integer,
        ForeignKey(
            'document.id',
            ondelete='CASCADE',
            onupdate='CASCADE',
        ),
        nullable=False,
    )

    document = relationship(
        Document,
        backref=backref('attachments'),
    )

    creator_id = Column(Integer,
                        ForeignKey('agent_profile.id'),
                        nullable=False)
    creator = relationship(AgentProfile)
    title = Column(CoerceUnicode(1024),
                   server_default="",
                   info={'rdf': QuadMapPatternS(None, DCTERMS.title)})
    description = Column(
        UnicodeText, info={'rdf': QuadMapPatternS(None, DCTERMS.description)})

    attachmentPurpose = Column(CoerceUnicode(256), nullable=False, index=True)

    __mapper_args__ = {
        'polymorphic_identity': 'attachment',
        'with_polymorphic': '*'
    }

    def get_discussion_id(self):
        return self.discussion_id or self.discussion.id

    @classmethod
    def get_discussion_conditions(cls, discussion_id, alias_maker=None):
        return (cls.id == discussion_id, )
コード例 #5
0
class Document(DiscussionBoundBase):
    """
    Represents a Document or file, local to the database or (more typically)
    a remote document
    """
    __tablename__ = "document"
    id = Column(Integer, primary_key=True)
    """
    The cannonical identifier of this document.  If a URL, it's to be 
    interpreted as a purl
    """
    uri_id = Column(
        CoerceUnicode(514), unique=False, index=True
    )  ## MAP:  Change to true once https://app.asana.com/0/51461630427071/52921943509398 is done
    creation_date = Column(
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        info={'rdf': QuadMapPatternS(None, DCTERMS.created)})
    discussion_id = Column(
        Integer,
        ForeignKey(
            'discussion.id',
            ondelete='CASCADE',
            onupdate='CASCADE',
        ),
        nullable=False,
    )

    discussion = relationship(
        "Discussion",
        backref=backref('documents', cascade="all, delete-orphan"),
    )

    oembed_type = Column(CoerceUnicode(1024), server_default="")
    mime_type = Column(CoerceUnicode(1024), server_default="")
    #From metadata, not the user
    title = Column(CoerceUnicode(),
                   server_default="",
                   info={'rdf': QuadMapPatternS(None, DCTERMS.title)})
    #From metadata, not the user
    description = Column(
        UnicodeText, info={'rdf': QuadMapPatternS(None, DCTERMS.description)})
    #From metadata, not the user
    author_name = Column(UnicodeText)
    #From metadata, not the user
    author_url = Column(UnicodeText)
    #From metadata, not the user
    thumbnail_url = Column(UnicodeText)
    #From metadata, not the user
    site_name = Column(UnicodeText)

    __mapper_args__ = {
        'polymorphic_identity': 'document',
    }

    def get_discussion_id(self):
        return self.discussion_id or self.discussion.id

    @classmethod
    def get_discussion_conditions(cls, discussion_id, alias_maker=None):
        return (cls.id == discussion_id, )
コード例 #6
0
ファイル: generic.py プロジェクト: rmoorman/assembl
class Content(DiscussionBoundBase):
    """
    Content is a polymorphic class to describe what is imported from a Source.
    The body and subject properly belong to the Post but were moved here to
    optimize the most common case.
    """
    __tablename__ = "content"
    # __table_cls__ = TableWithTextIndex

    rdf_class = SIOC.Post

    id = Column(Integer,
                primary_key=True,
                info={'rdf': QuadMapPatternS(None, ASSEMBL.db_id)})
    type = Column(String(60), nullable=False)
    creation_date = Column(
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        info={'rdf': QuadMapPatternS(None, DCTERMS.created)})

    discussion_id = Column(
        Integer,
        ForeignKey(
            'discussion.id',
            ondelete='CASCADE',
            onupdate='CASCADE',
        ),
        nullable=False,
    )

    discussion = relationship(
        "Discussion",
        backref=backref('posts',
                        order_by=creation_date,
                        cascade="all, delete-orphan"),
        info={'rdf': QuadMapPatternS(None, ASSEMBL.in_conversation)})

    subject = Column(CoerceUnicode(),
                     server_default="",
                     info={'rdf': QuadMapPatternS(None, DCTERMS.title)})
    # TODO: check HTML or text? SIOC.content should be text.
    # Do not give it for now, privacy reasons
    body = Column(UnicodeText, server_default="")
    #    info={'rdf': QuadMapPatternS(None, SIOC.content)})

    hidden = Column(Boolean, server_default='0')

    # Another bloody virtuoso bug. Insert with large string fails.
    # body_text_index = TextIndex(body, clusters=[discussion_id])

    __mapper_args__ = {
        'polymorphic_identity': 'content',
        'polymorphic_on': 'type',
        'with_polymorphic': '*'
    }

    def get_body(self):
        return self.body.strip()

    def get_title(self):
        return self.subject

    def get_body_mime_type(self):
        """ Return the format of the body, so the frontend will know how to
        display it.  Currently, only:
        text/plain (Understood as preformatted text)
        text/html (Undestood as some subste of html)
        """
        return "text/plain"

    def get_body_as_html(self):
        if self.get_body_mime_type() == 'text/html':
            return self.body
        else:
            return '<span style="white-space: pre-wrap">%s</div>' % (
                self.get_body_as_text(), )

    def get_body_as_text(self):
        mimetype = self.get_body_mime_type()
        body = self.body or ""
        if mimetype == 'text/plain':
            return body.strip()
        elif mimetype == 'text/html':
            return BeautifulSoup(body).get_text().strip()
        else:
            log.error("What is this mimetype?" + mimetype)
            return body

    def send_to_changes(self,
                        connection=None,
                        operation=UPDATE_OP,
                        discussion_id=None,
                        view_def="changes"):
        super(Content, self).send_to_changes(connection, operation,
                                             discussion_id, view_def)
        watcher = get_model_watcher()
        if operation == INSERT_OP:
            watcher.processPostCreated(self.id)

    def get_discussion_id(self):
        return self.discussion_id or self.discussion.id

    @property
    def exported_to_sources(self):
        return [
            ContentSource.uri_generic(s.source_id)
            for s in self.post_sink_associations
        ]

    @classmethod
    def get_discussion_conditions(cls, discussion_id, alias_maker=None):
        return (cls.discussion_id == discussion_id, )

    @classmethod
    def special_quad_patterns(cls, alias_maker, discussion_id):
        discussion_alias = alias_maker.get_reln_alias(cls.discussion)
        return [
            QuadMapPatternS(
                None,
                FOAF.homepage,
                PatternIriClass(
                    QUADNAMES.post_external_link_iri,
                    # TODO: Use discussion.get_base_url.
                    # This should be computed outside the DB.
                    get_global_base_url() + '/%s/posts/local:Content/%d',
                    None,
                    ('slug', Unicode, False),
                    ('id', Integer, False)).apply(discussion_alias.slug,
                                                  cls.id),
                name=QUADNAMES.post_external_link_map)
        ]

    widget_idea_links = relationship('IdeaContentWidgetLink')

    def widget_ideas(self):
        from .idea import Idea
        return [
            Idea.uri_generic(wil.idea_id) for wil in self.widget_idea_links
        ]

    crud_permissions = CrudPermissions(P_ADD_POST, P_READ, P_EDIT_POST,
                                       P_ADMIN_DISC, P_EDIT_POST, P_ADMIN_DISC)
コード例 #7
0
class Post(Content):
    """
    A Post represents input into the broader discussion taking place on
    Assembl. It may be a response to another post, it may have responses, and
    its content may be of any type.
    """
    __tablename__ = "post"

    id = Column(Integer,
                ForeignKey('content.id',
                           ondelete='CASCADE',
                           onupdate='CASCADE'),
                primary_key=True)

    message_id = Column(CoerceUnicode(),
                        nullable=False,
                        index=True,
                        doc="The email-compatible message-id for the post.",
                        info={'rdf': QuadMapPatternS(None, SIOC.id)})

    ancestry = Column(String, default="")

    parent_id = Column(
        Integer, ForeignKey('post.id', ondelete='CASCADE',
                            onupdate='SET NULL'))
    children = relationship(
        "Post",
        foreign_keys=[parent_id],
        backref=backref('parent', remote_side=[id]),
    )

    @classmethod
    def special_quad_patterns(cls, alias_maker, discussion_id):
        # Don't we need a recursive alias for this? It seems not.
        return [
            QuadMapPatternS(Post.iri_class().apply(cls.id),
                            SIOC.reply_of,
                            cls.iri_class().apply(cls.parent_id),
                            name=QUADNAMES.post_parent,
                            conditions=(cls.parent_id != None, )),
        ]

    creator_id = Column(Integer,
                        ForeignKey('agent_profile.id'),
                        nullable=False,
                        info={
                            'rdf':
                            QuadMapPatternS(
                                None, SIOC.has_creator,
                                AgentProfile.agent_as_account_iri.apply(None))
                        })
    creator = relationship(AgentProfile, backref="posts_created")

    __mapper_args__ = {'polymorphic_identity': 'post', 'with_polymorphic': '*'}

    def get_descendants(self):
        assert self.id
        descendants = self.db.query(Post).filter(
            Post.parent_id == self.id).order_by(Content.creation_date)

        return descendants

    def is_read(self):
        # TODO: Make it user-specific.
        return self.views is not None

    def get_url(self):
        from assembl.lib.frontend_urls import FrontendUrls
        frontendUrls = FrontendUrls(self.discussion)
        return frontendUrls.get_post_url(self)

    def get_body_preview(self):
        body = self.get_body().strip()
        target_len = 120
        shortened = False
        if self.get_body_mime_type() == 'text/html':
            html_len = 2 * target_len
            while True:
                text = BeautifulSoup(body[:html_len]).get_text().strip()
                if html_len >= len(body) or len(text) > target_len:
                    shortened = html_len < len(body)
                    body = text
                    break
                html_len += target_len
        if len(body) > target_len:
            body = body[:target_len].rsplit(' ', 1)[0].rstrip() + ' '
        elif shortened:
            body += ' '
        return body

    def _set_ancestry(self, new_ancestry):
        self.ancestry = new_ancestry

        descendant_ancestry = "%s%d," % (self.ancestry, self.id)
        for descendant in self.get_descendants():
            descendant._set_ancestry(descendant_ancestry)

    def set_parent(self, parent):
        self.parent = parent

        self.db.add(self)
        self.db.flush()

        self._set_ancestry("%s%d," % (parent.ancestry or '', parent.id))

    def last_updated(self):
        ancestry_query_string = "%s%d,%%" % (self.ancestry or '', self.id)

        query = self.db.query(func.max(
            Content.creation_date)).select_from(Post).join(Content).filter(
                or_(Post.ancestry.like(ancestry_query_string),
                    Post.id == self.id))

        return query.scalar()

    def ancestor_ids(self):
        ancestor_ids = [
            int(ancestor_id) \
            for ancestor_id \
            in self.ancestry.split(',') \
            if ancestor_id
        ]
        return ancestor_ids

    def ancestors(self):

        ancestors = [
            Post.get(ancestor_id) \
            for ancestor_id \
            in self.ancestor_ids
        ]

        return ancestors

    def prefetch_descendants(self):
        pass  #TODO

    def visit_posts_depth_first(self, post_visitor):
        self.prefetch_descendants()
        self._visit_posts_depth_first(post_visitor, set())

    def _visit_posts_depth_first(self, post_visitor, visited):
        if self in visited:
            # not necessary in a tree, but let's start to think graph.
            return False
        result = post_visitor.visit_post(self)
        visited.add(self)
        if result is not PostVisitor.CUT_VISIT:
            for child in self.children:
                child._visit_posts_depth_first(post_visitor, visited)

    def visit_posts_breadth_first(self, post_visitor):
        self.prefetch_descendants()
        result = post_visitor.visit_post(self)
        visited = {self}
        if result is not PostVisitor.CUT_VISIT:
            self._visit_posts_breadth_first(post_visitor, visited)

    def _visit_posts_breadth_first(self, post_visitor, visited):
        children = []
        for child in self.children:
            if child in visited:
                continue
            result = post_visitor.visit_post(child)
            visited.add(child)
            if result != PostVisitor.CUT_VISIT:
                children.append(child)
        for child in children:
            child._visit_posts_breadth_first(post_visitor, visited)

    def has_next_sibling(self):
        if self.parent_id:
            return self != self.parent.children[-1]
        return False

    @classmethod
    def restrict_to_owners(cls, query, user_id):
        "filter query according to object owners"
        return query.filter(cls.creator_id == user_id)
コード例 #8
0
class FacebookPost(ImportedPost):
    """
    A facebook post, from any resource on the Open Graph API
    """
    __tablename__ = 'facebook_post'

    id = Column(Integer,
                ForeignKey('imported_post.id',
                           onupdate='CASCADE',
                           ondelete='CASCADE'),
                primary_key=True)

    attachment = Column(String(1024))
    link_name = Column(CoerceUnicode(1024))
    post_type = Column(String(20))

    __mapper_args__ = {'polymorphic_identity': 'facebook_post'}

    @classmethod
    def create(cls, source, post, user):
        import_date = datetime.utcnow()
        source_post_id = post.get('id')
        source = source
        creation_date = parse_datetime(post.get('created_time'))
        discussion = source.discussion
        creator_agent = user.profile
        blob = json.dumps(post)

        post_type = post.get('type', None)
        subject, body, attachment, link_name = (None, None, None, None)
        if not post_type:
            has_attach = post.get('link', None)
            if has_attach:
                attachment = has_attach
                body = post.get('message', "") + "\n" + post.get('link', "") \
                    if 'message' in post else post.get('link', "")
            else:
                post_type = 'comment'
                body = post.get('message')

        elif post_type is 'video' or 'photo':
            subject = post.get('story', None)
            body = post.get('message', "") + "\n" + post.get('link', "") \
                if 'message' in post else post.get('link', "")
            attachment = post.get('link', None)
            link_name = post.get('caption', None)

        elif post_type is 'link':
            subject = post.get('story', None)
            body = post.get('message', "")
            attachment = post.get('link')
            link_name = post.get('caption', None)
            match_str = re.split(r"^\w+://", attachment)[1]
            if match_str not in body:
                body += "\n" + attachment

        elif post_type is 'status':
            if not post.get('message', None):
                # A type of post that does not have any links nor body content
                # It is useless, therefore it should never generate a post
                return None
            body = post.get('message')

        return cls(attachment=attachment,
                   link_name=link_name,
                   body_mime_type='text/plain',
                   import_date=import_date,
                   source_post_id=source_post_id,
                   message_id=source_post_id,
                   source=source,
                   creation_date=creation_date,
                   discussion=discussion,
                   creator=creator_agent,
                   post_type=post_type,
                   imported_blob=blob,
                   subject=subject,
                   body=body)
コード例 #9
0
def upgrade(pyramid_env):
    with context.begin_transaction():
        op.create_table(
            'document',
            sa.Column('id', sa.Integer, primary_key=True),
            sa.Column('uri_id', CoerceUnicode(1024), server_default="", unique=False, index=True),
            sa.Column('creation_date',
                      sa.DateTime,
                      nullable=False,
                      default = datetime.utcnow),
            sa.Column('discussion_id',
                       sa.Integer,
                       sa.ForeignKey(
                            'discussion.id',
                             ondelete="CASCADE",
                             onupdate="CASCADE"),
                nullable=False,
                index=False,),
            sa.Column('oembed_type', CoerceUnicode(1024), server_default=""),
            sa.Column('mime_type', CoerceUnicode(1024), server_default=""),
            sa.Column('title', CoerceUnicode(1024), server_default=""),
            sa.Column('description', sa.UnicodeText),
            sa.Column('author_name', sa.UnicodeText),
            sa.Column('author_url', sa.UnicodeText),
            sa.Column('thumbnail_url', sa.UnicodeText),
            sa.Column('site_name', sa.UnicodeText),
                
            )
        op.create_table(
            'attachment',
            sa.Column('id', sa.Integer, primary_key=True),
            sa.Column('creation_date',
                      sa.DateTime,
                      nullable=False,
                      default = datetime.utcnow),
            sa.Column('discussion_id',
                       sa.Integer,
                       sa.ForeignKey(
                            'discussion.id',
                             ondelete="CASCADE",
                             onupdate="CASCADE"),
                nullable=False,
                index=False,),
            sa.Column('document_id',
                       sa.Integer,
                       sa.ForeignKey(
                            'document.id',
                             ondelete="CASCADE",
                             onupdate="CASCADE"),
                nullable=False,),
            sa.Column('creator_id',
                       sa.Integer,
                       sa.ForeignKey(
                            'agent_profile.id',
                             ondelete="CASCADE",
                             onupdate="CASCADE"),
                nullable=False,
                index=False,),
            sa.Column('title', CoerceUnicode(1024), server_default=""),
            sa.Column('description', sa.UnicodeText),
            sa.Column('attachmentPurpose', 
                      CoerceUnicode(256), 
                      server_default="",
                      index=True,),
            )
        op.create_table(
            'post_attachment',
            sa.Column('id', 
                      sa.Integer,
                      sa.ForeignKey(
                            'attachment.id',
                             ondelete="CASCADE",
                             onupdate="CASCADE"),
                      primary_key=True),
            sa.Column('post_id',
                       sa.Integer,
                       sa.ForeignKey(
                            'post.id',
                             ondelete="CASCADE",
                             onupdate="CASCADE"),
                nullable=False,
                index=True,),
            )
        op.create_table(
            'idea_attachment',
            sa.Column('id', 
                      sa.Integer,
                      sa.ForeignKey(
                            'attachment.id',
                             ondelete="CASCADE",
                             onupdate="CASCADE"),
                      primary_key=True),
            sa.Column('idea_id',
                       sa.Integer,
                       sa.ForeignKey(
                            'idea.id',
                             ondelete="CASCADE",
                             onupdate="CASCADE"),
                nullable=False,
                index=True,),
            )
コード例 #10
0
ファイル: mail.py プロジェクト: rmoorman/assembl
class Email(ImportedPost):
    """
    An Email refers to an email message that was imported from an AbstractMailbox.
    """
    __tablename__ = "email"

    id = Column(Integer,
                ForeignKey('imported_post.id',
                           ondelete='CASCADE',
                           onupdate='CASCADE'),
                primary_key=True)

    # in virtuoso, varchar is 1024 bytes and sizeof(wchar)==4, so varchar is 256 chars
    recipients = Column(UnicodeText, nullable=False)
    sender = Column(CoerceUnicode(), nullable=False)

    in_reply_to = Column(CoerceUnicode())

    __mapper_args__ = {
        'polymorphic_identity': 'email',
    }

    def REWRITEMEreply(self, sender, response_body):
        """
        Send a response to this email.

        `sender` is a user instance.
        `response` is a string.
        """

        sent_from = ' '.join([
            "%(sender_name)s on Assembl" % {
                "sender_name": sender.display_name()
            },
            "<%(sender_email)s>" % {
                "sender_email": sender.get_preferred_email(),
            }
        ])

        if type(response_body) == 'str':
            response_body = response_body.decode('utf-8')

        recipients = self.recipients

        message = MIMEMultipart('alternative')
        message['Subject'] = Header(self.subject, 'utf-8')
        message['From'] = sent_from

        message['To'] = self.recipients
        message.add_header('In-Reply-To', self.message_id)

        plain_text_body = response_body
        html_body = response_body

        # TODO: The plain text and html parts of the email should be different,
        # but we'll see what we can get from the front-end.

        plain_text_part = MIMEText(plain_text_body.encode('utf-8'), 'plain',
                                   'utf-8')

        html_part = MIMEText(html_body.encode('utf-8'), 'html', 'utf-8')

        message.attach(plain_text_part)
        message.attach(html_part)

        smtp_connection = smtplib.SMTP(
            get_current_registry().settings['mail.host'])

        smtp_connection.sendmail(sent_from, recipients, message.as_string())

        smtp_connection.quit()

    def __repr__(self):
        return "%s from %s to %s>" % (super(
            Email, self).__repr__(), self.sender.encode(
                'ascii', 'ignore'), self.recipients.encode('ascii', 'ignore'))

    def get_title(self):
        return self.source.mangle_mail_subject(self.subject)
コード例 #11
0
ファイル: sqla_types.py プロジェクト: Lornz-/assembl
 def normalize_to_type(self, value, dialect):
     return CoerceUnicode.process_bind_param(self, value, dialect)
コード例 #12
0
class TimelineEvent(DiscussionBoundBase):
    __tablename__ = 'timeline_event'

    id = Column(Integer, primary_key=True,
                info={'rdf': QuadMapPatternS(None, ASSEMBL.db_id)})

    discussion_id = Column(Integer, ForeignKey(
        'discussion.id',
        ondelete='CASCADE',
        onupdate='CASCADE'
    ), nullable=False)

    type = Column(String(60), nullable=False)

    __mapper_args__ = {
        'polymorphic_identity': 'timeline_event',
        'polymorphic_on': type,
        'with_polymorphic': '*'
    }

    title = Column(CoerceUnicode(), nullable=False,
        info={'rdf': QuadMapPatternS(None, DCTERMS.title)})

    description = Column(UnicodeText,
        info={'rdf': QuadMapPatternS(None, DCTERMS.description)})

    start = Column(DateTime,
        # Formally, TIME.hasBeginning o TIME.inXSDDateTime
        info={'rdf': QuadMapPatternS(None, TIME.hasBeginning)})

    end = Column(DateTime,
        info={'rdf': QuadMapPatternS(None, TIME.hasEnd)})

    # Since dates are optional, the previous event pointer allows
    # dateless events to form a linked list.
    # Ideally we could use a uniqueness constraint but
    # that disallows multiple NULLs.
    # Also, the linked list defines lanes.
    previous_event_id = Column(Integer, ForeignKey(
        'timeline_event.id', ondelete="SET NULL"), nullable=True)

    previous_event = relationship(
        "TimelineEvent", remote_side=[id], post_update=True, uselist=False,
        backref=backref("next_event", uselist=False, remote_side=[previous_event_id]))

    def __init__(self, **kwargs):
        previous_event_id = None
        previous_event = None
        if 'previous_event' in kwargs:
            previous_event = kwargs['previous_event']
            del kwargs['previous_event']
        if 'previous_event_id' in kwargs:
            previous_event_id = kwargs['previous_event_id']
            del kwargs['previous_event_id']
        super(TimelineEvent, self).__init__(**kwargs)
        if previous_event is not None:
            self.set_previous_event(previous_event)
        elif previous_event_id is not None:
            self.set_previous_event_id(previous_event_id)

    discussion = relationship(
        Discussion,
        backref=backref(
            'timeline_events', order_by=start,
            cascade="all, delete-orphan"),
        info={'rdf': QuadMapPatternS(None, ASSEMBL.in_conversation)}
    )

    def set_previous_event(self, previous_event):
        # This allows setting the previous event as an insert.
        # this method may not be reliable with unflushed objects.
        self.set_previous_event_id(previous_event.id if previous_event is not None else None)
        self.previous_event = previous_event
        previous_event.next_event = self

    def set_previous_event_id(self, previous_event_id):
        if previous_event_id != self.previous_event_id:
            # TODO: Detect and avoid cycles
            if previous_event_id is not None:
                existing = self.__class__.get_by(previous_event_id=previous_event_id)
                if existing:
                    existing.previous_event = self
            if inspect(self).persistent:
                self.db.expire(self, ['previous_event'])
            elif 'previous_event' in self.__dict__:
                del self.__dict__['previous_event']
            self.previous_event_id = previous_event_id

    def get_discussion_id(self):
        return self.discussion_id

    @classmethod
    def get_discussion_conditions(cls, discussion_id, alias_maker=None):
        return (cls.discussion_id == discussion_id,)

    crud_permissions = CrudPermissions(P_ADMIN_DISC)