from django.db import models
from django.dispatch import Signal


def _add_field_to_class(cls, name):
    try:
        pk_field = (x for x in cls._meta.fields if x.primary_key).next()
        pk_type = pk_field.__class__
    except StopIteration:
        pk_type = None

    if pk_type is None:
        pk_type = models.AutoField

    if pk_type == models.CharField:
        models.CharField(
            editable=False,
            max_length=pk_field.max_length,
            blank=True,
            default=""
        ).contribute_to_class(cls, name)
    else:
        models.BigIntegerField(
            editable=False,
            blank=True,
            default=0
        ).contribute_to_class(cls, name)


def _add_field_to_unique_constraints(cls, name):
    new_unique_togethers = []

    for field in cls._meta.fields:
        if field.unique and not field.primary_key:
            field._unique = False

            new_unique_togethers.append((field.name, "archive_identifier"))

    for constraint in cls._meta.unique_together:
        new_unique_togethers.append(
            tuple(list(constraint) + ["archive_identifier"])
        )

    cls._meta.unique_together = new_unique_togethers


def _replace_manager(cls, new_manager_name):
    if hasattr(cls, "objects"):
        default_manager = cls.objects.__class__
    else:
        default_manager = models.Manager

    class ExcludeArchivedManager(default_manager):
        use_in_migrations = False

        def get_queryset(self):
            return super(ExcludeArchivedManager, self).get_queryset().filter(archive_identifier=0)

    default_manager().contribute_to_class(cls, "with_archived")
    ExcludeArchivedManager().contribute_to_class(cls, "objects")
    try:
        cls._default_manager = cls.objects
    except AttributeError:  # Django >= 1.10
        cls._meta.default_manager_name = cls.objects.name


post_archive = Signal(providing_args=["instance"])


def _override_methods(cls):
    def archive(self):
        self.archive_identifier = self.pk
        self.save()
        post_archive.send(sender=self.__class__, instance=self)

    def restore(self):
        self.archive_identifier = 0
        self.save()

    def delete(self, force=False, *args, **kwargs):
        if force:
            return super(cls, self).delete(*args, **kwargs)
        else:
            self.archive()

    @property
    def is_archived(self):
        return self.archive_identifier != 0

    cls.archive = archive
    cls.restore = restore
    cls.delete = delete
    cls.is_archived = is_archived


def archivable(cls):
    """
        A class decorator that makes a model archivable

        Works by:

         - Adding an archive_identifier field, which if zero means the object is unarchived, otherwise
           it's made equal to the instance's PK
         - Manipulates unique constraints to add the archive_identifier
         - Overrides delete to instead archive unless you pass force=True as a param
         - Adds with_archived as a manager
         - Replaces objects with ExcludeArchivedManager
    """

    _add_field_to_class(cls, "archive_identifier")
    _add_field_to_unique_constraints(cls, "archive_identifier")
    _replace_manager(cls, "with_archived")
    _override_methods(cls)

    return cls

@@ -1954,24 +1954,54 @@
 thods(cls):%0A
+%0A    class Mixin(object):%0A    
     def arch
@@ -2003,32 +2003,36 @@
  archive(self):%0A
+    
         self.arc
@@ -2057,32 +2057,36 @@
 self.pk%0A        
+    
 self.save()%0A    
@@ -2079,18 +2079,176 @@
 lf.save(
-)%0A
+update_fields=%5B%22archive_identifier%22%5D)%0A%0A            parent = super(Mixin, self)%0A            if hasattr(parent, %22archive%22):%0A                parent.archive()%0A%0A    
         
@@ -2304,16 +2304,20 @@
 =self)%0A%0A
+    
     def 
@@ -2331,32 +2331,36 @@
 (self):%0A        
+  
+  
 self.archive_ide
@@ -2371,32 +2371,36 @@
 ier = 0%0A        
+    
 self.save()%0A%0A   
@@ -2393,19 +2393,176 @@
 lf.save(
-)%0A%0A
+update_fields=%5B%22archive_identifier%22%5D)%0A%0A            parent = super(Mixin, self)%0A            if hasattr(parent, %22restore%22):%0A                parent.archive()%0A%0A    
     def 
@@ -2609,24 +2609,28 @@
 s):%0A        
+    
 if force:%0A  
@@ -2631,32 +2631,36 @@
 ce:%0A            
+  
+  
 return super(cls
@@ -2656,19 +2656,21 @@
 n super(
-cls
+Mixin
 , self).
@@ -2697,24 +2697,28 @@
 gs)%0A        
+    
 else:%0A      
@@ -2715,32 +2715,36 @@
 se:%0A            
+   
+ 
 self.archive()%0A%0A
@@ -2739,24 +2739,28 @@
 .archive()%0A%0A
+    
     @propert
@@ -2761,24 +2761,28 @@
 roperty%0A    
+   
+ 
 def is_archi
@@ -2784,32 +2784,36 @@
 archived(self):%0A
+    
         return s
@@ -2849,113 +2849,134 @@
     
-cls.archive = archive%0A    cls.restore = restore%0A    cls.delete = delete%0A    cls.is_archived = is_archived
+# Insert our mixin above the class we are decorating in the __bases__%0A    cls.__bases__ = tuple(%5BMixin%5D + list(cls.__bases__))
 %0A%0A%0Ad

Change how overriding works to use a mixin-based approach