@@ -0,0 +1,1562 @@
+# Copyright (C) 2016 Google Inc., authors, and contributors %3Csee AUTHORS file%3E%0A# Licensed under http://www.apache.org/licenses/LICENSE-2.0 %3Csee LICENSE file%3E%0A# Created By: urban@reciprocitylabs.com%0A# Maintained By: urban@reciprocitylabs.com%0A%0A%22%22%22A mixin for objects that can be cloned%22%22%22%0A%0Afrom ggrc.services import common%0A%0A%0Aclass Clonable(object):%0A  %22%22%22Clonable mixin%22%22%22%0A%0A  __lazy_init__ = True%0A%0A  CLONEABLE_CHILDREN = %7B%7D%0A%0A  _operation_data = %7B%7D%0A%0A  @classmethod%0A  def init(cls, model):%0A    cls.set_handlers(model)%0A%0A  @classmethod%0A  def set_handlers(cls, model):%0A    @common.Resource.model_put_after_commit.connect_via(model)%0A    def handle_model_clone(sender, obj=None, src=None, service=None):%0A      # pylint: disable=unused-argument, unused-variable%0A      if src.get(%22operation%22, %22%22) == u%22clone%22:%0A        obj.clone(children=src.get(%22associatedObjects%22, %5B%5D))%0A%0A  def generate_attribute(self, attribute):%0A    %22%22%22Generate a new unique attribute as a copy of original%22%22%22%0A    attr = getattr(self, attribute)%0A%0A    def count_values(key, value):%0A      return self.query.filter_by(**%7Bkey: value%7D).count()%0A%0A    i = 1%0A    generated_attr_value = %22%7B0%7D - copy %7B1%7D%22.format(attr, i)%0A    while count_values(attribute, generated_attr_value):%0A      i += 1%0A      generated_attr_value = %22%7B0%7D - copy %7B1%7D%22.format(attr, i)%0A    return generated_attr_value%0A%0A  def clone_custom_attribute_values(self, obj):%0A    %22%22%22Copy object's custom attribute values%22%22%22%0A    ca_values = self.custom_attribute_values%0A%0A    for value in ca_values:%0A      value._clone(obj)  # pylint: disable=protected-access%0A

Add Clonable mixin