Esempio n. 1
0
 def __init__(self, manager_slug, revision_context_manager=revision_context_manager):
     """Initializes the revision manager."""
     # Check the slug is unique for this revision manager.
     if manager_slug in RevisionManager._created_managers:
         raise RegistrationError("A revision manager has already been created with the slug %r" % manager_slug)
     # Store a reference to this manager.
     self.__class__._created_managers[manager_slug] = self
     # Store config params.
     self._manager_slug = manager_slug
     self._registered_models = {}
     self._revision_context_manager = revision_context_manager
     # Proxies to common context methods.
     self._revision_context = revision_context_manager.create_revision()
     self.create_on_success = deprecated("@revision.create_on_success", "@reversion.create_revision")(self._revision_context)
     self.add_meta = deprecated("revision.add_meta()", "reversion.add_meta()")(revision_context_manager.add_meta)
Esempio n. 2
0
 def __init__(self,
              manager_slug,
              revision_context_manager=revision_context_manager):
     """Initializes the revision manager."""
     # Check the slug is unique for this revision manager.
     if manager_slug in RevisionManager._created_managers:
         raise RegistrationError(
             "A revision manager has already been created with the slug %r"
             % manager_slug)
     # Store a reference to this manager.
     self.__class__._created_managers[manager_slug] = self
     # Store config params.
     self._manager_slug = manager_slug
     self._registered_models = {}
     self._revision_context_manager = revision_context_manager
     # Proxies to common context methods.
     self._revision_context = revision_context_manager.create_revision()
     self.create_on_success = deprecated("@revision.create_on_success",
                                         "@reversion.create_revision")(
                                             self._revision_context)
     self.add_meta = deprecated("revision.add_meta()",
                                "reversion.add_meta()")(
                                    revision_context_manager.add_meta)
