• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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