1#!/usr/bin/env python 2# 3# Copyright 2010 Google Inc. 4# 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 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 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. 16# 17 18"""Internal Datastore model for the Tunes DB. 19 20The Tunes DB is a simple polymophic structure composed of polymorphic 21Info entities. Artists and Albums are represented. 22""" 23 24__author__ = 'rafek@google.com (Rafe Kaplan)' 25 26import re 27 28from google.appengine.ext import db 29from google.appengine.ext.db import polymodel 30 31 32_SEARCH_NAME_REGEX = re.compile('\w+', re.UNICODE) 33 34 35def _normalize_name(name): 36 """Helper used to convert a user entered name in to search compatible string. 37 38 In order to make search as error free as possible, names of info records 39 are converted to a simplified utf-8 encoded string that makes prefix searches 40 easy. to make searching simpler, it removes all extra punctuation and spaces. 41 42 Examples: 43 _normalize_name('Duke Ellington') == 'duke ellington' 44 _normalize_name(' Duke Ellington ') == 'duke ellington' 45 _normalize_name('Duke-Ellington!') == 'duke ellington' 46 _normalize_name('Duke_Ellington') == 'duke ellington' 47 _normalize_name(u'Duk\xea Ellington') == 'Duk\xc3\xaa Ellington' 48 49 Args: 50 name: Name to convert to search string. 51 52 Returns: 53 Lower case, single space separated ByteString of name with punctuation 54 removed. Unicode values are converted to UTF-8 encoded string. 55 """ 56 if name is None: 57 return None 58 elif isinstance(name, str): 59 name = name.decode('utf-8') 60 61 # Must explicitly replace '_' because the \w re part does not 62 name = name.replace(u'_', u' ') 63 64 names = _SEARCH_NAME_REGEX.findall(name) 65 name = ' '.join(names) 66 return db.ByteString(name.lower().encode('utf-8')) 67 68 69class Info(polymodel.PolyModel): 70 """Base class for all Info records in Tunes DB. 71 72 Properties: 73 name: User friendly name for record. 74 encoded_name: Derived from name to allow easy prefix searching. Name is 75 transformed using _normalize_name. 76 """ 77 78 name = db.StringProperty() 79 80 @db.ComputedProperty 81 def encoded_name(self): 82 return _normalize_name(self.name) 83 84 @classmethod 85 def search(cls, name_prefix=None): 86 """Create search query based on info record name prefix. 87 88 Args: 89 name_prefix: User input name-prefix to search for. If name_prefix 90 is empty string or None returns all records of Info sub-class. Records 91 are sorted by their encoded name. 92 93 Returns: 94 Datastore query pointing to search results. 95 """ 96 name_prefix = _normalize_name(name_prefix) 97 query = cls.all().order('encoded_name') 98 if name_prefix: 99 query.filter('encoded_name >=', db.ByteString(name_prefix)) 100 # Do not need to worry about name_prefix + '\xff\xff' because not 101 # a unicode character. 102 query.filter('encoded_name <=', db.ByteString(name_prefix + '\xff')) 103 return query 104 105 106class ArtistInfo(Info): 107 """Musician or music group responsible for recording. 108 109 Properties: 110 album_count: Number of albums produced by artist. 111 albums: Implicit collection of albums produced by artist. 112 """ 113 114 album_count = db.IntegerProperty(default=0) 115 116 117class AlbumInfo(Info): 118 """Album produced by a musician or music group. 119 120 Properties: 121 artist: Artist that produced album. 122 released: Year that album was released. 123 """ 124 125 artist = db.ReferenceProperty(ArtistInfo, 126 collection_name='albums', 127 required=True) 128 released = db.IntegerProperty() 129