<commit_before>from __future__ import unicode_literals
from collections import defaultdict
from itertools import chain
from django.contrib.contenttypes.models import ContentType
try:
    from django.contrib.contenttypes.fields import GenericForeignKey
except ImportError:  # Django < 1.9 pragma: no cover
    from django.contrib.contenttypes.generic import GenericForeignKey
from django.conf import settings
from django.core import serializers
from django.core.exceptions import ObjectDoesNotExist
from django.db import models, IntegrityError, transaction
from django.db.models.lookups import In
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text, python_2_unicode_compatible
from reversion.errors import RevertError
from reversion.revisions import RevisionManager, default_revision_manager, get_version_data_key
from reversion.signals import pre_revision_commit, post_revision_commit, post_revision_context_end


def safe_revert(versions, using=None):
    """
    Attempts to revert the given models contained in the give versions.

    This method will attempt to resolve dependencies between the versions to revert
    them in the correct order to avoid database integrity errors.
    """
    unreverted_versions = []
    for version in versions:
        try:
            with transaction.atomic(using=using):
                version.revert(using=using)
        except (IntegrityError, ObjectDoesNotExist):  # pragma: no cover
            unreverted_versions.append(version)
    if len(unreverted_versions) == len(versions):  # pragma: no cover
        raise RevertError("Could not revert revision, due to database integrity errors.")
    if unreverted_versions:  # pragma: no cover
        safe_revert(unreverted_versions, using)


class RevisionModelManager(models.Manager):

    def save_revision(self, objects=(), serialized_objects=(), ignore_duplicates=False, user=None, comment="", meta=(),
                      revision_manager=default_revision_manager):
        """
        Manually saves a new revision containing the given objects.

        `objects` is an iterable of model instances.
        `serialized_objects` is an iterable of dicts of version data.
        """
        # Create the object versions.
        version_data_dict = {
            get_version_data_key(version_data): version_data
            for version_data
            in serialized_objects
        }
        for obj in objects:
            for version_data in revision_manager._get_version_data_list(obj, self.db):
                version_data_dict[get_version_data_key(version_data)] = version_data
        new_versions = [Version(**version_data) for version_data in version_data_dict.values()]
        # Bail early if there are no objects to save.
        if not new_versions:
            return
        # Check for duplicates, if requested.
        save_revision = True
        if ignore_duplicates:
            # Find the latest revision amongst the latest previous version of each object.
            latest_revision_qs = self.annotate(
                version_count=models.Count("version"),
            ).filter(
                version_count=len(new_versions),
                manager_slug=revision_manager._manager_slug,
            )
            for version in new_versions:
                latest_revision_qs = latest_revision_qs.filter(
                    version__object_id=version.object_id,
                    version__content_type_id=version.content_type_id,
                )
            latest_revision = latest_revision_qs.order_by("-pk").first()
            # If we have a latest revision, compare it to the current revision.
            if latest_revision is not None:
                previous_versions = latest_revision.version_set.all()

                # Creates a sorted list of version dicts for comparison.
                def get_version_dicts(versions):
                    return [
                        version.local_field_dict
                        for version
                        in sorted(versions, key=lambda v: (v.content_type_id, v.object_id))
                    ]
                save_revision = get_version_dicts(previous_versions) != get_version_dicts(new_versions)
        # Only save if we're always saving, or have changes.
        if save_revision:
            # Save a new revision.
            revision = Revision(
                manager_slug=revision_manager._manager_slug,
                user=user,
                comment=comment,
            )
            # Send the pre_revision_commit signal.
            pre_revision_commit.send(
                sender=revision_manager,
                instances=objects,
                revision=revision,
                versions=new_versions,
            )
            # Save the revision.
            with transaction.atomic(using=self.db):
                revision.save(using=self.db)
                # Save version models.
                for version in new_versions:
                    version.revision = revision
                    version.save(using=self.db)
                # Save the meta information.
                for meta_obj in meta:
                    meta_obj.revision = revision
                    meta_obj.save(using=self.db)
            # Send the post_revision_commit signal.
            post_revision_commit.send(
                sender=revision_manager,
                instances=objects,
                revision=revision,
                versions=new_versions,
            )
            # Return the revision.
            return revision


