~launchpad-pqm/launchpad/devel

8687.15.17 by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/.
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
3
4
"""Announcement feed (syndication) views."""
5
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
6
# This module has been chosen to be the example for how to implement a new
7
# feed class.  While the two interfaces `IFeed` and `IFeedEntry` are heavily
8
# documented, additional documentation has been added to this module to
9
# clearly demonstrate the concepts required to implement a feed rather than
10
# simply referencing the interfaces.
11
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
12
__metaclass__ = type
13
14
__all__ = [
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
15
    'LaunchpadAnnouncementsFeed',
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
16
    'TargetAnnouncementsFeed',
17
    ]
18
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
19
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
20
from zope.component import getUtility
21
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
22
from canonical.launchpad.interfaces.launchpad import IFeedsApplication
23
from canonical.launchpad.webapp import (
24
    canonical_url,
25
    urlappend,
26
    )
27
from canonical.lazr.feed import (
28
    FeedBase,
29
    FeedEntry,
30
    FeedPerson,
31
    FeedTypedData,
32
    )
33
from lp.app.browser.stringformatter import FormattersAPI
10326.1.3 by Henning Eggers
Fixed too long lines.
34
from lp.registry.interfaces.announcement import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
35
    IAnnouncementSet,
36
    IHasAnnouncements,
37
    )
7675.110.3 by Curtis Hovey
Ran the migration script to move registry code to lp.registry.
38
from lp.registry.interfaces.distribution import IDistribution
39
from lp.registry.interfaces.product import IProduct
10326.1.2 by Henning Eggers
Renamed project interfaces module to projectgroup.
40
from lp.registry.interfaces.projectgroup import IProjectGroup
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
41
42
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
43
class AnnouncementsFeedBase(FeedBase):
44
    """Abstract class for announcement feeds."""
45
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
46
    # Every feed must have a feed name.  This name will be used to construct
47
    # the final element in the URL for the feed with the extension for one of
48
    # the supported feed types appended.  So announcement feeds will end with
49
    # 'announcements.atom' or 'announcements.html'.
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
50
    feedname = "announcements"
51
52
    @property
5427.5.10 by Brad Crittenden
Added more documentation to IFeed and created IFeedEntry. Renamed some attributes for clarity and consistency.
53
    def link_alternate(self):
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
54
        """See `IFeed`."""
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
55
        # Return the human-readable alternate URL for this feed.  For example:
56
        # https://launchpad.net/ubuntu/+announcements
5204.6.18 by Brad Crittenden
Removed normalizedUrl and us urlappend instead.
57
        return urlappend(canonical_url(self.context, rootsite="mainsite"),
58
                         "+announcements")
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
59
60
    def itemToFeedEntry(self, announcement):
61
        """See `IFeed`."""
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
62
        # Given an instance of an announcement, create a FeedEntry out of it
63
        # and return.
64
65
        # The title for the FeedEntry is an IFeedTypedData instance and may be
66
        # plain text or html.
67
        title = self._entryTitle(announcement)
68
        # The link_alternate for the entry is the human-readable alternate URL
69
        # for the entry.  For example:
70
        # http://launchpad.net/ubuntu/+announcment/12
5427.5.10 by Brad Crittenden
Added more documentation to IFeed and created IFeedEntry. Renamed some attributes for clarity and consistency.
71
        entry_link_alternate = "%s%s" % (
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
72
            canonical_url(announcement.target, rootsite=self.rootsite),
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
73
            "/+announcement/%d" % announcement.id)
74
        # The content of the entry is the text displayed as the body in the
75
        # feed reader.  For announcements it is plain text but it must be
76
        # escaped to account for any special characters the user may have
77
        # entered, such as '&' and '<' because it will be embedded in the XML
78
        # document.
5475.2.1 by Edwin Grubbs
Fixed bug 175780
79
        formatted_summary = FormattersAPI(announcement.summary).text_to_html()
5204.6.35 by Brad Crittenden
Correct relative hrefs in feeds. Removed duplicate branch entries.
80
        content = FeedTypedData(formatted_summary,
81
                                content_type="html",
82
                                root_url=self.root_url)
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
83
        # The entry for an announcement has distinct dates for created,
84
        # updated, and published.  For some data, the created and published
85
        # dates will be the same.  The announcements also only have a singe
86
        # author.
5958.2.10 by Brad Crittenden
Changes from JamesH review.
87
88
        entry_id = 'tag:launchpad.net,%s:/+announcement/%d' % (
89
            announcement.date_created.date().isoformat(),
90
            announcement.id)
91
        entry = FeedEntry(
5958.2.1 by Brad Crittenden
Changes to make feeds database access more efficient.
92
            title=title,
93
            link_alternate=entry_link_alternate,
94
            date_created=announcement.date_created,
95
            date_updated=announcement.date_updated,
96
            date_published=announcement.date_announced,
97
            authors=[FeedPerson(announcement.registrant,
98
                                rootsite="mainsite")],
5958.2.10 by Brad Crittenden
Changes from JamesH review.
99
            content=content,
100
            id_=entry_id)
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
101
        return entry
102
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
103
    def _entryTitle(self, announcement):
104
        """Return the title for the announcement.
105
106
        Override in each base class.
107
        """
108
        raise NotImplementedError
109
110
111
class LaunchpadAnnouncementsFeed(AnnouncementsFeedBase):
5151.1.49 by Mark Shuttleworth
Additional review feedback and test fixes
112
    """Publish an Atom feed of all public announcements in Launchpad."""
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
113
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
114
    # The `usedfor` property identifies the class associated with this feed
