diff options
Diffstat (limited to 'appengine_django/models.py')
| -rwxr-xr-x | appengine_django/models.py | 182 | 
1 files changed, 182 insertions, 0 deletions
| diff --git a/appengine_django/models.py b/appengine_django/models.py new file mode 100755 index 0000000..0b9f6dc --- /dev/null +++ b/appengine_django/models.py @@ -0,0 +1,182 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#     http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import types + +from google.appengine.ext import db + +from django import VERSION +from django.core.exceptions import ObjectDoesNotExist +from django.db.models.fields import Field +from django.db.models.options import Options +from django.db.models.loading import register_models, get_model + + +class ModelManager(object): +  """Replacement for the default Django model manager.""" + +  def __init__(self, owner): +    self.owner = owner + +  def __getattr__(self, name): +    """Pass all attribute requests through to the real model""" +    return getattr(self.owner, name) + + +class ModelOptions(object): +  """Replacement for the default Django options class. + +  This class sits at ._meta of each model. The primary information supplied by +  this class that needs to be stubbed out is the list of fields on the model. +  """ + +  def __init__(self, cls): +    self.object_name = cls.__name__ +    self.module_name = self.object_name.lower() +    model_module = sys.modules[cls.__module__] +    self.app_label = model_module.__name__.split('.')[-2] +    self.abstract = False + +  class pk: +    """Stub the primary key to always be 'key_name'""" +    name = "key_name" + +  def __str__(self): +    return "%s.%s" % (self.app_label, self.module_name) + +  @property +  def many_to_many(self): +    """The datastore does not support many to many relationships.""" +    return [] + + +class Relation(object): +  def __init__(self, to): +    self.field_name = "key_name" + + +def PropertyWrapper(prop): +  """Wrapper for db.Property to make it look like a Django model Property""" +  if isinstance(prop, db.Reference): +    prop.rel = Relation(prop.reference_class) +  else: +    prop.rel = None +  prop.serialize = True +  return prop + + +class PropertiedClassWithDjango(db.PropertiedClass): +  """Metaclass for the combined Django + App Engine model class. + +  This metaclass inherits from db.PropertiedClass in the appengine library. +  This metaclass has two additional purposes: +  1) Register each model class created with Django (the parent class will take +     care of registering it with the appengine libraries). +  2) Add the (minimum number) of attributes and methods to make Django believe +     the class is a normal Django model. + +  The resulting classes are still not generally useful as Django classes and +  are intended to be used by Django only in limited situations such as loading +  and dumping fixtures. +  """ + +  def __new__(cls, name, bases, attrs): +    """Creates a combined appengine and Django model. + +    The resulting model will be known to both the appengine libraries and +    Django. +    """ +    if name == 'BaseModel': +      # This metaclass only acts on subclasses of BaseModel. +      return super(PropertiedClassWithDjango, cls).__new__(cls, name, +                                                           bases, attrs) + +    new_class = super(PropertiedClassWithDjango, cls).__new__(cls, name, +                                                              bases, attrs) + +    new_class._meta = ModelOptions(new_class) +    new_class.objects = ModelManager(new_class) +    new_class._default_manager = new_class.objects +    new_class.DoesNotExist = types.ClassType('DoesNotExist', +                                             (ObjectDoesNotExist,), {}) + +    m = get_model(new_class._meta.app_label, name, False) +    if m: +      return m + +    register_models(new_class._meta.app_label, new_class) +    return get_model(new_class._meta.app_label, name, False) + +  def __init__(cls, name, bases, attrs): +    """Initialises the list of Django properties. + +    This method takes care of wrapping the properties created by the superclass +    so that they look like Django properties and installing them into the +    ._meta object of the class so that Django can find them at the appropriate +    time. +    """ +    super(PropertiedClassWithDjango, cls).__init__(name, bases, attrs) +    if name == 'BaseModel': +      # This metaclass only acts on subclasses of BaseModel. +      return + +    fields = [PropertyWrapper(p) for p in cls._properties.values()] +    cls._meta.local_fields = fields + + +class BaseModel(db.Model): +  """Combined appengine and Django model. + +  All models used in the application should derive from this class. +  """ +  __metaclass__ = PropertiedClassWithDjango +  +  def __eq__(self, other): +    if not isinstance(other, self.__class__): +      return False +    return self._get_pk_val() == other._get_pk_val() + +  def __ne__(self, other): +    return not self.__eq__(other) + +  def _get_pk_val(self): +    """Return the string representation of the model's key""" +    return unicode(self.key()) + +  def __repr__(self): +    """Create a string that can be used to construct an equivalent object. + +    e.g. eval(repr(obj)) == obj +    """ +    # First, creates a dictionary of property names and values. Note that +    # property values, not property objects, has to be passed in to constructor. +    def _MakeReprTuple(prop_name): +      prop = getattr(self.__class__, prop_name) +      return (prop_name, prop.get_value_for_datastore(self)) + +    d = dict([_MakeReprTuple(prop_name) for prop_name in self.properties()]) +    return "%s(**%s)" % (self.__class__.__name__, repr(d)) + + +class RegistrationTestModel(BaseModel): +  """Used to check registration with Django is working correctly. + +  Django 0.96 only recognises models defined within an applications models +  module when get_models() is called so this definition must be here rather +  than within the associated test (tests/model_test.py). +  """ +  pass | 