Esempio n. 3
0
class RevisionManager(object):
    """Manages the configuration and creation of revisions."""

    _created_managers = WeakValueDictionary()

    @classmethod
    def get_created_managers(cls):
        """Returns all created revision managers."""
        return list(cls._created_managers.items())

    @classmethod
    def get_manager(cls, manager_slug):
        """Returns the manager with the given slug."""
        if manager_slug in cls._created_managers:
            return cls._created_managers[manager_slug]
        raise RegistrationError("No revision manager exists with the slug %r" %
                                manager_slug)

    def __init__(self,
                 manager_slug,
                 revision_context_manager=revision_context_manager):
        """Initializes the revision manager."""
        # Check the slug is unique for this revision manager.
        if manager_slug in RevisionManager._created_managers:
            raise RegistrationError(
                "A revision manager has already been created with the slug %r"
                % manager_slug)
        # Store a reference to this manager.
        self.__class__._created_managers[manager_slug] = self
        # Store config params.
        self._manager_slug = manager_slug
        self._registered_models = {}
        self._revision_context_manager = revision_context_manager
        # Proxies to common context methods.
        self._revision_context = revision_context_manager.create_revision()
        self.create_on_success = deprecated("@revision.create_on_success",
                                            "@reversion.create_revision")(
                                                self._revision_context)
        self.add_meta = deprecated("revision.add_meta()",
                                   "reversion.add_meta()")(
                                       revision_context_manager.add_meta)

    # Registration methods.

    def is_registered(self, model):
        """
        Checks whether the given model has been registered with this revision
        manager.
        """
        return model in self._registered_models

    def get_registered_models(self):
        """Returns an iterable of all registered models."""
        return self._registered_models.keys()

    def register(self, model, adapter_cls=VersionAdapter, **field_overrides):
        """Registers a model with this revision manager."""
        # Prevent multiple registration.
        if self.is_registered(model):
            raise RegistrationError, "%r has already been registered with django-reversion" % model
        # Prevent proxy models being registered.
        if model._meta.proxy:
            raise RegistrationError(
                "Proxy models cannot be used with django-reversion, register the parent class instead"
                % model)
        # Perform any customization.
        if field_overrides:
            adapter_cls = type("Custom" + adapter_cls.__name__,
                               (adapter_cls, ), field_overrides)
        # Perform the registration.
        adapter_obj = adapter_cls(model)
        self._registered_models[model] = adapter_obj
        # Connect to the post save signal of the model.
        post_save.connect(self._post_save_receiver, model)
        pre_delete.connect(self._pre_delete_receiver, model)

    def get_adapter(self, model):
        """Returns the registration information for the given model class."""
        if self.is_registered(model):
            return self._registered_models[model]
        raise RegistrationError, "%r has not been registered with django-reversion" % model

    def unregister(self, model):
        """Removes a model from version control."""
        if not self.is_registered(model):
            raise RegistrationError, "%r has not been registered with django-reversion" % model
        del self._registered_models[model]
        post_save.disconnect(self._post_save_receiver, model)
        pre_delete.disconnect(self._pre_delete_receiver, model)

    def _follow_relationships(self, objects):
        """Follows all relationships in the given set of objects."""
        followed = set()

        def _follow(obj):
            if obj in followed or obj.pk is None:
                return
            followed.add(obj)
            adapter = self.get_adapter(obj.__class__)
            for related in adapter.get_followed_relations(obj):
                _follow(related)

        for obj in objects:
            _follow(obj)
        return followed

    def _get_versions(self):
        """Returns all versions that apply to this manager."""
        return Version.objects.filter(
            revision__manager_slug=self._manager_slug, ).select_related(
                "revision")

    def save_revision(self,
                      objects,
                      ignore_duplicates=False,
                      user=None,
                      comment="",
                      meta=()):
        """Saves a new revision."""
        # Adapt the objects to a dict.
        if isinstance(objects, (list, tuple)):
            objects = dict(
                (obj, self.get_adapter(obj.__class__).get_version_data(
                    obj, VERSION_CHANGE)) for obj in objects)
        # Create the revision.
        if objects:
            # Follow relationships.
            for obj in self._follow_relationships(objects.iterkeys()):
                if not obj in objects:
                    adapter = self.get_adapter(obj.__class__)
                    objects[obj] = adapter.get_version_data(
                        obj, VERSION_CHANGE)
            # Create all the versions without saving them
            new_versions = [
                Version(**version_data)
                for obj, version_data in objects.iteritems()
            ]
            # Check if there's some change in all the revision's objects.
            save_revision = True
            if ignore_duplicates:
                # Find the latest revision amongst the latest previous version of each object.
                subqueries = [
                    Q(object_id=version.object_id,
                      content_type=version.content_type)
                    for version in new_versions
                ]
                subqueries = reduce(operator.or_, subqueries)
                latest_revision = self._get_versions().filter(
                    subqueries).aggregate(Max("revision"))["revision__max"]
                # If we have a latest revision, compare it to the current revision.
                if latest_revision is not None:
                    previous_versions = self._get_versions().filter(
                        revision=latest_revision).values_list(
                            "serialized_data", flat=True)
                    if len(previous_versions) == len(new_versions):
                        all_serialized_data = [
                            version.serialized_data for version in new_versions
                        ]
                        if sorted(previous_versions) == sorted(
                                all_serialized_data):
                            save_revision = False
            # Only save if we're always saving, or have changes.
            if save_revision:
                # Save a new revision.
                revision = Revision.objects.create(
                    manager_slug=self._manager_slug,
                    user=user,
                    comment=comment,
                )
                # Save version models.
                for version in new_versions:
                    version.revision = revision
                    version.save()
                # Save the meta information.
                for cls, kwargs in meta:
                    cls._default_manager.create(revision=revision, **kwargs)

    # Context management.

    @deprecated("reversion.revision", "reversion.create_revision()")
    def __enter__(self, *args, **kwargs):
        """Enters a revision management block."""
        return self._revision_context.__enter__(*args, **kwargs)

    @deprecated("reversion.revision", "reversion.create_revision()")
    def __exit__(self, *args, **kwargs):
        """Leaves a block of revision management."""
        return self._revision_context.__exit__(*args, **kwargs)

    # Revision meta data.

    user = property(
        deprecated("revision.user", "reversion.get_user()")(
            lambda self: self._revision_context_manager.get_user()),
        deprecated("revision.user", "reversion.set_user()")(
            lambda self, user: self._revision_context_manager.set_user(user)),
    )

    comment = property(
        deprecated("revision.comment", "reversion.get_comment()")(
            lambda self: self._revision_context_manager.get_comment()),
        deprecated("revision.comment", "reversion.set_comment()")(
            lambda self, comment: self._revision_context_manager.set_comment(
                comment)),
    )

    ignore_duplicates = property(
        deprecated("revision.ignore_duplicates",
                   "reversion.get_ignore_duplicates()")
        (lambda self: self._revision_context_manager.get_ignore_duplicates()),
        deprecated("revision.ignore_duplicates",
                   "reversion.set_ignore_duplicates()")
        (lambda self, ignore_duplicates: self._revision_context_manager.
         set_ignore_duplicates(ignore_duplicates)))

    # Revision management API.

    def get_for_object_reference(self, model, object_id):
        """
        Returns all versions for the given object reference.
        
        The results are returned with the most recent versions first.
        """
        content_type = ContentType.objects.get_for_model(model)
        versions = self._get_versions().filter(
            content_type=content_type, ).select_related("revision")
        if has_int_pk(model):
            # We can do this as a fast, indexed lookup.
            object_id_int = int(object_id)
            versions = versions.filter(object_id_int=object_id_int)
        else:
            # We can't do this using an index. Never mind.
            object_id = unicode(object_id)
            versions = versions.filter(object_id=object_id)
        versions = versions.order_by("-pk")
        return versions

    def get_for_object(self, obj):
        """
        Returns all the versions of the given object, ordered by date created.
        
        The results are returned with the most recent versions first.
        """
        return self.get_for_object_reference(obj.__class__, obj.pk)

    def get_unique_for_object(self, obj):
        """
        Returns unique versions associated with the object.
        
        The results are returned with the most recent versions first.
        """
        versions = self.get_for_object(obj)
        changed_versions = []
        last_serialized_data = None
        for version in versions:
            if last_serialized_data != version.serialized_data:
                changed_versions.append(version)
            last_serialized_data = version.serialized_data
        return changed_versions

    def get_for_date(self, object, date):
        """Returns the latest version of an object for the given date."""
        versions = self.get_for_object(object)
        versions = versions.filter(revision__date_created__lte=date)
        try:
            version = versions[0]
        except IndexError:
            raise Version.DoesNotExist
        else:
            return version

    def get_deleted(self, model_class):
        """
        Returns all the deleted versions for the given model class.
        
        The results are returned with the most recent versions first.
        """
        content_type = ContentType.objects.get_for_model(model_class)
        live_pk_queryset = model_class._default_manager.all().values_list(
            "pk", flat=True)
        versioned_objs = self._get_versions().filter(
            content_type=content_type, )
        if has_int_pk(model_class):
            # We can do this as a fast, in-database join.
            deleted_version_pks = versioned_objs.exclude(
                object_id_int__in=live_pk_queryset).values_list(
                    "object_id_int")
        else:
            # This join has to be done as two separate queries.
            deleted_version_pks = versioned_objs.exclude(object_id__in=list(
                live_pk_queryset.iterator())).values_list("object_id")
        deleted_version_pks = deleted_version_pks.exclude(
            type=VERSION_DELETE, ).annotate(latest_pk=Max("pk")).values_list(
                "latest_pk", flat=True)
        return self._get_versions().filter(
            pk__in=deleted_version_pks).order_by("-pk")

    # Signal receivers.

    def _post_save_receiver(self, instance, created, **kwargs):
        """Adds registered models to the current revision, if any."""
        if self._revision_context_manager.is_active():
            adapter = self.get_adapter(instance.__class__)
            if created:
                version_data = adapter.get_version_data(instance, VERSION_ADD)
            else:
                version_data = adapter.get_version_data(
                    instance, VERSION_CHANGE)
            self._revision_context_manager.add_to_context(
                self, instance, version_data)

    def _pre_delete_receiver(self, instance, **kwargs):
        """Adds registered models to the current revision, if any."""
        if self._revision_context_manager.is_active():
            adapter = self.get_adapter(instance.__class__)
            version_data = adapter.get_version_data(instance, VERSION_DELETE)
            self._revision_context_manager.add_to_context(
                self, instance, version_data)