Skip to content

Commit

Permalink
IMPORTANT: import path changed, it's now: "from polymorphic import Po…
Browse files Browse the repository at this point in the history
…lymorphicModel, ..."

- added python2.4 compatibility. Contributed by Charles Leifer. Thanks!
- general reorganization of the code - there is no single polymorphic.py module anymore,
  so d-p now needs to be installed as a regular Django app
- polymorphic.VERSION/get_version added
- version numbering started: V0.5 beta
  • Loading branch information
bconstantin committed Feb 22, 2010
1 parent e6c1e7e commit 116e2af
Show file tree
Hide file tree
Showing 17 changed files with 1,010 additions and 761 deletions.
4 changes: 2 additions & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
django_polymorphic was created by Bert Constantin in 2009/2010.

setup.py contributed by Andrew Ingram

Andrew Ingram contributed setup.py
Charles Leifer contributed Python 2.4 compatibility
16 changes: 12 additions & 4 deletions DOCS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Testing
-------

The repository (or tar file) contains a complete Django project
that may be used for tests or experiments (without any installation needed).
that may be used for tests or experiments, without any installation needed.

To run the included test suite, execute::

Expand All @@ -36,21 +36,21 @@ Alternatively you can simply copy the ``polymorphic`` directory
(under "django_polymorphic") into your Django project dir.

If you want to use the management command ``polymorphic_dumpdata``, then
you need to add ``polymorphic`` to your INSTALLED_APPS setting.
you need to add ``polymorphic`` to your INSTALLED_APPS setting. This is also
needed if you want to run the test cases in `polymorphic/tests.py`.

In any case, Django's ContentType framework (``django.contrib.contenttypes``)
needs to be listed in INSTALLED_APPS (usually it already is).



Defining Polymorphic Models
===========================

To make models polymorphic, use ``PolymorphicModel`` instead of Django's
``models.Model`` as the superclass of your base model. All models
inheriting from your base class will be polymorphic as well::

from polymorphic.models import PolymorphicModel
from polymorphic import PolymorphicModel

class ModelA(PolymorphicModel):
field1 = models.CharField(max_length=10)
Expand Down Expand Up @@ -244,6 +244,8 @@ from ``PolymorphicManager`` instead of ``models.Manager``. In your model
class, explicitly add the default manager first, and then your
custom manager::

from polymorphic import PolymorphicModel, PolymorphicManager

class MyOrderedManager(PolymorphicManager):
def get_query_set(self):
return super(MyOrderedManager,self).get_query_set().order_by('some_field')
Expand Down Expand Up @@ -282,6 +284,8 @@ The ``PolymorphicManager`` class accepts one initialization argument,
which is the queryset class the manager should use. A custom
custom queryset class can be defined and used like this::

from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet

class MyQuerySet(PolymorphicQuerySet):
def my_queryset_method(...):
...
Expand Down Expand Up @@ -409,6 +413,10 @@ Restrictions & Caveats
by subclassing it instead of modifying Django core (as we do here
with PolymorphicModel).

* Django Admin Integration: There currently is no admin integration,
but it surely would be nice to have one. There is a discussion about it here:
http://groups.google.de/group/django-polymorphic/browse_thread/thread/84290fe76c40c12d

* It must be possible to instantiate the base model objects, even if your
application never does this itself. This is needed by the current
implementation of polymorphic querysets but (likely) also by Django internals.
Expand Down
28 changes: 26 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Release Notes, Usage, Code
* Please see `here for release notes, news and discussion`_ (Google Group)
* `Many Examples`_, or full `Installation and Usage Docs`_ (or the short `Overview`_)
* Download from GitHub_ or Bitbucket_, or as TGZ_ or ZIP_
* Improve django_polymorphic: Report issues, discuss, post patch, or fork the code (GitHub_, Bitbucket_, Newsgroup_, Mail_)
* Improve django_polymorphic: Report issues, discuss, post patch, or fork (GitHub_, Bitbucket_, Group_, Mail_)

.. _here for release notes, news and discussion: http://groups.google.de/group/django-polymorphic/topics
.. _Newsgroup: http://groups.google.de/group/django-polymorphic/topics
.. _Group: http://groups.google.de/group/django-polymorphic/topics
.. _Mail: http://github.com/bconstantin/django_polymorphic/tree/master/setup.py
.. _Installation and Usage Docs: http://bserve.webhop.org/wiki/django_polymorphic/doc
.. _Many Examples: http://bserve.webhop.org/wiki/django_polymorphic/doc#defining-polymorphic-models
Expand Down Expand Up @@ -72,6 +72,30 @@ License
django_polymorphic uses the same license as Django (BSD-like).


API Change on February 22, plus Installation Note
-------------------------------------------------

The django_polymorphic source code has been restructured
and as a result needs to be installed like a normal Django App
- either via copying the "polymorphic" directory into your
Django project or by running setup.py. Adding 'polymorphic'
to INSTALLED_APPS in settings.py is still optional, however.

