Repository: jazzband/django-simple-history

Issue:
FileField should be transformed into a CharField instead of a TextField
**Describe the bug**
`FileField` is turned into a `TextField` on history tables by the [`transform_field` function](https://github.com/treyhunner/django-simple-history/blob/68211b5dcbf79dfab71144fe1a524598b3656e23/simple_history/models.py#L546), but it should be turned into a CharField instead:

```
    elif isinstance(field, models.FileField):
        # Don't copy file, just path.
        field.__class__ = models.TextField  # should be CharField
```

Using `TextField` changes the representation of the file field in the database, because `FileField`s use the same database representation as `CharField`, not `TextField`.

For example, see Django's [postgres backend](https://github.com/django/django/blob/master/django/db/backends/postgresql/base.py#L72), where FileField is mapped to `'varchar(%(max_length)s)'` instead of `'text'`; the same is true for other backends. 

The upshot is that `FileFields` end up as `varchar`s in the primary table but as `text`s in the history table. This probably works fine for most users, since it's a more permissive field, but it's breaking a third party tool I'm using that expects the primary table and history table columns to have the same type.

**To Reproduce**
Steps to reproduce the behavior:
1. Create a history-tracked table with a `FileField`
2. Run `./manage.py sqlmigrate` to observe that the file field on the primary table is `varchar` but the field on the history table is `text`.

**Expected behavior**
Fields on the primary and history table should have the same type.



Discussion:
Thanks for this. Not sure why it was a TextField in the first place, should be fine as a CharField. If you want to put up a backward compatible pr, I'd be happy to merge. The change should occur here https://github.com/treyhunner/django-simple-history/blame/57507748205a5dd2340893940c624749f1ba503d/simple_history/models.py#L546

and you'll want to have a setting that by default leaves them as TextFields, but allows you to switch them to CharFields

Resolving patch:
diff --git a/simple_history/models.py b/simple_history/models.py
--- a/simple_history/models.py
+++ b/simple_history/models.py
@@ -545,7 +545,10 @@ def transform_field(field):
 
     elif isinstance(field, models.FileField):
         # Don't copy file, just path.
-        field.__class__ = models.TextField
+        if getattr(settings, "SIMPLE_HISTORY_FILEFIELD_TO_CHARFIELD", False):
+            field.__class__ = models.CharField
+        else:
+            field.__class__ = models.TextField
 
     # Historical instance shouldn't change create/update timestamps
     field.auto_now = False


Test patch:
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
@@ -192,6 +192,20 @@ class FileModel(models.Model):
     history = HistoricalRecords()
 
 
+# Set SIMPLE_HISTORY_FILEFIELD_TO_CHARFIELD
+setattr(settings, "SIMPLE_HISTORY_FILEFIELD_TO_CHARFIELD", True)
+
+
+class CharFieldFileModel(models.Model):
+    title = models.CharField(max_length=100)
+    file = models.FileField(upload_to="files")
+    history = HistoricalRecords()
+
+
+# Clear SIMPLE_HISTORY_FILEFIELD_TO_CHARFIELD
+delattr(settings, "SIMPLE_HISTORY_FILEFIELD_TO_CHARFIELD")
+
+
 class Document(models.Model):
     changed_by = models.ForeignKey(
         User, on_delete=models.CASCADE, null=True, blank=True
diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py
--- a/simple_history/tests/tests/test_models.py
+++ b/simple_history/tests/tests/test_models.py
@@ -42,6 +42,7 @@
     BucketDataRegisterChangedBy,
     BucketMember,
     CharFieldChangeReasonModel,
+    CharFieldFileModel,
     Choice,
     City,
     ConcreteAttr,
@@ -316,6 +317,19 @@ def test_file_field(self):
         self.assertEqual(create_record.file, "files/name")
         self.assertEqual(update_record.file, "")
 
+    def test_file_field_with_char_field_setting(self):
+        # setting means history table's file field is a CharField
+        file_field = CharFieldFileModel.history.model._meta.get_field("file")
+        self.assertIs(type(file_field), models.CharField)
+        self.assertEqual(file_field.max_length, 100)
+        # file field works the same as test_file_field()
+        model = CharFieldFileModel.objects.create(file=get_fake_file("name"))
+        self.assertEqual(model.file.name, "files/name")
+        model.file.delete()
+        update_record, create_record = model.history.all()
+        self.assertEqual(create_record.file, "files/name")
+        self.assertEqual(update_record.file, "")
+
     def test_inheritance(self):
         pizza_place = Restaurant.objects.create(name="Pizza Place", rating=3)
         pizza_place.rating = 4