Merge ~pappacena/launchpad:stormify-faq into launchpad:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: 9e34b72d5dfc4cca22fa8fc78c62750b15cbcdb3
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~pappacena/launchpad:stormify-faq
Merge into: launchpad:master
Diff against target: 277 lines (+99/-66)
1 file modified
lib/lp/answers/model/faq.py (+99/-66)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+395042@code.launchpad.net

Commit message

Stormifying FAQ

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/answers/model/faq.py b/lib/lp/answers/model/faq.py
2index 74b133c..0c22fd5 100644
3--- a/lib/lp/answers/model/faq.py
4+++ b/lib/lp/answers/model/faq.py
5@@ -12,15 +12,25 @@ __all__ = [
6 ]
7
8 from lazr.lifecycle.event import ObjectCreatedEvent
9+import pytz
10 import six
11-from sqlobject import (
12- ForeignKey,
13- SQLMultipleJoin,
14- SQLObjectNotFound,
15- StringCol,
16+from storm.expr import (
17+ And,
18+ Desc,
19+ )
20+from storm.properties import (
21+ DateTime,
22+ Int,
23+ Unicode,
24+ )
25+from storm.references import (
26+ Reference,
27+ ReferenceSet,
28+ )
29+from storm.store import (
30+ EmptyResultSet,
31+ Store,
32 )
33-from storm.expr import And
34-from storm.references import ReferenceSet
35 from zope.event import notify
36 from zope.interface import implementer
37
38@@ -38,13 +48,12 @@ from lp.registry.interfaces.person import (
39 from lp.registry.interfaces.product import IProduct
40 from lp.registry.interfaces.projectgroup import IProjectGroup
41 from lp.services.database.constants import DEFAULT
42-from lp.services.database.datetimecol import UtcDateTimeCol
43-from lp.services.database.nl_search import nl_phrase_search
44-from lp.services.database.sqlbase import (
45- quote,
46- SQLBase,
47- sqlvalues,
48+from lp.services.database.interfaces import (
49+ IMasterStore,
50+ IStore,
51 )
52+from lp.services.database.nl_search import nl_phrase_search
53+from lp.services.database.stormbase import StormBase
54 from lp.services.database.stormexpr import (
55 fti_search,
56 rank_by_fti,
57@@ -52,41 +61,54 @@ from lp.services.database.stormexpr import (
58
59
60 @implementer(IFAQ)
61-class FAQ(SQLBase):
62+class FAQ(StormBase):
63 """See `IFAQ`."""
64
65- _table = 'FAQ'
66- _defaultOrder = ['date_created', 'id']
67+ __storm_table__ = "FAQ"
68+
69+ __storm_order__ = ['date_created', 'id']
70
71- owner = ForeignKey(
72- dbName='owner', foreignKey='Person',
73- storm_validator=validate_public_person, notNull=True)
74+ id = Int(primary=True)
75
76- title = StringCol(notNull=True)
77+ owner_id = Int(
78+ name="owner", allow_none=False, validator=validate_public_person)
79+ owner = Reference(owner_id, "Person.id")
80
81- keywords = StringCol(dbName="tags", notNull=False, default=None)
82+ title = Unicode(allow_none=False)
83
84- content = StringCol(notNull=False, default=None)
85+ keywords = Unicode(name="tags", allow_none=True, default=None)
86
87- date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
88+ content = Unicode(allow_none=True, default=None)
89
90- last_updated_by = ForeignKey(
91- dbName='last_updated_by', foreignKey='Person',
92- storm_validator=validate_public_person, notNull=False,
93- default=None)
94+ date_created = DateTime(allow_none=False, default=DEFAULT, tzinfo=pytz.UTC)
95
96- date_last_updated = UtcDateTimeCol(notNull=False, default=None)
97+ last_updated_by_id = Int(
98+ name="last_updated_by", allow_none=True, default=None,
99+ validator=validate_public_person)
100+ last_updated_by = Reference(last_updated_by_id, "Person.id")
101
102- product = ForeignKey(
103- dbName='product', foreignKey='Product', notNull=False, default=None)
104+ date_last_updated = DateTime(
105+ allow_none=True, default=None, tzinfo=pytz.UTC)
106
107- distribution = ForeignKey(
108- dbName='distribution', foreignKey='Distribution', notNull=False,
109- default=None)
110+ product_id = Int(name="product", allow_none=True, default=None)
111+ product = Reference(product_id, "Product.id")
112+
113+ distribution_id = Int(name="distribution", allow_none=True, default=None)
114+ distribution = Reference(distribution_id, "Distribution.id")
115
116 related_questions = ReferenceSet(
117 'id', 'Question.faq_id', order_by=('Question.datecreated'))
118
119+ def __init__(self, owner, title, content=None, keywords=None,
120+ date_created=DEFAULT, product=None, distribution=None):
121+ self.owner = owner
122+ self.title = title
123+ self.content = content
124+ self.keywords = keywords
125+ self.date_created = date_created
126+ self.product = product
127+ self.distribution = distribution
128+
129 @property
130 def target(self):
131 """See `IFAQ`."""
132@@ -99,7 +121,7 @@ class FAQ(SQLBase):
133 if not self.related_questions.is_empty():
134 raise CannotDeleteFAQ(
135 "Cannot delete FAQ: questions must be unlinked first.")
136- super(FAQ, self).destroySelf()
137+ Store.of(self).remove(self)
138
139 @staticmethod
140 def new(owner, title, content, keywords=keywords, date_created=None,
141@@ -119,9 +141,14 @@ class FAQ(SQLBase):
142 if date_created is None:
143 date_created = DEFAULT
144 faq = FAQ(
145- owner=owner, title=title, content=content, keywords=keywords,
146+ owner=owner, title=six.text_type(title),
147+ content=six.text_type(content),
148+ keywords=keywords,
149 date_created=date_created, product=product,
150 distribution=distribution)
151+ store = IMasterStore(FAQ)
152+ store.add(faq)
153+ store.flush()
154 notify(ObjectCreatedEvent(faq))
155 return faq
156
157@@ -143,12 +170,16 @@ class FAQ(SQLBase):
158 phrases = nl_phrase_search(summary, FAQ, [target_constraint])
159 if not phrases:
160 # No useful words to search on in that summary.
161- return FAQ.select('1 = 2')
162+ return EmptyResultSet()
163
164- return FAQ.select(
165- And(target_constraint, fti_search(FAQ, phrases, ftq=False)),
166- orderBy=[
167- rank_by_fti(FAQ, phrases, ftq=False), "-FAQ.date_created"])
168+ store = IStore(FAQ)
169+ resultset = store.find(
170+ FAQ,
171+ fti_search(FAQ, phrases, ftq=False),
172+ target_constraint)
173+ return resultset.order_by(
174+ rank_by_fti(FAQ, phrases, ftq=False),
175+ Desc(FAQ.date_created))
176
177 @staticmethod
178 def getForTarget(id, target):
179@@ -157,13 +188,12 @@ class FAQ(SQLBase):
180 When target is not None, the target will be checked to make sure
181 that the FAQ is in the expected target or return None otherwise.
182 """
183- try:
184- faq = FAQ.get(id)
185- if target is None or target == faq.target:
186- return faq
187- else:
188- return None
189- except SQLObjectNotFound:
190+ faq = IStore(FAQ).get(FAQ, int(id))
191+ if faq is None:
192+ return None
193+ if target is None or target == faq.target:
194+ return faq
195+ else:
196 return None
197
198
199@@ -229,41 +259,44 @@ class FAQSearch:
200
201 def getResults(self):
202 """Return the FAQs matching this search."""
203- return FAQ.select(
204- self.getConstraints(),
205- clauseTables=self.getClauseTables(),
206- orderBy=self.getOrderByClause())
207+ store = IStore(FAQ)
208+ tables = self.getClauseTables()
209+ if tables:
210+ store = store.using(*tables)
211+ resultset = store.find(FAQ, *self.getConstraints())
212+ return resultset.order_by(self.getOrderByClause())
213
214 def getConstraints(self):
215 """Return the constraints to use by this search."""
216+ from lp.registry.model.product import Product
217 constraints = []
218
219 if self.search_text:
220- constraints.append('FAQ.fti @@ ftq(%s)' % quote(self.search_text))
221+ constraints.append(fti_search(FAQ, self.search_text))
222
223 if self.owner:
224- constraints.append('FAQ.owner = %s' % sqlvalues(self.owner))
225+ constraints.append(FAQ.owner == self.owner)
226
227 if self.product:
228- constraints.append('FAQ.product = %s' % sqlvalues(self.product))
229+ constraints.append(FAQ.product == self.product)
230
231 if self.distribution:
232- constraints.append(
233- 'FAQ.distribution = %s' % sqlvalues(self.distribution))
234+ constraints.append(FAQ.distribution == self.distribution)
235
236 if self.projectgroup:
237- constraints.append(
238- 'FAQ.product = Product.id AND Product.project = %s' % (
239- sqlvalues(self.projectgroup)))
240+ constraints.append(And(
241+ FAQ.product == Product.id,
242+ Product.projectgroup == self.projectgroup))
243
244- return '\n AND '.join(constraints)
245+ return constraints
246
247 def getClauseTables(self):
248 """Return the tables that should be added to the FROM clause."""
249+ from lp.registry.model.product import Product
250 if self.projectgroup:
251- return ['Product']
252+ return [FAQ, Product]
253 else:
254- return []
255+ return [FAQ]
256
257 def getOrderByClause(self):
258 """Return the ORDER BY clause to sort the results."""
259@@ -274,15 +307,15 @@ class FAQSearch:
260 else:
261 sort = FAQSort.NEWEST_FIRST
262 if sort is FAQSort.NEWEST_FIRST:
263- return "-FAQ.date_created"
264+ return Desc(FAQ.date_created)
265 elif sort is FAQSort.OLDEST_FIRST:
266- return "FAQ.date_created"
267+ return FAQ.date_created
268 elif sort is FAQSort.RELEVANCY:
269 if self.search_text:
270 return [
271- rank_by_fti(FAQ, self.search_text), "-FAQ.date_created"]
272+ rank_by_fti(FAQ, self.search_text), Desc(FAQ.date_created)]
273 else:
274- return "-FAQ.date_created"
275+ return Desc(FAQ.date_created)
276 else:
277 raise AssertionError("Unknown FAQSort value: %r" % sort)
278