Switch to unified view

a/bitergiametrics/model/blog.py b/bitergiametrics/model/blog.py
...
...
14
from allura.lib import utils, patience
14
from allura.lib import utils, patience
15
15
16
config = utils.ConfigProxy(
16
config = utils.ConfigProxy(
17
    common_suffix='forgemail.domain')
17
    common_suffix='forgemail.domain')
18
18
19
class BlogPostSnapshot(M.Snapshot):
19
class MetricSnapshot(M.Snapshot):
20
    class __mongometa__:
20
    class __mongometa__:
21
        name='blog_post_snapshot'
21
        name='metric_snapshot'
22
    type_s='Blog Post Snapshot'
22
    type_s='Metric Snapshot'
23
23
24
    def original(self):
24
    def original(self):
25
        return BlogPost.query.get(_id=self.artifact_id)
25
        return Metric.query.get(_id=self.artifact_id)
26
26
27
    def shorthand_id(self):
27
    def shorthand_id(self):
28
        return '%s#%s' % (self.original().shorthand_id(), self.version)
28
        return '%s#%s' % (self.original().shorthand_id(), self.version)
29
29
30
    def url(self):
30
    def url(self):
31
        return self.original().url() + '?version=%d' % self.version
31
        return self.original().url() + '?version=%d' % self.version
32
32
33
    def index(self):
33
    def index(self):
34
        result = super(BlogPostSnapshot, self).index()
34
        result = super(MetricSnapshot, self).index()
35
        result.update(
35
        result.update(
36
            title_s='Version %d of %s' % (
36
            title_s='Version %d of %s' % (
37
                self.version, self.original().shorthand_id()),
37
                self.version, self.original().shorthand_id()),
38
            type_s=self.type_s,
38
            type_s=self.type_s,
39
            text=self.data.text)
39
            text=self.data.text)
...
...
43
    def html_text(self):
43
    def html_text(self):
44
        """A markdown processed version of the page text"""
44
        """A markdown processed version of the page text"""
45
        return g.markdown_wiki.convert(self.data.text)
45
        return g.markdown_wiki.convert(self.data.text)
46
46
47
    @property
47
    @property
48
    def attachments(self):
49
        return self.original().attachments
50
51
    @property
52
    def email_address(self):
48
    def email_address(self):
53
        return self.original().email_address
49
        return self.original().email_address
54
50
55
class BlogPost(M.VersionedArtifact):
51
class MetricPost(M.VersionedArtifact):
56
    class __mongometa__:
52
    class __mongometa__:
57
        name='blog_post'
53
        name='metric'
58
        history_class = BlogPostSnapshot
54
        history_class = MetricSnapshot
59
        unique_indexes = [ ('project_id', 'app_config_id', 'slug') ]
55
        unique_indexes = [ ('project_id', 'app_config_id', 'slug') ]
60
    type_s = 'Blog Post'
56
    type_s = 'Metric'
61
57
62
    title = FieldProperty(str, if_missing='Untitled')
58
    title = FieldProperty(str, if_missing='Untitled')
63
    text = FieldProperty(str, if_missing='')
59
    text = FieldProperty(str, if_missing='')
64
    timestamp = FieldProperty(datetime, if_missing=datetime.utcnow)
60
    timestamp = FieldProperty(datetime, if_missing=datetime.utcnow)
65
    slug = FieldProperty(str)
61
    slug = FieldProperty(str)
66
    state = FieldProperty(schema.OneOf('draft', 'published'), if_missing='draft')
62
    state = FieldProperty(schema.OneOf('draft', 'published'), if_missing='draft')
67
    neighborhood_id = ForeignIdProperty('Neighborhood', if_missing=None)
63
    neighborhood_id = ForeignIdProperty('Neighborhood', if_missing=None)
68
64
69
    def author(self):
65
    def author(self):
70
        '''The author of the first snapshot of this BlogPost'''
66
        '''The author of the first snapshot of this Metric'''
71
        return M.User.query.get(_id=self.get_version(1).author.id) or M.User.anonymous()
67
        return M.User.query.get(_id=self.get_version(1).author.id) or M.User.anonymous()
72
68
73
    def _get_date(self):
69
    def _get_date(self):
74
        return self.timestamp.date()
70
        return self.timestamp.date()
75
    def _set_date(self, value):
71
    def _set_date(self, value):
...
...
84
80
85
    @property
81
    @property
86
    def html_text(self):
82
    def html_text(self):
87
        return g.markdown.convert(self.text)
83
        return g.markdown.convert(self.text)
88
84
89
    @property
90
    def html_text_preview(self):