The file `polymorphic.py` cannot be used as a standalone
extension module anymore (as is has been split into a number
of smaller files).

Importing works slightly different now: All relevant symbols are
imported directly from 'polymorphic' instead from
'polymorphic.models'::

# new way
from polymorphic import PolymorphicModel, ...

# old way, doesn't work anymore
from polymorphic.models import PolymorphicModel, ...


Database Schema Change on January 26
------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
project_path = os.path.dirname(os.path.abspath(__file__))
libs_local_path = os.path.join(project_path, 'libraries-local')
if libs_local_path not in sys.path: sys.path.insert(1, libs_local_path)

sys.stderr.write( 'using Python version: %s\n' % sys.version[:5])

import django
sys.stderr.write( 'using Django version: %s, from %s\n' % (
django.get_version(),
Expand Down
4 changes: 3 additions & 1 deletion pexp/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-

from django.db import models

from polymorphic.models import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet, ShowFields, ShowFieldsAndTypes
from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet, ShowFields, ShowFieldsAndTypes


class Project(ShowFields, PolymorphicModel):
Expand Down
24 changes: 24 additions & 0 deletions polymorphic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
"""
Seamless Polymorphic Inheritance for Django Models
Copyright:
This code and affiliated files are (C) by Bert Constantin and individual contributors.
Please see LICENSE and AUTHORS for more information.
"""

from polymorphic_model import PolymorphicModel
from manager import PolymorphicManager
from query import PolymorphicQuerySet
from showfields import ShowFields, ShowFieldsAndTypes


VERSION = (0, 5, 0, 'beta')

def get_version():
version = '%s.%s' % VERSION[0:2]
if VERSION[2]:
version += '.%s' % VERSION[2]
if VERSION[3]:
version += ' %s' % VERSION[3]
return version
152 changes: 152 additions & 0 deletions polymorphic/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
""" PolymorphicModel Meta Class
Please see README.rst or DOCS.rst or http://bserve.webhop.org/wiki/django_polymorphic
"""

from django.db import models
from django.db.models.base import ModelBase

from manager import PolymorphicManager
from query import PolymorphicQuerySet

# PolymorphicQuerySet Q objects (and filter()) support these additional key words.
# These are forbidden as field names (a descriptive exception is raised)
POLYMORPHIC_SPECIAL_Q_KWORDS = [ 'instance_of', 'not_instance_of']


###################################################################################
### PolymorphicModel meta class

class PolymorphicModelBase(ModelBase):
"""
Manager inheritance is a pretty complex topic which may need
more thought regarding how this should be handled for polymorphic
models.
In any case, we probably should propagate 'objects' and 'base_objects'
from PolymorphicModel to every subclass. We also want to somehow
inherit/propagate _default_manager as well, as it needs to be polymorphic.
The current implementation below is an experiment to solve this
problem with a very simplistic approach: We unconditionally
inherit/propagate any and all managers (using _copy_to_model),
as long as they are defined on polymorphic models
(the others are left alone).
Like Django ModelBase, we special-case _default_manager:
if there are any user-defined managers, it is set to the first of these.
We also require that _default_manager as well as any user defined
polymorphic managers produce querysets that are derived from
PolymorphicQuerySet.
"""

def __new__(self, model_name, bases, attrs):
#print; print '###', model_name, '- bases:', bases

# create new model
new_class = self.call_superclass_new_method(model_name, bases, attrs)

# check if the model fields are all allowed
self.validate_model_fields(new_class)

# create list of all managers to be inherited from the base classes
inherited_managers = new_class.get_inherited_managers(attrs)

# add the managers to the new model
for source_name, mgr_name, manager in inherited_managers:
#print '** add inherited manager from model %s, manager %s, %s' % (source_name, mgr_name, manager.__class__.__name__)
new_manager = manager._copy_to_model(new_class)
new_class.add_to_class(mgr_name, new_manager)

# get first user defined manager; if there is one, make it the _default_manager
user_manager = self.get_first_user_defined_manager(attrs)
if user_manager:
def_mgr = user_manager._copy_to_model(new_class)
#print '## add default manager', type(def_mgr)
new_class.add_to_class('_default_manager', def_mgr)
new_class._default_manager._inherited = False # the default mgr was defined by the user, not inherited

# validate resulting default manager
self.validate_model_manager(new_class._default_manager, model_name, '_default_manager')

return new_class

def get_inherited_managers(self, attrs):
"""
Return list of all managers to be inherited/propagated from the base classes;
use correct mro, only use managers with _inherited==False,
skip managers that are overwritten by the user with same-named class attributes (in attrs)
"""
add_managers = []; add_managers_keys = set()
for base in self.__mro__[1:]:
if not issubclass(base, models.Model): continue
if not getattr(base, 'polymorphic_model_marker', None): continue # leave managers of non-polym. models alone

for key, manager in base.__dict__.items():
if type(manager) == models.manager.ManagerDescriptor: manager = manager.manager
if not isinstance(manager, models.Manager): continue
if key in attrs: continue
if key in add_managers_keys: continue # manager with that name already added, skip
if manager._inherited: continue # inherited managers have no significance, they are just copies
if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers
self.validate_model_manager(manager, self.__name__, key)
add_managers.append((base.__name__, key, manager))
add_managers_keys.add(key)
return add_managers

@classmethod
def get_first_user_defined_manager(self, attrs):
mgr_list = []
for key, val in attrs.items():
if not isinstance(val, models.Manager): continue
mgr_list.append((val.creation_counter, val))
# if there are user defined managers, use first one as _default_manager
if mgr_list: #
_, manager = sorted(mgr_list)[0]
return manager
return None

@classmethod
def call_superclass_new_method(self, model_name, bases, attrs):
"""call __new__ method of super class and return the newly created class.
Also work around a limitation in Django's ModelBase."""
# There seems to be a general limitation in Django's app_label handling
# regarding abstract models (in ModelBase). See issue 1 on github - TODO: propose patch for Django
# We run into this problem if polymorphic.py is located in a top-level directory
# which is directly in the python path. To work around this we temporarily set
# app_label here for PolymorphicModel.
meta = attrs.get('Meta', None)
model_module_name = attrs['__module__']
do_app_label_workaround = (meta
and model_module_name == 'polymorphic'
and model_name == 'PolymorphicModel'
and getattr(meta, 'app_label', None) is None )

if do_app_label_workaround: meta.app_label = 'poly_dummy_app_label'
new_class = super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs)
if do_app_label_workaround: del(meta.app_label)
return new_class

def validate_model_fields(self):
"check if all fields names are allowed (i.e. not in POLYMORPHIC_SPECIAL_Q_KWORDS)"
for f in self._meta.fields:
if f.name in POLYMORPHIC_SPECIAL_Q_KWORDS:
e = 'PolymorphicModel: "%s" - field name "%s" is not allowed in polymorphic models'
raise AssertionError(e % (self.__name__, f.name) )

@classmethod
def validate_model_manager(self, manager, model_name, manager_name):
"""check if the manager is derived from PolymorphicManager
and its querysets from PolymorphicQuerySet - throw AssertionError if not"""

if not issubclass(type(manager), PolymorphicManager):
e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" manager is of type "' + type(manager).__name__
e += '", but must be a subclass of PolymorphicManager'
raise AssertionError(e)
if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet):
e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" (PolymorphicManager) has been instantiated with a queryset class which is'
e += ' not a subclass of PolymorphicQuerySet (which is required)'
raise AssertionError(e)
return manager

