Repository: CTFd/CTFd

Issue:
Unable to import backups with ISO 8601 date+time based flags
**Environment**:

  - CTFd Version/Commit: Latest (As of 18/02/2020)
  - Operating System: Docker (Underlying OS tested on Mac and Google Cloud Run)
  - Web Browser and Version: Chrome for Mac Version 79.0.3945.130 (Official Build) (64-bit)

**What happened?**

When importing a backup into a fresh CTFd container I receive the error "ValueError('unconverted data remains: Z')"

After a bit of testing, the 'Z' is part of one of my flags in flag.json. Unzipping the backup, removing the Z and re-zipping allows the backup to be imported correctly.

Full flag in broken backup was: 1996-02-20T05:00:00Z

**What did you expect to happen?**

Backup should be imported correctly

**How to reproduce your issue**

Create a CTF game with "1996-02-20T05:00:00Z" as a flag (no quotes). Export this backup, then try and re-import it

**Any associated stack traces or error logs**

"ValueError('unconverted data remains: Z')" as a browser popup




Discussion:
This is related to #1174. Are you using SQLite or MySQL as your database backend? 
Considering this appears to be more common than I thought I'll re-evaluate merging that fix in. 
Hey @ColdHeat Appreciate the quick reply.

Apologies, I did not see the PR to fix this issue.

I am using the standard DockerFile in the repo, which looks to use SQLite

Let me know if you'd anymore information, or want me to test anything
You'd probably be better off using docker-compose which will give you a MySQL database to avoid this issue. 

Resolving patch:
diff --git a/CTFd/utils/exports/__init__.py b/CTFd/utils/exports/__init__.py
--- a/CTFd/utils/exports/__init__.py
+++ b/CTFd/utils/exports/__init__.py
@@ -14,9 +14,10 @@
 from flask import current_app as app
 from flask_migrate import upgrade
 from sqlalchemy.exc import OperationalError, ProgrammingError
+from sqlalchemy.sql import sqltypes
 
 from CTFd.cache import cache
-from CTFd.models import db
+from CTFd.models import db, get_class_by_tablename
 from CTFd.utils import get_app_config, set_config
 from CTFd.utils.migrations import (
     create_database,
@@ -242,24 +243,36 @@ def import_ctf(backup, erase=True):
                     # This is a hack to get SQLite to properly accept datetime values from dataset
                     # See Issue #246
                     if sqlite:
+                        direct_table = get_class_by_tablename(table.name)
                         for k, v in entry.items():
                             if isinstance(v, six.string_types):
-                                match = re.match(
-                                    r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d", v
-                                )
-                                if match:
-                                    entry[k] = datetime.datetime.strptime(
-                                        v, "%Y-%m-%dT%H:%M:%S.%f"
+                                # We only want to apply this hack to columns that are expecting a datetime object
+                                try:
+                                    is_dt_column = (
+                                        type(getattr(direct_table, k).type)
+                                        == sqltypes.DateTime
                                     )
-                                    continue
-                                match = re.match(
-                                    r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", v
-                                )
-                                if match:
-                                    entry[k] = datetime.datetime.strptime(
-                                        v, "%Y-%m-%dT%H:%M:%S"
+                                except AttributeError:
+                                    is_dt_column = False
+
+                                # If the table is expecting a datetime, we should check if the string is one and convert it
+                                if is_dt_column:
+                                    match = re.match(
+                                        r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d", v
+                                    )
+                                    if match:
+                                        entry[k] = datetime.datetime.strptime(
+                                            v, "%Y-%m-%dT%H:%M:%S.%f"
+                                        )
+                                        continue
+                                    match = re.match(
+                                        r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", v
                                     )
-                                    continue
+                                    if match:
+                                        entry[k] = datetime.datetime.strptime(
+                                            v, "%Y-%m-%dT%H:%M:%S"
+                                        )
+                                        continue
                     # From v2.0.0 to v2.1.0 requirements could have been a string or JSON because of a SQLAlchemy issue
                     # This is a hack to ensure we can still accept older exports. See #867
                     if member in (


Test patch: