diff --git a/djeneralize/models.py b/djeneralize/models.py index f03b56c..8c59204 100644 --- a/djeneralize/models.py +++ b/djeneralize/models.py @@ -80,8 +80,8 @@ def __new__(cls, name, bases, attrs): # This must be a direct descendant from the BaseGeneralizationModel. # Prepare the look-up mapping of specializations which the sub- # classes will update: - new_model._meta.specializations = {} new_model._meta.specialization = PATH_SEPERATOR + new_model._meta.specializations = {new_model._meta.specialization: new_model} if specialization is not None: # We need to ensure this is actually None and not just evaluates @@ -103,7 +103,6 @@ def __new__(cls, name, bases, attrs): parent_class = new_model.__base__ - new_model._meta.specializations = {} new_model._generalized_parent = parent_class path_specialization = '%s%s%s' % ( @@ -114,7 +113,8 @@ 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 - + new_model._meta.specializations = {new_model._meta.specialization: new_model} + # 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) @@ -187,14 +187,12 @@ def __init__(self, *args, **kwargs): 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.specialization_type = self.__class__.model_specialization + if ('specialization_type' not in kwargs): + kwargs['specialization_type'] = self.__class__.model_specialization + + super(BaseGeneralizationModel, self).__init__(*args, **kwargs) class Meta: abstract = True @@ -212,13 +210,13 @@ def get_as_specialization(self, final_specialization=True): """ - path = self.specialization_type - - if not final_specialization: + current_path = self.__class__.model_specialization + path = self.specialization_type + + if not final_specialization and not path is current_path: # We need to find the path which is only one-step down from the # current level of specialization. - path = find_next_path_down(self.__class__.model_specialization, - path, PATH_SEPERATOR) + path = find_next_path_down(current_path, path, PATH_SEPERATOR) return self._meta.specializations[path].objects.get(pk=self.pk) @@ -280,4 +278,4 @@ def ensure_specialization_manager(sender, **kwargs): ) specialized_model_prepared.connect(ensure_specialization_manager) -#} \ No newline at end of file +#} diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 462bcb6..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright (c) 2011, 2degrees Limited . -# All Rights Reserved. -# -# This file is part of djeneralize , -# which is subject to the provisions of the BSD at -# . A copy of the -# license should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" -# AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT -# NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST -# INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. -# -############################################################################## \ No newline at end of file diff --git a/tests/fixtures.py b/tests/test_djeneralize/fixtures.py similarity index 96% rename from tests/fixtures.py rename to tests/test_djeneralize/fixtures.py index 826aa10..cbd1cc1 100644 --- a/tests/fixtures.py +++ b/tests/test_djeneralize/fixtures.py @@ -12,8 +12,8 @@ # INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## -from tests.test_djeneralize.producers.models import EcoProducer -from tests.test_djeneralize.fruit.models import Banana +from test_djeneralize.producers.models import EcoProducer +from test_djeneralize.fruit.models import Banana """Fixtures for djeneralize tests""" @@ -129,4 +129,4 @@ class Meta: class EcoMart: name = 'EcoMart' producer = EcoProducerData.BananaProducer - \ No newline at end of file + diff --git a/tests/test_djeneralize/settings.py b/tests/test_djeneralize/settings.py index 8a6ce9d..3f4fc6d 100644 --- a/tests/test_djeneralize/settings.py +++ b/tests/test_djeneralize/settings.py @@ -15,6 +15,9 @@ ############################################################################## from os import path +# add djeneralize to the path +import sys +sys.path.append('../..') def get_and_ensure_exists_path_to_db_file(): """ @@ -31,6 +34,8 @@ def get_and_ensure_exists_path_to_db_file(): return db_path +TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' + # Django settings for test_djeneralize project. DEBUG = True @@ -122,7 +127,8 @@ def get_and_ensure_exists_path_to_db_file(): 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', - 'tests.test_djeneralize.writing', - 'tests.test_djeneralize.fruit', - 'tests.test_djeneralize.producers', + 'test_djeneralize.writing', + 'test_djeneralize.fruit', + 'test_djeneralize.producers', + 'django_nose', ) diff --git a/tests/test_djeneralize/tests/__init__.py b/tests/test_djeneralize/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_base.py b/tests/test_djeneralize/tests/test_base.py similarity index 91% rename from tests/test_base.py rename to tests/test_djeneralize/tests/test_base.py index 5f4f6e5..d523514 100644 --- a/tests/test_base.py +++ b/tests/test_djeneralize/tests/test_base.py @@ -15,9 +15,6 @@ ############################################################################## from itertools import chain -import os -# Ensure that Django knows where the project is: -os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_djeneralize.settings' from django.http import Http404 from fixture.django_testcase import FixtureTestCase @@ -26,8 +23,8 @@ ) from djeneralize.utils import * -from .fixtures import * -from .test_djeneralize.writing.models import * +from test_djeneralize.fixtures import * +from test_djeneralize.writing.models import * class TestMetaclass(object): @@ -37,7 +34,7 @@ class TestMetaclass(object): def test_specializations_general(self): """ The specializations dictionary on the most general case is populated - with all the descdencts direct or otherwise. + with all the descdencts direct or otherwise or the model itself. """ @@ -45,11 +42,13 @@ def test_specializations_general(self): specializations_classes = set(specializations.values()) specializations_keys = set(specializations.keys()) + ok_(WritingImplement in specializations_classes) ok_(Pen in specializations_classes) ok_(Pencil in specializations_classes) ok_(FountainPen in specializations_classes) ok_(BallPointPen in specializations_classes) + ok_(WritingImplement.model_specialization in specializations_keys) ok_(Pen.model_specialization in specializations_keys) ok_(Pencil.model_specialization in specializations_keys) ok_(FountainPen.model_specialization in specializations_keys) @@ -58,8 +57,8 @@ def test_specializations_general(self): def test_sub_specialization(self): """ - Only the child specializations are stored in a specialization of the - general case. + Only the child specializations and the model iself are stored in a specialization of the + most general case. """ @@ -67,17 +66,17 @@ def test_sub_specialization(self): specializations_classes = set(specializations.values()) specializations_keys = set(specializations.keys()) - assert_false(Pen in specializations_classes) assert_false(Pencil in specializations_classes) + ok_(Pen in specializations_classes) ok_(FountainPen in specializations_classes) ok_(BallPointPen in specializations_classes) - assert_false(Pen.model_specialization in specializations_keys) assert_false(Pencil.model_specialization in specializations_keys) + ok_(Pen.model_specialization in specializations_keys) ok_(FountainPen.model_specialization in specializations_keys) ok_(BallPointPen.model_specialization in specializations_keys) - eq_(BallPointPen._meta.specializations, {}) + eq_(BallPointPen._meta.specializations, {BallPointPen.model_specialization: BallPointPen}) def test_path_specialization(self): """ @@ -162,17 +161,6 @@ class TestModelInstance(FixtureTestCase): """Tests for model instances""" datasets = [PenData, PencilData, FountainPenData, BallPointPenData] - - def test_no_matching_specialization_raises_key_error(self): - """ - If get_as_specialization is called on the most specialized instance a - key error will be raised. - - """ - - pencil = Pencil.objects.all()[0] - - assert_raises(KeyError, pencil.get_as_specialization) def test_final_specialization(self): """ @@ -186,8 +174,8 @@ def test_final_specialization(self): montblanc_special = FountainPen.objects.get(name='Mont Blanc') eq_(montblanc_general.get_as_specialization(), montblanc_special) - eq_(montblanc_intermediate.get_as_specialization(), montblanc_special) + eq_(montblanc_special.get_as_specialization(), montblanc_special) def test_direct_specialization(self): """ @@ -275,6 +263,36 @@ def test_all_final(self): model_value = getattr(wi, field_name) eq_(model_value, value) + + def test_all_final_on_model_with_instances(self): + """ + The all() method on the manager and queryset returns final + specializations, on a model that has instances of itself AND specialized instances. + + """ + all_pens = Pen.specializations.all() + + models = set(m.__class__ for m in all_pens) + + assert_false(WritingImplement in models) + ok_(Pen in models) + ok_(FountainPen in models) + ok_(BallPointPen in models) + + + # Ensure that all the objects have the correct fields and values + # specified in their original definition, i.e. they've been + # reconstituted correctly: + for p in all_pens: + dataset = self.datasets[p.name] + + for field_name, value in dataset.__dict__.items(): + if field_name.startswith('_') or field_name == 'ref': + continue + + model_value = getattr(p, field_name) + eq_(model_value, value) + def test_all_direct(self): """ The all() method on the manager and queryset returns direct diff --git a/tests/test_fields.py b/tests/test_djeneralize/tests/test_fields.py similarity index 83% rename from tests/test_fields.py rename to tests/test_djeneralize/tests/test_fields.py index 3d2e645..375f8df 100644 --- a/tests/test_fields.py +++ b/tests/test_djeneralize/tests/test_fields.py @@ -12,17 +12,14 @@ # INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. # ############################################################################## -import os -# Ensure that Django knows where the project is: -os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_djeneralize.settings' from fixture.django_testcase import FixtureTestCase from nose.tools import eq_ -from tests.fixtures import BananaData, EcoProducerData, PenData, ShopData -from tests.test_djeneralize.fruit.models import Banana -from tests.test_djeneralize.producers.models import EcoProducer, Shop -from tests.test_djeneralize.writing.models import WritingImplement +from test_djeneralize.fixtures import BananaData, EcoProducerData, PenData, ShopData +from test_djeneralize.fruit.models import Banana +from test_djeneralize.producers.models import EcoProducer, Shop +from test_djeneralize.writing.models import WritingImplement class TestForeignKey(FixtureTestCase): diff --git a/tests/test_integration.py b/tests/test_djeneralize/tests/test_integration.py similarity index 98% rename from tests/test_integration.py rename to tests/test_djeneralize/tests/test_integration.py index eaaa6d8..a9a14d9 100644 --- a/tests/test_integration.py +++ b/tests/test_djeneralize/tests/test_integration.py @@ -16,10 +16,6 @@ """Integration tests to confirm nothing Django-related is broken""" -import os -# Ensure that Django knows where the project is: -os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_djeneralize.settings' - from django.db.models import Avg, F, Q from django.db.models.query import ValuesListQuerySet, ValuesQuerySet from fixture.django_testcase import FixtureTestCase @@ -29,8 +25,8 @@ from djeneralize.models import BaseGeneralizationModel from djeneralize.utils import * -from .fixtures import * -from .test_djeneralize.writing.models import * +from test_djeneralize.fixtures import * +from test_djeneralize.writing.models import * class TestManager(FixtureTestCase): @@ -276,4 +272,4 @@ def test_complex_query(self): q_small | q_large ) - self._check_attributes(normal_objects, specialized_objects) \ No newline at end of file + self._check_attributes(normal_objects, specialized_objects) diff --git a/tests/test_managers.py b/tests/test_djeneralize/tests/test_managers.py similarity index 92% rename from tests/test_managers.py rename to tests/test_djeneralize/tests/test_managers.py index 6ce6f15..d665176 100644 --- a/tests/test_managers.py +++ b/tests/test_djeneralize/tests/test_managers.py @@ -1,9 +1,5 @@ """Test suite to ensure that correct copying of managers""" -import os -# Ensure that Django knows where the project is: -os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_djeneralize.settings' - from django.db.models.manager import Manager from fixture.django_testcase import FixtureTestCase from nose.tools import ( @@ -11,7 +7,7 @@ ) from djeneralize.manager import SpecializationManager -from .test_djeneralize.fruit.models import * +from test_djeneralize.fruit.models import * class TestManager(object): @@ -73,4 +69,4 @@ def test_specialized_not_inherit_specialized_managers(self): eq_(Apple.specializations.__class__, SpecializationManager) eq_(Banana.specializations.__class__, SpecializationManager) - \ No newline at end of file +