Repository: jazzband/django-simple-history

Issue:
Can't Access the History Detail View of /app/model/<pk>/history/<history_id>
We have this setup (Django **1.8.18**, SimpleHistory **1.8.2**):
```
# core/models.py
class VersionedAbstractModel(models.Model):
    class Meta:
        abstract = True

    history = HistoricalRecords(inherit=True)


# accounts/models.py
class Brand(VersionedAbstractModel):
    ...

```

So, when I run the `makemigrations`, SimpleHistory migration files will be created in **Accounts** (contains the VersionedAbstractModel child models) and **Core** apps (contains the VersionedAbstractModel parent model). Everything is working fine except when going to the Detail View of particular history objects (e.g. `/accounts/brand/4/history/3/`) since the breadcrumbs in `object_history_form.html` are not resolving properly. It will reference the `/core` admin URL for the Core app (which is non-existent in our case).

Moreover, breadcrumb will switch from `Home › Accounts › Brands › Brand-1 › History` to `Home › Core › Historical Brands › Brand-1 › History › Revert Brand` as you go from history list to history details/revert pages. Hence, it switches scope from **Accounts** app to **Core** app. Unfortunately, we have **NO** non-abstract model registered in our **Core** app and it only contains services/utility/helper functions, plus the migration files for every model due to the inherited VersionedAbstractModel. This issue probably happens when `inherit=True` and/or the involved `parent-child` models are in separate apps/namespaces.

Error Trace:
```
NoReverseMatch at /accounts/brand/4/history/3/
Reverse for 'app_list' with arguments '(u'core',)' and keyword arguments '{}' not found. 
1 pattern(s) tried: [u'(?P<app_label>accounts|events)/$']

In template ~/.virtualenvs/eventcms/eventcms/templates/simple_history/object_history_form.html,
error at line 8:
<a href="{% url "admin:app_list" app_label %}">{{app_label\|capfirst\|escape}}</a> &rsaquo;
```

Possible Workarounds:
1. Create a dummy model in our **Core** app and register it via `admin.site.register()` so that `/core` URL will be registered. However, the dummy model will show in Admin dashboard.
2. Override the `object_history_form.html` and remove the line referencing the **Core** app or the app containing the parent `SimpleHistory` model (i.e. the `VersionedAbstractModel`).

Eventually, we did the item 2 since we have no need for the `/core` URL as of now. It would be great/interesting if you could find a cleaner/simpler solution.

Cheers for this great app! :)



Discussion:
I'm guessing this also means that the historical tables have a 'core_' prefix? It looks like our app label testing is a bit light with regards to inherited history tracking:

https://github.com/treyhunner/django-simple-history/blob/1.9.0/simple_history/tests/tests/test_models.py#L378

I'm thinking it's a bug in how the app label is being set. I would expect the historical models to match the models they are registered with.
Possibly related to #269 

Resolving patch:
diff --git a/simple_history/admin.py b/simple_history/admin.py
--- a/simple_history/admin.py
+++ b/simple_history/admin.py
@@ -162,7 +162,7 @@ def history_form_view(self, request, object_id, version_id):
             'is_popup': False,
             'media': mark_safe(self.media + admin_form.media),
             'errors': helpers.AdminErrorList(form, formsets),
