3 # Copyright 2012 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """ Contains the Datastore model classes used by the app: Category, Product,
19 Each Product entity will have a corresponding indexed "product" search.Document.
20 Product entities contain a subset of the fields in their corresponding document.
21 Product Review entities are not indexed (do not have corresponding Documents).
22 Reviews include a product id field, pointing to their 'parent' product, but
23 are not part of the same entity group, thus avoiding contention in
24 scenarios where a large number of product reviews might be edited/added at once.
32 from google
.appengine
.api
import memcache
33 from google
.appengine
.ext
import ndb
36 class Category(ndb
.Model
):
37 """The model class for product category information. Supports building a
42 _RCATEGORY_DICT
= None
43 _ROOT
= 'root' # the 'root' category of the category tree
45 parent_category
= ndb
.KeyProperty()
48 def category_name(self
):
52 def buildAllCategories(cls
):
53 """ build the category instances from the provided static data, if category
54 entities do not already exist in the Datastore. (see categories.py)."""
56 # Don't build if there are any categories in the datastore already
59 root_category
= categories
.ctree
60 cls
.buildCategory(root_category
, None)
63 def buildCategory(cls
, category_data
, parent_key
):
64 """build a category and any children from the given data dict."""
68 cname
= category_data
.get('name')
70 logging
.warn('no category name for %s', category
)
73 cat
= cls(id=cname
, parent_category
=parent_key
)
78 children
= category_data
.get('children')
79 # if there are any children, build them using their parent key
80 cls
.buildChildCategories(children
, cat
.key
)
83 def buildChildCategories(cls
, children
, parent_key
):
84 """Given a list of category data structures and a parent key, build the
85 child categories, with the given key as their entity group parent."""
87 cls
.buildCategory(cat
, parent_key
)
90 def getCategoryInfo(cls
):
91 """Build and cache a list of category id/name correspondences. This info is
92 used to populate html select menus."""
93 if not cls
._CATEGORY
_INFO
:
94 cls
.buildAllCategories() #first build categories from data file
96 cats
= cls
.query().fetch()
97 cls
._CATEGORY
_INFO
= [(c
.key
.id(), c
.key
.id()) for c
in cats
98 if c
.key
.id() != cls
._ROOT
]
99 return cls
._CATEGORY
_INFO
101 class Product(ndb
.Model
):
102 """Model for Product data. A Product entity will be built for each product,
103 and have an associated search.Document. The product entity does not include
104 all of the fields in its corresponding indexed product document, only 'core'
107 doc_id
= ndb
.StringProperty() # the id of the associated document
108 price
= ndb
.FloatProperty()
109 category
= ndb
.StringProperty()
110 # average rating of the product over all its reviews
111 avg_rating
= ndb
.FloatProperty(default
=0)
112 # the number of reviews of that product
113 num_reviews
= ndb
.IntegerProperty(default
=0)
114 active
= ndb
.BooleanProperty(default
=True)
115 # indicates whether the associated document needs to be re-indexed due to a
116 # change in the average review rating.
117 needs_review_reindex
= ndb
.BooleanProperty(default
=False)
124 """Retrieve all the (active) associated reviews for this product, via the
125 reviews' product_key field."""
127 Review
.active
== True,
128 Review
.rating_added
== True,
129 Review
.product_key
== self
.key
).fetch()
132 def updateProdDocsWithNewRating(cls
, pkeys
):
133 """Given a list of product entity keys, check each entity to see if it is
134 marked as needing a document re-index. This flag is set when a new review
135 is created for that product, and config.BATCH_RATINGS_UPDATE = True.
136 Generate the modified docs as needed and batch re-index them."""
141 prod
= cls
.get_by_id(pid
)
142 if prod
and prod
.needs_review_reindex
:
144 # update the associated document with the new ratings info
146 modified_doc
= docs
.Product
.updateRatingInDoc(
147 prod
.doc_id
, prod
.avg_rating
)
149 doclist
.append(modified_doc
)
150 prod
.needs_review_reindex
= False
153 ndb
.transaction(lambda: _tx(pkey
.id()))
154 # reindex all modified docs in batch
155 docs
.Product
.add(doclist
)
158 def create(cls
, params
, doc_id
):
159 """Create a new product entity from a subset of the given params dict
160 values, and the given doc_id."""
162 id=params
['pid'], price
=params
['price'],
163 category
=params
['category'], doc_id
=doc_id
)
167 def update_core(self
, params
, doc_id
):
168 """Update 'core' values from the given params dict and doc_id."""
170 price
=params
['price'], category
=params
['category'],
174 def updateProdDocWithNewRating(cls
, pid
):
175 """Given the id of a product entity, see if it is marked as needing
176 a document re-index. This flag is set when a new review is created for
177 that product. If it needs a re-index, call the document method."""
180 prod
= cls
.get_by_id(pid
)
181 if prod
and prod
.needs_review_reindex
:
182 prod
.needs_review_reindex
= False
184 return (prod
.doc_id
, prod
.avg_rating
)
185 (doc_id
, avg_rating
) = ndb
.transaction(_tx
)
186 # update the associated document with the new ratings info
188 docs
.Product
.updateRatingsInfo(doc_id
, avg_rating
)
191 class Review(ndb
.Model
):
192 """Model for Review data. Associated with a product entity via the product
195 doc_id
= ndb
.StringProperty()
196 date_added
= ndb
.DateTimeProperty(auto_now_add
=True)
197 product_key
= ndb
.KeyProperty(kind
=Product
)
198 username
= ndb
.StringProperty()
199 rating
= ndb
.IntegerProperty()
200 active
= ndb
.BooleanProperty(default
=True)
201 comment
= ndb
.TextProperty()
202 rating_added
= ndb
.BooleanProperty(default
=False)
205 def deleteReviews(cls
, pid
):
206 """Deletes the reviews associated with a product id."""
210 cls
.product_key
== ndb
.Key(Product
, pid
)).fetch(keys_only
=True)
211 return ndb
.delete_multi(reviews
)