from django.db.models import Model, CharField
from django.db.models.base import ModelBase
from roles import RoleType
class ModelBaseRoleType(ModelBase, RoleType):
"""
As every Django model which inherits from django.db.models.Model already has
ModelBase as its __metaclass__, applying a RoleType to it would causes a
conflict because a Python class can apply only 1 metaclass.
In order to resolve the conflict, a new type named ModelBaseRoleType is
created.
ModelBaseRoleType inherits from both Django's ModelBase and Roles' RoleType
>>> from django.db.models import Model
>>> from roles import RoleType, clone
>>> john = Person()
>>> john.name = 'John'
>>> john.save() # no problem saving John
>>> john.id
1
>>> #----------------------------------------
>>> #--- 1st strategy, using clone method ---
>>> #----------------------------------------
>>> jack = Person()
>>> jack.name = 'Jack'
>>> carpenter_jack = Carpenter(jack, method=clone)
>>> carpenter_jack
<Person+Carpenter: Person+Carpenter object>
>>> carpenter_jack.chop()
'chop, chop'
>>> jack.save()
>>> jack.id
2
>>> #----------------------------------------
>>> #--- 2nd strategy, using Role's revoke --
>>> #----------------------------------------
>>> jack = Person()
>>> jack.name = 'Jack'
>>> Carpenter(jack)
<Person+Carpenter: Person+Carpenter object>
>>> jack.chop()
'chop, chop'
>>> Carpenter.revoke(jack)
<Person: Person object>
>>> jack.save()
>>> jack.id
3
"""
pass
class Person(Model):
name = CharField(max_length=20)
#def __init__(self, name='', *args, **kwargs):
# super(Person, self).__init__(args, kwargs)
# self.name = name
class Carpenter(object):
__metaclass__ = ModelBaseRoleType
def chop(self):
return 'chop, chop'
from carpenter.models import Person, Carpenter
from roles import RoleType, clone
from django.test import TestCase
from django.db import DatabaseError
class TestCarpenter(TestCase):
def test_assign_role(self):
"""
Role Carpenter is being assigned to a person
"""
p = Person('juacompe')
Carpenter(p)
def test_save_error(self):
"""
As applying a role to an object changes object type's name, resulting
in DatabaseError when trying to save the object, the applied object
must exits all roles before it can call save().
There are 2 strategies provided by roles library: using clone when
applying roles and revoke all role before calling save.
"""
jack = Person()
jack.name = 'Jack'
Carpenter(jack)
self.failUnless(isinstance(jack, Person))
self.failUnless(isinstance(jack, Carpenter))
jack.chop()
self.assertRaises(AttributeError, jack.save)
def test_save_using_clone(self):
"""
1st strategy, using clone method
"""
jack = Person()
jack.name = 'Jack'
# using clone when applying a role creates another object that uses
# the same __dict__ as original
carpenter_jack = Carpenter(jack, method=clone)
self.failUnless(isinstance(carpenter_jack, Person))
self.failUnless(isinstance(carpenter_jack, Carpenter))
carpenter_jack.chop()
carpenter_jack.name = 'Jack the giant killer'
# nonetheless carpenter_jack cannot save
self.assertRaises(AttributeError, carpenter_jack.save)
# jack still can
jack.save()
self.assertEquals(1, jack.id)
# changes applied to carpenter_jack affects jack
self.assertEquals('Jack the giant killer', jack.name)
def test_save_using_revoke(self):
"""
2nd strategy, using Role's revoke
"""
jack = Person()
jack.name = 'Jack'
Carpenter(jack)
self.failUnless(isinstance(jack, Person))
self.failUnless(isinstance(jack, Carpenter))
jack.chop()
jack.name = 'Jack the giant killer'
# jack with Carpenter role cannot save
self.assertRaises(AttributeError, jack.save)
# revoke jack first
Carpenter.revoke(jack)
# then he can save
jack.save()
self.assertEquals(1, jack.id)
self.assertEquals('Jack the giant killer', jack.name)Any suggestions how can I improve the solution? Any comments or suggestions would be highly appreciated.
On 21 Nov 2010, at 19:12, Chokchai Phatharamalai wrote:
> Dear all,
>
> I managed to get the Roles 0.8 working with Django 1.2.3. I just want to share how I did it and hope to get some advises on how to improve the solution.
>
> When I first tried to apply Roles in a Django project, I found a __metaclass__ conflict. After spent a day reading 2 parts of "Metaclass Programming in Python", I managed to fixed the conflict by creating another metaclass (named ModelBaseRoleType in the sample code) which inherits from both django.db.models.base.ModelBase and roles.RoleType. Then I created a Carpenter role using ModelBaseRoleType as its __metaclass__ (instead of roles.RoleType).
This is what I recently tried and it seemed to work. This is the default way to fix a metaclass conflict.
> The conflict is solved; however, I found that a model cannot be saved after applied a role as its class name has been changed and Django seems to inspect that class name in order to find which database to save the object to.
Okay. So that's the caveat. :)
> So I avoid that by either applying a role using roles.clone as "method" parameter or revoking the applied role from the object before calling save.
In my example, the saving did not happen when the role is applied, hence I did not experience this problem. Personally I'm not a big fan of using the clone method, since it only clones the instance dict. Hence stuff like properties (and Django is all about properties in the model) do not work properly. I'd suggest revoking the roles before saving (which is simple if you use the in a 'with' block).
See: https://github.com/amolenaar/roles/blob/django/django_dci/account/tests.py (line 43-55).
> [snip code blocks]
> Any suggestions how can I improve the solution? Any comments or suggestions would be highly appreciated.
I'd settle for the revoke() method. If this poses to be a problem we can do something about the class name generation ;).
> references:
> - Metaclass Programming in Python Part 1, http://gnosis.cx/publish/programming/metaclass_1.html
> - Metaclass Programming in Python Part 2, http://gnosis.cx/publish/programming/metaclass_2.html
> - Roles 0.8, http://pypi.python.org/pypi/roles
> - Django 1.2.3, http://pypi.python.org/pypi/Django/1.2.3
Kind regards,
Arjan
--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To post to this group, send email to object-co...@googlegroups.com.
To unsubscribe from this group, send email to object-composit...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/object-composition?hl=en.
I updated the code and pushed it to github.