-            'app_label': model._meta.app_label,
+            'app_label': original_opts.app_label,
             'original_opts': original_opts,
             'changelist_url': reverse('%s:%s_%s_changelist' % url_triplet),
             'change_url': reverse('%s:%s_%s_change' % url_triplet,


Test patch:
diff --git a/simple_history/tests/admin.py b/simple_history/tests/admin.py
--- a/simple_history/tests/admin.py
+++ b/simple_history/tests/admin.py
@@ -3,7 +3,7 @@
 from django.contrib import admin
 
 from simple_history.admin import SimpleHistoryAdmin
-from .models import Poll, Choice, Person, Book, Document, Paper, Employee
+from .models import Poll, Choice, Person, Book, Document, Paper, Employee, ConcreteExternal
 
 
 class PersonAdmin(SimpleHistoryAdmin):
@@ -22,3 +22,4 @@ class ChoiceAdmin(SimpleHistoryAdmin):
 admin.site.register(Document, SimpleHistoryAdmin)
 admin.site.register(Paper, SimpleHistoryAdmin)
 admin.site.register(Employee, SimpleHistoryAdmin)
+admin.site.register(ConcreteExternal, SimpleHistoryAdmin)
diff --git a/simple_history/tests/external/models/model1.py b/simple_history/tests/external/models/model1.py
new file mode 100644
--- /dev/null
+++ b/simple_history/tests/external/models/model1.py
@@ -0,0 +1,12 @@
+from __future__ import unicode_literals
+
+from django.db import models
+from simple_history.models import HistoricalRecords
+
+
+class AbstractExternal(models.Model):
+    history = HistoricalRecords(inherit=True)
+
+    class Meta:
+        abstract = True
+        app_label = 'external'
diff --git a/simple_history/tests/models.py b/simple_history/tests/models.py
--- a/simple_history/tests/models.py
+++ b/simple_history/tests/models.py
@@ -1,12 +1,31 @@
 from __future__ import unicode_literals
 
 from django.db import models
+from django import VERSION
 
 from simple_history.models import HistoricalRecords
 from simple_history import register
 
 from .custom_user.models import CustomUser as User
 
+try:
+    from django.apps import apps
+except ImportError:  # Django < 1.7
+    from django.db.models import get_model
+else:
+    get_model = apps.get_model
+
+# 1.6 has different way of importing models
+if VERSION[:3] >= (1, 7, 0):
+    from .external.models.model1 import AbstractExternal
+else:
+    class AbstractExternal(models.Model):
+        history = HistoricalRecords(inherit=True)
+
+        class Meta:
+            abstract = True
+            app_label = 'external'
+
 
 class Poll(models.Model):
     question = models.CharField(max_length=200)
@@ -341,6 +360,13 @@ class UntrackedConcreteBase(models.Model):
     pass
 
 
+class ConcreteExternal(AbstractExternal):
+    name = models.CharField(max_length=50)
+
+    class Meta:
+        app_label = 'tests'
+
+
 class TrackedWithAbstractBase(TrackedAbstractBaseA):
     pass
 
diff --git a/simple_history/tests/tests/test_admin.py b/simple_history/tests/tests/test_admin.py
--- a/simple_history/tests/tests/test_admin.py
+++ b/simple_history/tests/tests/test_admin.py
@@ -13,7 +13,7 @@
 
 from simple_history.models import HistoricalRecords
 from simple_history.admin import SimpleHistoryAdmin, get_complete_version
-from ..models import Book, Person, Poll, State, Employee, Choice
+from ..models import Book, Person, Poll, State, Employee, Choice, ConcreteExternal
 
 try:
     from django.contrib.admin.utils import quote
@@ -551,3 +551,58 @@ def test_history_form_view_getting_history_with_setting_off(self):
         }
         mock_render.assert_called_once_with(
             request, admin.object_history_form_template, context, **extra_kwargs)
+
+    def test_history_form_view_getting_history_abstract_external(self):
+        request = RequestFactory().post('/')
+        request.session = 'session'
+        request._messages = FallbackStorage(request)
+        request.user = self.user
+        request.POST = {'_change_history': True}
+
+        obj = ConcreteExternal.objects.create(name='test')
+        obj.name = "new_test"
+        obj.save()
+        history = obj.history.all()[0]
+
+        admin_site = AdminSite()
+        admin = SimpleHistoryAdmin(ConcreteExternal, admin_site)
+
+        with patch('simple_history.admin.render') as mock_render:
+            with patch('simple_history.admin.SIMPLE_HISTORY_EDIT', True):
+                admin.history_form_view(request, obj.id, history.pk)
+
+        context = {
+            # Verify this is set for history object
+            'original': history.instance,
+            'change_history': True,
+
+            'title': 'Revert %s' % force_text(history.instance),
+            'adminform': ANY,
+            'object_id': obj.id,
+            'is_popup': False,
+            'media': ANY,
+            'errors': ANY,
+            'app_label': 'tests',
+            'original_opts': ANY,
+            'changelist_url': '/admin/tests/concreteexternal/',
+            'change_url': ANY,
+            'history_url': '/admin/tests/concreteexternal/{pk}/history/'.format(
+                pk=obj.pk),
+            'add': False,
+            'change': True,
+            'has_add_permission': admin.has_add_permission(request),
+            'has_change_permission': admin.has_change_permission(
+                request, obj),
+            'has_delete_permission': admin.has_delete_permission(
+                request, obj),
+            'has_file_field': True,
+            'has_absolute_url': False,
+            'form_url': '',
+            'opts': ANY,
+            'content_type_id': ANY,
+            'save_as': admin.save_as,
+            'save_on_top': admin.save_on_top,
+            'root_path': getattr(admin_site, 'root_path', None),
+        }
+        mock_render.assert_called_once_with(
+            request, admin.object_history_form_template, context, **extra_kwargs)