Skip to content
This repository was archived by the owner on Feb 27, 2020. It is now read-only.
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 49 additions & 49 deletions djeneralize/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import re

from django.db.models.base import ModelBase, Model
from django.db.models.fields import FieldDoesNotExist, TextField
from django.db.models.fields import FieldDoesNotExist, CharField
from django.dispatch import Signal

from djeneralize import PATH_SEPERATOR
Expand All @@ -36,37 +36,37 @@
#}

# { Metaclass:

class BaseGeneralizationMeta(ModelBase):
"""The metaclass for BaseGeneralizedModel"""

def __new__(cls, name, bases, attrs):
super_new = super(BaseGeneralizationMeta, cls).__new__
parents = [b for b in bases if isinstance(b, BaseGeneralizationMeta)]

# Get the declared Meta inner class before the super-metaclass removes
# it:
meta = attrs.get('Meta')

# We must remove the specialization declarations from the Meta inner
# class since ModelBase will raise a TypeError is it encounters these:
if meta:
specialization = meta.__dict__.pop('specialization', None)
else:
specialization = None
specialization = None

#if name == "Fruit": import pdb; pdb.set_trace()
new_model = super_new(cls, name, bases, attrs)

# Ensure that the _meta attribute has some additional attributes:
if not hasattr(new_model._meta, 'abstract_specialization_managers'):
new_model._meta.abstract_specialization_managers = []
if not hasattr(new_model._meta, 'concrete_specialization_managers'):
new_model._meta.concrete_specialization_managers = []

if not parents:
return new_model

if new_model._meta.abstract:
# This is an abstract base-class and no specializations should be
# declared on the inner class:
Expand All @@ -83,30 +83,30 @@ def __new__(cls, name, bases, attrs):
# classes will update:
new_model._meta.specializations = {}
new_model._meta.specialization = PATH_SEPERATOR

if specialization is not None:
# We need to ensure this is actually None and not just evaluates
# to False as we enforce that it's not declared:
raise TypeError(
"General models should not have a specialization declared "
"on their inner Meta class"
)

else:
if specialization is None:
raise TypeError(
"Specialized models must declare specialization on their "
"inner Meta class"
)

if not SPECIALIZATION_RE.match(specialization):
raise ValueError("Specializations must be alphanumeric string")

parent_class = new_model.__base__

new_model._meta.specializations = {}
new_model._generalized_parent = parent_class

path_specialization = '%s%s%s' % (
parent_class._meta.specialization, specialization,
PATH_SEPERATOR
Expand All @@ -115,7 +115,7 @@ def __new__(cls, name, bases, attrs):
# Calculate the specialization as a path taking into account the
# specialization of any ancestors:
new_model._meta.specialization = path_specialization

# Update the specializations mapping on the General model so that it
# knows to use this class for that specialization:
ancestor = getattr(new_model, '_generalized_parent', None)
Expand All @@ -124,11 +124,11 @@ def __new__(cls, name, bases, attrs):
new_model._meta.specialization
] = new_model
ancestor = getattr(ancestor, '_generalized_parent', None)

parent_class._meta.specializations[path_specialization] = new_model

is_proxy = new_model._meta.proxy

if getattr(new_model, '_default_specialization_manager', None):
if not is_proxy:
new_model._default_specialization_manager = None
Expand All @@ -142,13 +142,13 @@ def __new__(cls, name, bases, attrs):
new_model._base_specialization_manager._copy_to_model(
new_model
)

for obj_name, obj in attrs.items():
# We need to do this to ensure that a declared SpecializationManager
# will be correctly set-up:
if isinstance(obj, SpecializationManager):
new_model.add_to_class(obj_name, obj)

for base in parents:
# Inherit managers from the abstract base classes.
new_model.copy_managers(
Expand All @@ -161,9 +161,9 @@ def __new__(cls, name, bases, attrs):
new_model.copy_managers(
base._meta.concrete_specialization_managers
)

specialized_model_prepared.send(sender=new_model)

return new_model

#}
Expand All @@ -172,87 +172,87 @@ def __new__(cls, name, bases, attrs):

class BaseGeneralizationModel(Model):
"""Base model from which all Generalized and Specialized models inherit"""


__metaclass__ = BaseGeneralizationMeta
specialization_type = TextField(db_index=True)

specialization_type = CharField(db_index=True, max_length=255)
"""Field to store the specialization"""

def __init__(self, *args, **kwargs):
"""
If specialization_type is not set in kwargs, add this is the most
specialized model, set specialization_type to match the specialization
declared in Meta

"""

super(BaseGeneralizationModel, self).__init__(*args, **kwargs)

# If we have a final specialization, and a specialization_type is not
# specified in kwargs, set it to the default for this model:
if ('specialization_type' not in kwargs and
not self._meta.specializations):
self.set_default_specialization()

class Meta:
abstract = True

def get_as_specialization(self, final_specialization=True):
"""
Get the specialized model instance which corresponds to the general
case.

:param final_specialization: Whether the specialization returned is
the most specialized specialization or whether the direct
specialization is used
:type final_specialization: :class:`bool`
:return: The specialized model corresponding to the current model

"""

path = self.specialization_type

if not final_specialization:
# We need to find the path which is only one-step down from the
# current level of specialization.
path = find_next_path_down(
self._meta.specialization, path, PATH_SEPERATOR
)

return self._meta.specializations[path].objects.get(
pk=self.pk
)

def set_default_specialization(self):
"""
Set the ``specialization_type`` of this instance to match that specified
by its specialization in the inner class Meta.

"""
self.specialization_type = self._meta.specialization

self.specialization_type = self._meta.specialization

#}

# { Signal handler
# { Signal handler

def ensure_specialization_manager(sender, **kwargs):
"""
Ensure that a BaseGeneralizationModel subclass contains a default
specialization manager and sets the ``_default_specialization_manager``
attribute on the class.

:param sender: The new specialized model
:type sender: :class:`BaseGeneralizationModel`

"""

cls = sender

if cls._meta.abstract:
return

if not getattr(cls, '_default_specialization_manager', None):
# Create the default specialization manager, if needed.
try:
Expand Down Expand Up @@ -291,4 +291,4 @@ def ensure_specialization_manager(sender, **kwargs):
)

specialized_model_prepared.connect(ensure_specialization_manager)
#}
#}