91
        """Return an html preview of the BlogPost text.
92
93
        Truncation happens at paragraph boundaries to avoid chopping markdown
94
        in inappropriate places.
95
96
        If the entire post is one paragraph, the full text is returned.
97
        If the entire text is <= 400 chars, the full text is returned.
98
        Else, at least 400 chars are returned, rounding up to the nearest
99
        whole paragraph.
100
101
        If truncation occurs, a hyperlink to the full text is appended.
102
103
        """
104
        # Splitting on spaces or single lines breaks isn't sufficient as some
105
        # markup can span spaces and single line breaks. Converting to HTML
106
        # first and *then* truncating doesn't work either, because the
107
        # ellipsis tag ends up orphaned from the main text.
108
        ellipsis = '... [read more](%s)' % self.url()
109
        paragraphs = self.text.replace('\r','').split('\n\n')
110
        total_length = 0
111
        for i, p in enumerate(paragraphs):
112
            total_length += len(p)
113
            if total_length >= 400:
114
                break
115
        text = '\n\n'.join(paragraphs[:i+1])
116
        return g.markdown.convert(text + (ellipsis if i + 1 < len(paragraphs)
117
                                                   else ''))
118
85
119
    @property
86
    @property
120
    def email_address(self):
87
    def email_address(self):
121
        domain = '.'.join(reversed(self.app.url[1:-1].split('/'))).replace('_', '-')
88
        domain = '.'.join(reversed(self.app.url[1:-1].split('/'))).replace('_', '-')
122
        return '%s@%s%s' % (self.title.replace('/', '.'), domain, config.common_suffix)
89
        return '%s@%s%s' % (self.title.replace('/', '.'), domain, config.common_suffix)
...
...
143
110
144
    def shorthand_id(self):
111
    def shorthand_id(self):
145
        return self.slug
112
        return self.slug
146
113
147
    def index(self):
114
    def index(self):
148
        result = super(BlogPost, self).index()
115
        result = super(Metric, self).index()
149
        result.update(
116
        result.update(
150
            title_s=self.slug,
117
            title_s=self.slug,
151
            type_s=self.type_s,
118
            type_s=self.type_s,
152
            state_s=self.state,
119
            state_s=self.state,
153
            snippet_s='%s: %s' % (self.title, h.text.truncate(self.text, 200)),
120
            snippet_s='%s: %s' % (self.title, h.text.truncate(self.text, 200)),
...
...
158
        HC = self.__mongometa__.history_class
125
        HC = self.__mongometa__.history_class
159
        return HC.query.find({'artifact_id':self._id, 'version':int(version)}).one()
126
        return HC.query.find({'artifact_id':self._id, 'version':int(version)}).one()
160
127
161
    def commit(self):
128
    def commit(self):
162
        self.subscribe()
129
        self.subscribe()
163
        super(BlogPost, self).commit()
130
        super(Metric, self).commit()
164
        if self.version > 1:
165
            v1 = self.get_version(self.version-1)
166
            v2 = self
167
            la = [ line + '\n'  for line in v1.text.splitlines() ]
168
            lb = [ line + '\n'  for line in v2.text.splitlines() ]
169
            diff = ''.join(patience.unified_diff(
170
                    la, lb,
171
                    'v%d' % v1.version,
172
                    'v%d' % v2.version))
173
            description = diff
174
            if v1.state != 'published' and v2.state == 'published':
175
                M.Feed.post(self, self.title, self.text, author=self.author())
176
                description = self.text
177
                subject = '%s created post %s' % (
178
                    c.user.username, self.title)
179
            elif v1.title != v2.title:
180
                subject = '%s renamed post %s to %s' % (
181
                    c.user.username, v2.title, v1.title)
182
            else:
183
                subject = '%s modified post %s' % (
184
                    c.user.username, self.title)
185
        else:
186
            description = self.text
131
        description = self.text
187
            subject = '%s created post %s' % (
132
        subject = '%s created metric %s' % (
188
                c.user.username, self.title)
133
            c.user.username, self.title)
189
            if self.state == 'published':
190
                M.Feed.post(self, self.title, self.text, author=self.author())
191
        if self.state == 'published':
134
        if self.state == 'published':
192
            M.Notification.post(
135
            M.Notification.post(
193
                artifact=self, topic='metadata', text=description, subject=subject)
136
                artifact=self, topic='metadata', text=description, subject=subject)
194
137
195
class Attachment(M.BaseAttachment):
196
    ArtifactClass=BlogPost
197
    class __mongometa__:
198
        polymorphic_identity='BlogAttachment'
199
    attachment_type=FieldProperty(str, if_missing='BlogAttachment')
200
201
202
Mapper.compile_all()
138
Mapper.compile_all()