@python_2_unicode_compatible
class Revision(models.Model):

    """A group of related object versions."""

    objects = RevisionModelManager()

    manager_slug = models.CharField(
        max_length=191,
        db_index=True,
        default="default",
    )

    date_created = models.DateTimeField(
        auto_now_add=True,
        db_index=True,
        verbose_name=_("date created"),
        help_text="The date and time this revision was created.",
    )

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name=_("user"),
        help_text="The user who created this revision.",
    )

    comment = models.TextField(
        blank=True,
        verbose_name=_("comment"),
        help_text="A text comment on this revision.",
    )

    def revert(self, delete=False, using=None):
        """Reverts all objects in this revision."""
        using = using or self._state.db
        version_set = self.version_set.all()
        # Optionally delete objects no longer in the current revision.
        if delete:
            # Get a dict of all objects in this revision.
            old_revision_models = defaultdict(list)
            for version in version_set:
                content_type = ContentType.objects.db_manager(using).get_for_id(version.content_type_id)
                old_revision_models[content_type].append(version.object_id)
            old_revision = set(chain.from_iterable(
                content_type.model_class()._base_manager.in_bulk(object_ids).values()
                for content_type, object_ids
                in old_revision_models.items()
            ))
            # Calculate the set of all objects that are in the revision now.
            revision_manager = RevisionManager.get_manager(self.manager_slug)
            current_revision = chain.from_iterable(
                revision_manager._follow_relationships(obj)
                for obj in old_revision
            )
            # Delete objects that are no longer in the current revision.
            for item in current_revision:
                if item not in old_revision:
                    item.delete(using=using)
        # Attempt to revert all revisions.
        safe_revert(version_set, using)

    def __str__(self):
        """Returns a unicode representation."""
        return ", ".join(force_text(version) for version in self.version_set.all())

    class Meta:
        app_label = 'reversion'