115
    # class.  It is used by the `IFeedsDirective` in
116
    # launchpad/webapp/metazcml.py to provide a mapping from the supported
117
    # feed types to this class.  It is a more maintainable method than simply
118
    # listing each mapping in the zcml.  The only zcml change is to add this
119
    # class to the list of classes in the `browser:feeds` stanza of
120
    # launchpad/zcml/feeds.zcml.
5151.1.14 by Mark Shuttleworth
Publish single Atom feed with all announcements hosted in LP
121
    usedfor = IFeedsApplication
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
122
5958.2.7 by Brad Crittenden
Changed getItemsWorker to _getItemsWorker.
123
    def _getItemsWorker(self):
5958.2.4 by Brad Crittenden
Added caching to the getItems method for Feeds.
124
        """Create the list of items.
125
126
        Called by getItems which may cache the results.
127
        """
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
128
        # Return a list of items that will be the entries in the feed.  Each
129
        # item shall be an instance of `IFeedEntry`.
130
5151.1.49 by Mark Shuttleworth
Additional review feedback and test fixes
131
        # The quantity is defined in FeedBase or config file.
9041.1.1 by William Grant
IHasAnnouncements.announcements() -> getAnnouncements()
132
        items = getUtility(IAnnouncementSet).getAnnouncements(
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
133
            limit=self.quantity)
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
134
        # Convert the items into their feed entry representation.
135
        items = [self.itemToFeedEntry(item) for item in items]
136
        return items
137
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
138
    def _entryTitle(self, announcement):
139
        """Return an `IFeedTypedData` instance for the feed title."""
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
140
        return FeedTypedData('[%s] %s' % (
141
                announcement.target.name, announcement.title))
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
142
143
    @property
144
    def title(self):
145
        """See `IFeed`."""
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
146
        # The textual representation of the title for the feed.
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
147
        return "Announcements published via Launchpad"
148
149
    @property
150
    def logo(self):
151
        """See `IFeed`."""
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
152
        # The logo is an image representing the feed.  Since this feed is for
153
        # all announcements in Launchpad, return the Launchpad logo.
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
154
        url = '/@@/launchpad-logo'
155
        return self.site_url + url
156
157
    @property
158
    def icon(self):
159
        """See `IFeed`."""
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
160
        # The icon is an icon representing the feed.  Since this feed is for
161
        # all announcements in Launchpad, return the Launchpad icon.
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
162
        url = '/@@/launchpad'
163
        return self.site_url + url
164
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
165
166
class TargetAnnouncementsFeed(AnnouncementsFeedBase):
167
    """Publish an Atom feed of all announcements.
168
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
169
    Used for any class that implements IHasAnnouncements such as project,
170
    product, or distribution.
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
171
    """
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
172
    # This view is used for any class implementing `IHasAnnouncments`.
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
173
    usedfor = IHasAnnouncements
174
5958.2.7 by Brad Crittenden
Changed getItemsWorker to _getItemsWorker.
175
    def _getItemsWorker(self):
5958.2.4 by Brad Crittenden
Added caching to the getItems method for Feeds.
176
        """Create the list of items.
177
178
        Called by getItems which may cache the results.
179
        """
5151.1.49 by Mark Shuttleworth
Additional review feedback and test fixes
180
        # The quantity is defined in FeedBase or config file.
9041.1.1 by William Grant
IHasAnnouncements.announcements() -> getAnnouncements()
181
        items = self.context.getAnnouncements(limit=self.quantity)
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
182
        # Convert the items into their feed entry representation.
183
        items = [self.itemToFeedEntry(item) for item in items]
184
        return items
185
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
186
    def _entryTitle(self, announcement):
5427.5.1 by Brad Crittenden
Correcting feed elements for announcements and refactoring.
187
        return FeedTypedData(announcement.title)
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
188
189
    @property
190
    def title(self):
191
        """See `IFeed`."""
192
        return "%s Announcements" % self.context.displayname
193
194
    @property
195
    def logo(self):
196
        """See `IFeed`."""
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
197
        # The logo is different depending upon the context we are displaying.
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
198
        if self.context.logo is not None:
5151.1.32 by Mark Shuttleworth
Clean up unused imports in Announcements
199
            return self.context.logo.getURL()
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
200
        elif IProjectGroup.providedBy(self.context):
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
201
            url = '/@@/project-logo'
202
        elif IProduct.providedBy(self.context):
203
            url = '/@@/product-logo'
204
        elif IDistribution.providedBy(self.context):
205
            url = '/@@/distribution-logo'
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
206
        else:
207
            raise AssertionError(
208
                "Context for TargetsAnnouncementsFeed does not provide an "
209
                "expected interface.")
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
210
        return self.site_url + url
211
212
    @property
213
    def icon(self):
214
        """See `IFeed`."""
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
215
        # The icon is customized based upon the context.
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
216
        if self.context.icon is not None:
5151.1.32 by Mark Shuttleworth
Clean up unused imports in Announcements
217
            return self.context.icon.getURL()
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
218
        elif IProjectGroup.providedBy(self.context):
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
219
            url = '/@@/project'
220
        elif IProduct.providedBy(self.context):
221
            url = '/@@/product'
222
        elif IDistribution.providedBy(self.context):
223
            url = '/@@/distribution'
5427.5.11 by Brad Crittenden
Added additional documentation for feeds.
224
        else:
225
            raise AssertionError(
226
                "Context for TargetsAnnouncementsFeed does not provide an "
227
                "expected interface.")
5151.1.10 by Mark Shuttleworth
Lay foundations for announcement feeds
228
        return self.site_url + url