Package pyamf :: Package adapters :: Module _django_db_models_base |
[hide private]
[frames] | no frames]

# Source Code for Module pyamf.adapters._django_db_models_base

      1  # Copyright (c) 2007-2009 The PyAMF Project.
      2  # See LICENSE.txt for details.
      3
      4  """
      5  C{django.db.models} adapter module.
      6
      7  @see: U{Django Project<http://www.djangoproject.com>}
      8
      9  @since: 0.4.1
     10  """
     11
     12  from django.db.models.base import Model
     13  from django.db.models import fields
     14  from django.db.models.fields import related, files
     15
     16  import datetime
     17
     18  import pyamf
     19  from pyamf.util import imports
     20
     21
     22 -class DjangoReferenceCollection(dict):
     23      """
     24      This helper class holds a dict of klass to pk/objects loaded from the
     25      underlying db.
     26
     27      @since: 0.5
     28      """
     29
     30 -    def _getClass(self, klass):
     31          if klass not in self.keys():
     32              self[klass] = {}
     33
     34          return self[klass]
     35
     36 -    def getClassKey(self, klass, key):
     37          """
     38          Return an instance based on klass/key.
     39
     40          If an instance cannot be found then L{KeyError} is raised.
     41
     42          @param klass: The class of the instance.
     43          @param key: The primary_key of the instance.
     44          @return: The instance linked to the C{klass}/C{key}.
     45          @rtype: Instance of L{klass}.
     46          """
     47          d = self._getClass(klass)
     48
     49          return d[key]
     50
     51 -    def addClassKey(self, klass, key, obj):
     52          """
     53          Adds an object to the collection, based on klass and key.
     54
     55          @param klass: The class of the object.
     56          @param key: The datastore key of the object.
     57          @param obj: The loaded instance from the datastore.
     58          """
     59          d = self._getClass(klass)
     60
     61          d[key] = obj
     62
     63
     64 -class DjangoClassAlias(pyamf.ClassAlias):
     65      """
     66      """
     67
     68 -    def getCustomProperties(self):
     69          self.fields = {}
     70          self.relations = {}
     71          self.columns = []
     72
     73          self.meta = self.klass._meta
     74
     75          for x in self.meta.local_fields:
     76              if isinstance(x, files.FileField):
     77                  self.readonly_attrs.update([x.name])
     78
     79              if not isinstance(x, related.ForeignKey):
     80                  self.fields[x.name] = x
     81              else:
     82                  self.relations[x.name] = x
     83
     84              self.columns.append(x.attname)
     85
     86          for k, v in self.klass.__dict__.iteritems():
     87              if isinstance(v, related.ReverseManyRelatedObjectsDescriptor):
     88                  self.fields[k] = v.field
     89
     90          parent_fields = []
     91
     92          for field in self.meta.parents.values():
     93              parent_fields.append(field.attname)
     94              del self.relations[field.name]
     95
     96          self.exclude_attrs.update(parent_fields)
     97
     98          props = self.fields.keys()
     99
    100          self.static_attrs.update(props)
    101          self.encodable_properties.update(props)
    102          self.decodable_properties.update(props)
    103
    104 -    def _compile_base_class(self, klass):
    105          if klass is Model:
    106              return
    107
    108          pyamf.ClassAlias._compile_base_class(self, klass)
    109
    110 -    def _encodeValue(self, field, value):
    111          if value is fields.NOT_PROVIDED:
    112              return pyamf.Undefined
    113
    114          if value is None:
    115              return value
    116
    117          # deal with dates ..
    118          if isinstance(field, fields.DateTimeField):
    119              return value
    120          elif isinstance(field, fields.DateField):
    121              return datetime.datetime(value.year, value.month, value.day, 0, 0, 0)
    122          elif isinstance(field, fields.TimeField):
    123              return datetime.datetime(1970, 1, 1,
    124                  value.hour, value.minute, value.second, value.microsecond)
    125          elif isinstance(value, files.FieldFile):
    126              return value.name
    127
    128          return value
    129
    130 -    def _decodeValue(self, field, value):
    131          if value is pyamf.Undefined:
    132              return fields.NOT_PROVIDED
    133
    134          if isinstance(field, fields.AutoField) and value == 0:
    135              return None
    136          elif isinstance(field, fields.DateTimeField):
    137              # deal with dates
    138              return value
    139          elif isinstance(field, fields.DateField):
    140              if not value:
    141                  return None
    142
    143              return datetime.date(value.year, value.month, value.day)
    144          elif isinstance(field, fields.TimeField):
    145              if not value:
    146                  return None
    147
    148              return datetime.time(value.hour, value.minute, value.second, value.microsecond)
    149
    150          return value
    151
    152 -    def getEncodableAttributes(self, obj, **kwargs):
    153          sa, da = pyamf.ClassAlias.getEncodableAttributes(self, obj, **kwargs)
    154
    155          for name, prop in self.fields.iteritems():
    156              if name not in sa:
    157                  continue
    158
    159              if isinstance(prop, related.ManyToManyField):
    160                  sa[name] = [x for x in getattr(obj, name).all()]
    161              else:
    162                  sa[name] = self._encodeValue(prop, getattr(obj, name))
    163
    164          if not da:
    165              da = {}
    166
    167          keys = da.keys()
    168
    169          for key in keys:
    170              if key.startswith('_'):
    171                  del da[key]
    172              elif key in self.columns:
    173                  del da[key]
    174
    175          for name, relation in self.relations.iteritems():
    176              if '_%s_cache' % name in obj.__dict__:
    177                  da[name] = getattr(obj, name)
    178              else:
    179                  da[name] = pyamf.Undefined
    180
    181          if not da:
    182              da = None
    183
    184          return sa, da
    185
    186 -    def getDecodableAttributes(self, obj, attrs, **kwargs):
    187          attrs = pyamf.ClassAlias.getDecodableAttributes(self, obj, attrs, **kwargs)
    188
    189          for n in self.decodable_properties:
    190              f = self.fields[n]
    191
    192              attrs[f.attname] = self._decodeValue(f, attrs[n])
    193
    194          # primary key of django object must always be set first for
    195          # relationships with other model objects to work properly
    196          # and dict.iteritems() does not guarantee order
    197          #
    198          # django also forces the use only one attribute as primary key, so
    199          # our obj._meta.pk.attname check is sufficient)
    200          try:
    201              setattr(obj, obj._meta.pk.attname, attrs[obj._meta.pk.attname])
    202              del attrs[obj._meta.pk.attname]
    203          except KeyError:
    204              pass
    205
    206          return attrs
    207
    208
    209 -def getDjangoObjects(context):
    210      """
    211      Returns a reference to the C{django_objects} on the context. If it doesn't
    212      exist then it is created.
    213
    214      @param context: The context to load the C{django_objects} index from.
    215      @type context: Instance of L{pyamf.BaseContext}
    216      @return: The C{django_objects} index reference.
    217      @rtype: Instance of L{DjangoReferenceCollection}
    218      @since: 0.5
    219      """
    220      if not hasattr(context, 'django_objects'):
    221          context.django_objects = DjangoReferenceCollection()
    222
    223      return context.django_objects
    224
    225
    226 -def writeDjangoObject(self, obj, *args, **kwargs):
    227      """
    228      The Django ORM creates new instances of objects for each db request.
    229      This is a problem for PyAMF as it uses the id(obj) of the object to do
    230      reference checking.
    231
    232      We could just ignore the problem, but the objects are conceptually the
    233      same so the effort should be made to attempt to resolve references for a
    234      given object graph.
    235
    236      We create a new map on the encoder context object which contains a dict of
    237      C{object.__class__: {key1: object1, key2: object2, .., keyn: objectn}}. We
    238      use the primary key to do the reference checking.
    239
    240      @since: 0.5
    241      """
    242      if not isinstance(obj, Model):
    243          self.writeNonDjangoObject(obj, *args, **kwargs)
    244
    245          return
    246
    247      context = self.context
    248      kls = obj.__class__
    249
    250      s = obj.pk
    251
    252      django_objects = getDjangoObjects(context)
    253
    254      try:
    255          referenced_object = django_objects.getClassKey(kls, s)
    256      except KeyError:
    257          referenced_object = obj
    258          django_objects.addClassKey(kls, s, obj)
    259
    260      self.writeNonDjangoObject(referenced_object, *args, **kwargs)
    261
    262
    263 -def install_django_reference_model_hook(mod):
    264      """
    265      Called when L{pyamf.amf0} or L{pyamf.amf3} are imported. Attaches the
    266      L{writeDjangoObject} method to the C{Encoder} class in that module.
    267
    268      @param mod: The module imported.
    269      @since: 0.4.1
    270      """
    271      if not hasattr(mod.Encoder, 'writeNonDjangoObject'):
    272          mod.Encoder.writeNonDjangoObject = mod.Encoder.writeObject
    273          mod.Encoder.writeObject = writeDjangoObject
    274
    275
    276  # initialise the module here: hook into pyamf
    277
    278  pyamf.register_alias_type(DjangoClassAlias, Model)
    279
    280  # hook the L{writeDjangobject} method to the Encoder class on import
    281  imports.when_imported('pyamf.amf0', install_django_reference_model_hook)
    282  imports.when_imported('pyamf.amf3', install_django_reference_model_hook)
    283