85 changes: 85 additions & 0 deletions polymorphic/compatibility_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
"""
Compatibility layer for Python 2.4
==================================
Currently implements:
+ collections.defaultdict
+ compat_partition (compatibility replacement for str.partition)
"""
try:
assert False
from collections import defaultdict
except:
class defaultdict(dict):
def __init__(self, default_factory=None, *a, **kw):
if (default_factory is not None and
not hasattr(default_factory, '__call__')):
raise TypeError('first argument must be callable')
dict.__init__(self, *a, **kw)
self.default_factory = default_factory
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
return self.__missing__(key)
def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
self[key] = value = self.default_factory()
return value
def __reduce__(self):
if self.default_factory is None:
args = tuple()
else:
args = self.default_factory,
return type(self), args, None, None, self.items()
def copy(self):
return self.__copy__()
def __copy__(self):
return type(self)(self.default_factory, self)
def __deepcopy__(self, memo):
import copy
return type(self)(self.default_factory,
copy.deepcopy(self.items()))
def __repr__(self):
return 'defaultdict(%s, %s)' % (self.default_factory, dict.__repr__(self))


if getattr(str,'partition',None):
def compat_partition(s,sep): return s.partition(sep)
else:
""" from:
http://mail.python.org/pipermail/python-dev/2005-September/055962.html
"""
def compat_partition(s, sep, at_sep=1):
""" Returns a three element tuple, (head, sep, tail) where:
head + sep + tail == s
sep == '' or sep is t
bool(sep) == (t in s) # sep indicates if the string was found
"""
if not isinstance(sep, basestring) or not sep:
raise ValueError('partititon argument must be a non-empty string')
if at_sep == 0:
result = ('', '', s)
else:
if at_sep > 0:
parts = s.split(sep, at_sep)
if len(parts) <= at_sep:
result = (s, '', '')
else:
result = (sep.join(parts[:at_sep]), sep, parts[at_sep])
else:
parts = s.rsplit(sep, at_sep)
if len(parts) <= at_sep:
result = ('', '', s)
else:
result = (parts[0], sep, sep.join(parts[1:]))
assert len(result) == 3
assert ''.join(result) == s
assert result[1] == '' or result[1] is sep
return result

Loading

0 comments on commit 116e2af

Please sign in to comment.