# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from copy import deepcopy
from django.db.models import FieldDoesNotExist, ForeignKey, ManyToManyField
from django.utils import six
from filemaker.exceptions import FileMakerObjectDoesNotExist
try:
from functools import total_ordering
except ImportError: # pragma: no cover
# Python < 2.7
def total_ordering(cls): # NOQA
'Class decorator that fills-in missing ordering methods'
convert = {
'__lt__': [('__gt__', lambda self, other: other < self),
('__le__', lambda self, other: not other < self),
('__ge__', lambda self, other: not self < other)],
'__le__': [('__ge__', lambda self, other: other <= self),
('__lt__', lambda self, other: not other <= self),
('__gt__', lambda self, other: not self <= other)],
'__gt__': [('__lt__', lambda self, other: other > self),
('__ge__', lambda self, other: not other > self),
('__le__', lambda self, other: not self > other)],
'__ge__': [('__le__', lambda self, other: other >= self),
('__gt__', lambda self, other: not other >= self),
('__lt__', lambda self, other: not self >= other)]
}
if hasattr(object, '__lt__'):
roots = [op for op in convert
if getattr(cls, op) is not getattr(object, op)]
else:
roots = set(dir(cls)) & set(convert)
assert roots, 'must define at least one ordering operation: < > <= >='
root = max(roots) # prefer __lt __ to __le__ to __gt__ to __ge__
for opname, opfunc in convert[root]:
if opname not in roots:
opfunc.__name__ = opname
opfunc.__doc__ = getattr(int, opname).__doc__
setattr(cls, opname, opfunc)
return cls
class ManagerDescriptor(object):
def __init__(self, manager):
self.manager = manager
def __get__(self, instance, type=None):
if instance is not None:
raise AttributeError(
'Manager isn\'t accessible via {0} instances'.format(
type.__name__))
return self.manager(type)
class BaseFileMakerModel(type):
def __new__(cls, name, bases, attrs):
from filemaker.fields import BaseFileMakerField
from filemaker.manager import Manager
super_new = super(BaseFileMakerModel, cls).__new__
if name == 'NewBase' and attrs == {}:
return super_new(cls, name, bases, attrs)
meta_override = attrs.pop('meta', {})
fields = []
new_attrs = []
new_attrs.append((
'DoesNotExist',
type(str('DoesNotExist'), (FileMakerObjectDoesNotExist,), {})
))
for attr_name, attr_value in attrs.items():
if isinstance(attr_value, BaseFileMakerField):
attr_value.name = attr_name
if attr_value.fm_attr is None:
attr_value.fm_attr = attr_name
field = deepcopy(attr_value)
fields.append((attr_name, field))
else:
new_attrs.append((attr_name, attr_value))
fields = dict(fields)
new_attrs.append(('_fields', fields))
meta = {
'connection': None,
'pk_name':
'id' if 'id' in fields else ('pk' if 'pk' in fields else None),
'django_pk_name': 'pk',
'django_model': None,
'django_field_map': None,
'abstract': False,
'to_many_action': 'clear',
'ordering': 'id' if 'id' in fields else None,
'default_manager': Manager,
'related': [],
'many_related': [],
}
if isinstance(meta_override, dict):
meta.update(meta_override)
new_attrs.append(('_meta', meta))
new_class = super_new(cls, name, bases, dict(new_attrs))
new_class._attach_manager()
new_class._process_fields()
return new_class
def _attach_manager(cls):
from filemaker.manager import Manager
if not cls._meta['abstract'] and cls._meta['connection']:
setattr(cls, '_base_manager', ManagerDescriptor(Manager))
setattr(
cls,
'_default_manager',
ManagerDescriptor(cls._meta['default_manager'])
)
if not isinstance(getattr(
cls, 'objects', None), cls._meta['default_manager']):
setattr(
cls,
'objects',
ManagerDescriptor(cls._meta['default_manager'])
)
def _process_fields(cls):
for field in cls._fields.values():
if hasattr(field, 'contribute_to_class'):
field.contribute_to_class(cls)
@total_ordering
[docs]class FileMakerModel(six.with_metaclass(BaseFileMakerModel)):
def __init__(self, fm_obj=None, **kwargs):
self._fields = deepcopy(self._fields)
self._meta = deepcopy(self._meta)
def make_prop(field_name):
def getter(self):
return self._fields[field_name].value
def setter(self, value):
self._fields[field_name].value = value
return property(getter, setter)
for field_name, field in self._fields.items():
field._value = field.default
setattr(self.__class__, field_name, make_prop(field_name))
if fm_obj is not None:
for field_name, field in self._fields.items():
value = deep_getattr(fm_obj, field.fm_attr)
field.value = value
else:
for name, value in kwargs.items():
setattr(self, name, value)
self._fm_obj = fm_obj
super(FileMakerModel, self).__init__()
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
for field in self._fields:
if not field in other._fields \
or not other._fields[field] == self._fields[field]:
return False
return True
def __lt__(self, other):
if not isinstance(other, self.__class__):
raise TypeError('Cannot compare {0} to {1}'
.format(self.__class__, other.__class__))
ordering = self._meta['ordering']
if ordering is None:
raise ValueError('You must specify a field to order by in meta')
if ordering.startswith('-'):
ordering = ordering[1:]
return getattr(self, ordering) > getattr(other, ordering)
return getattr(self, ordering) < getattr(other, ordering)
def get_django_instance(self):
if self._meta['pk_name']:
pk = getattr(self, self._meta['pk_name'], None)
else:
pk = None
manager = self._meta['model']._default_manager
lookup = {self._meta['django_pk_name']: pk}
if pk is not None and manager.filter(**lookup).exists():
obj = manager.get(**lookup)
elif pk is not None:
obj = self._meta['model'](**lookup)
else:
obj = self._meta['model']()
return obj
[docs] def to_django(self, *args, **kwargs):
from filemaker.fields import ModelField, ModelListField
if self._meta.get('model', None) is None:
return
obj = self.get_django_instance()
to_one_rels = []
to_many_rels = []
if self._meta['django_field_map']:
for field, dj_field in self._meta['django_field_map']:
if isinstance(self._fields[field], ModelListField):
to_many_rels.append((dj_field, self._fields[field]))
elif isinstance(self._fields[field], ModelField):
to_one_rels.append((dj_field, self._fields[field]))
else:
setattr(obj, dj_field,
self._fields[field].to_django())
else:
for field, instance in self._fields.items():
if isinstance(instance, ModelListField):
to_many_rels.append((field, instance))
elif isinstance(instance, ModelField):
to_one_rels.append((field, instance))
else:
setattr(obj, field, instance.to_django())
for field_name, field in to_one_rels:
instance = field.to_django()
setattr(obj, field_name, instance)
if kwargs.get('save', True):
obj.save()
for field_name, field in to_many_rels:
instances = field.to_django(save=False)
try:
obj._meta.get_field(field_name)
except FieldDoesNotExist:
# If we're here then this is a reverse relationship
rel_field = None
for model_field in field.model._meta['model']._meta.fields:
if isinstance(model_field, (ForeignKey, ManyToManyField)) \
and model_field.rel.to == obj.__class__:
rel_field = model_field.name
break
if rel_field is not None:
if self._meta['to_many_action'] == 'clear':
field.model._meta['model']._default_manager.filter(
**{rel_field: obj}).delete()
[setattr(instance, rel_field, obj)
for instance in instances]
[instance.save() for instance in instances]
else:
# This looks like a m2m on the obj
manager = getattr(obj, field_name)
if self._meta['to_many_action'] == 'clear':
manager.clear()
[instance.save() for instance in instances]
manager.add(*instances)
return obj
[docs] def to_dict(self, *args, **kwargs):
from filemaker.fields import ModelField, ModelListField
field_dict = {}
for field, instance in self._fields.items():
if isinstance(instance, ModelListField):
field_dict[field] = \
[i.to_dict() for i in instance.value]
elif isinstance(instance, ModelField):
field_dict[field] = instance.value.to_dict()
else:
field_dict[field] = instance.value
return field_dict
def deep_getattr(obj, attr):
value = obj
if not hasattr(attr, 'strip') or not attr.strip():
raise ValueError('You must specify an attribute name')
if attr.strip() == '+self':
return value
for sub_attr in attr.split('.'):
try:
value = getattr(value, sub_attr)
except AttributeError:
return None
return value