Adds to Melange a tags framework based on taggable-mixin.
The taggable-mixin allowed only tag per Datastore model. This is extended
framework allows any arbitrary number of tags per Datastore model. Also,
now one can define different models for different Tag types which are all
inherited from the base Tag model provided by taggable-mixin.
The GHOPTask model makes use of 2 tags per model, one for difficulty and the
other for task_type, both using the tags framework.
Reviewed by: Paweł Sołyga
"""
Convenience routines for creating non-trivial Field subclasses.
Add SubfieldBase as the __metaclass__ for your Field subclass, implement
to_python() and the other necessary methods and everything will work seamlessly.
"""
class SubfieldBase(type):
"""
A metaclass for custom Field subclasses. This ensures the model's attribute
has the descriptor protocol attached to it.
"""
def __new__(cls, base, name, attrs):
new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs)
new_class.contribute_to_class = make_contrib(
attrs.get('contribute_to_class'))
return new_class
class Creator(object):
"""
A placeholder class that provides a way to set the attribute on the model.
"""
def __init__(self, field):
self.field = field
def __get__(self, obj, type=None):
if obj is None:
raise AttributeError('Can only be accessed via an instance.')
return obj.__dict__[self.field.name]
def __set__(self, obj, value):
obj.__dict__[self.field.name] = self.field.to_python(value)
def make_contrib(func=None):
"""
Returns a suitable contribute_to_class() method for the Field subclass.
If 'func' is passed in, it is the existing contribute_to_class() method on
the subclass and it is called before anything else. It is assumed in this
case that the existing contribute_to_class() calls all the necessary
superclass methods.
"""
def contribute_to_class(self, cls, name):
if func:
func(self, cls, name)
else:
super(self.__class__, self).contribute_to_class(cls, name)
setattr(cls, self.name, Creator(self))
return contribute_to_class