def _post_revision_context_end_receiver(sender, objects, serialized_objects, ignore_duplicates, user, comment,
                                        meta, **kwargs):
    Revision.objects.save_revision(<commit_msg>A for_revision_manager method on Revision.objects<commit_after>from __future__ import unicode_literals
from collections import defaultdict
from itertools import chain
from django.contrib.contenttypes.models import ContentType
try:
    from django.contrib.contenttypes.fields import GenericForeignKey
except ImportError:  # Django < 1.9 pragma: no cover
    from django.contrib.contenttypes.generic import GenericForeignKey
from django.conf import settings
from django.core import serializers
from django.core.exceptions import ObjectDoesNotExist
from django.db import models, IntegrityError, transaction
from django.db.models.lookups import In
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text, python_2_unicode_compatible
from reversion.errors import RevertError
from reversion.revisions import RevisionManager, default_revision_manager, get_version_data_key
from reversion.signals import pre_revision_commit, post_revision_commit, post_revision_context_end


def safe_revert(versions, using=None):
    """
    Attempts to revert the given models contained in the give versions.

    This method will attempt to resolve dependencies between the versions to revert
    them in the correct order to avoid database integrity errors.
    """
    unreverted_versions = []
    for version in versions:
        try:
            with transaction.atomic(using=using):
                version.revert(using=using)
        except (IntegrityError, ObjectDoesNotExist):  # pragma: no cover
            unreverted_versions.append(version)
    if len(unreverted_versions) == len(versions):  # pragma: no cover
        raise RevertError("Could not revert revision, due to database integrity errors.")
    if unreverted_versions:  # pragma: no cover
        safe_revert(unreverted_versions, using)


class RevisionModelManager(models.Manager):

    def for_revision_manager(self, revision_manager):
        """
        Returns all revisions created using the given revision manager.
        """
        return self.filter(manager_slug=revision_manager._manager_slug)

    def save_revision(self, objects=(), serialized_objects=(), ignore_duplicates=False, user=None, comment="", meta=(),
                      revision_manager=default_revision_manager):
        """
        Manually saves a new revision containing the given objects.

        `objects` is an iterable of model instances.
        `serialized_objects` is an iterable of dicts of version data.
        """
        # Create the object versions.
        version_data_dict = {
            get_version_data_key(version_data): version_data
            for version_data
            in serialized_objects
        }
        for obj in objects:
            for version_data in revision_manager._get_version_data_list(obj, self.db):
                version_data_dict[get_version_data_key(version_data)] = version_data
        new_versions = [Version(**version_data) for version_data in version_data_dict.values()]
        # Bail early if there are no objects to save.
        if not new_versions:
            return
        # Check for duplicates, if requested.
        save_revision = True
        if ignore_duplicates:
            # Find the latest revision amongst the latest previous version of each object.
            latest_revision_qs = self.annotate(
                version_count=models.Count("version"),
            ).filter(
                version_count=len(new_versions),
                manager_slug=revision_manager._manager_slug,
            )
            for version in new_versions:
                latest_revision_qs = latest_revision_qs.filter(
                    version__object_id=version.object_id,
                    version__content_type_id=version.content_type_id,
                )
            latest_revision = latest_revision_qs.order_by("-pk").first()
            # If we have a latest revision, compare it to the current revision.
            if latest_revision is not None:
                previous_versions = latest_revision.version_set.all()

                # Creates a sorted list of version dicts for comparison.
                def get_version_dicts(versions):
                    return [
                        version.local_field_dict
                        for version
                        in sorted(versions, key=lambda v: (v.content_type_id, v.object_id))
                    ]
                save_revision = get_version_dicts(previous_versions) != get_version_dicts(new_versions)
        # Only save if we're always saving, or have changes.
        if save_revision:
            # Save a new revision.
            revision = Revision(
                manager_slug=revision_manager._manager_slug,
                user=user,
                comment=comment,
            )
            # Send the pre_revision_commit signal.
            pre_revision_commit.send(
                sender=revision_manager,
                instances=objects,
                revision=revision,
                versions=new_versions,
            )
            # Save the revision.
            with transaction.atomic(using=self.db):
                revision.save(using=self.db)
                # Save version models.
                for version in new_versions:
                    version.revision = revision
                    version.save(using=self.db)
                # Save the meta information.
                for meta_obj in meta:
                    meta_obj.revision = revision
                    meta_obj.save(using=self.db)
            # Send the post_revision_commit signal.
            post_revision_commit.send(
                sender=revision_manager,
                instances=objects,
                revision=revision,
                versions=new_versions,
            )
            # Return the revision.
            return revision


@python_2_unicode_compatible
class Revision(models.Model):

    """A group of related object versions."""

    objects = RevisionModelManager()

    manager_slug = models.CharField(
        max_length=191,
        db_index=True,
        default="default",
    )

    date_created = models.DateTimeField(
        auto_now_add=True,
        db_index=True,
        verbose_name=_("date created"),
        help_text="The date and time this revision was created.",
    )

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        verbose_name=_("user"),
        help_text="The user who created this revision.",
    )

    comment = models.TextField(
        blank=True,
        verbose_name=_("comment"),
        help_text="A text comment on this revision.",
    )

    def revert(self, delete=False, using=None):
        """Reverts all objects in this revision."""
        using = using or self._state.db
        version_set = self.version_set.all()
        # Optionally delete objects no longer in the current revision.
        if delete:
            # Get a dict of all objects in this revision.
            old_revision_models = defaultdict(list)
            for version in version_set:
                content_type = ContentType.objects.db_manager(using).get_for_id(version.content_type_id)
                old_revision_models[content_type].append(version.object_id)
            old_revision = set(chain.from_iterable(
                content_type.model_class()._base_manager.in_bulk(object_ids).values()
                for content_type, object_ids
                in old_revision_models.items()
            ))
            # Calculate the set of all objects that are in the revision now.
            revision_manager = RevisionManager.get_manager(self.manager_slug)
            current_revision = chain.from_iterable(
                revision_manager._follow_relationships(obj)
                for obj in old_revision
            )
            # Delete objects that are no longer in the current revision.
            for item in current_revision:
                if item not in old_revision:
                    item.delete(using=using)
        # Attempt to revert all revisions.
        safe_revert(version_set, using)

    def __str__(self):
        return ", ".join(force_text(version) for version in self.version_set.all())

    class Meta:
        app_label = 'reversion'


def _post_revision_context_end_receiver(sender, objects, serialized_objects, ignore_duplicates, user, comment,
                                        meta, **kwargs):
    Revision.objects.save_revision(
{'fraction_non_alphanumeric': 0.06605050921292753, 'fraction_numerical': 0.0008241596514982045, 'mean_word_length': 2.2216954295467475, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7624515', 'n_tokens_mistral': 4415, 'n_tokens_neox': 4277, 'n_words': 1145}