Search code examples
djangopostgresqlcsvresourcesdjango-import-export

Django Import-Export DateTime Naive DateTime Error


Importing DateTime Error Message:

 RuntimeWarning: DateTimeField Entry.date received a naive datetime (2020-07-20 09:23:19.881763) while time zone support is active.
  RuntimeWarning)

Is there a way a user can select the datetime they are in or at least import the data with +00 added? Currently, I can't seem to be able to import once I add the datetime field.

Right now I'm trying this issue solution

resources.py

class TzDateTimeWidget(DateTimeWidget):

    def render(self, value, obj=None):
        if settings.USE_TZ:
            value = localtime(value)
        return super(TzDateTimeWidget, self).render(value)

class EntryResource(resources.ModelResource):
    date = fields.Field(
        column_name='date',
        attribute='datetime',
        widget=TzDateTimeWidget(format='"%Y-%m-%d %H:%M:%S"'))


    class Meta:
        model = Entry
        fields = ('date', 'entry_type', 'amount', 'price', 'fee', 'reg_fee', 'id',)
        import_order = fields
        skip_unchanged = False
        report_skipped = True

csv file

2020-07-17 7:42:39,,40,14.56,0,Entry,0
2020-07-17 7:47:16,,40,14.78,0,Entry,0

Solution

  • The issue is that the datetime in your csv file is not Timezone aware. There are a few ways to solve this.

    Option 1 - all users are in one timezone

    You don't need timezone information in the csv if you know that the supplied date is always going to be in your timezone, because it will be converted during import and have the correct timezone.

    Option 2 - CSV contains TZ aware timestamps

    If the supplied timestamps are timezone aware, then you can handle this during import:

    csv:

    2020-07-20 00:00:00+03:00,40,14.56,0,Entry,0
    

    Widget:

    class TzDateTimeWidget(widgets.DateTimeWidget):
        def clean(self, value, row=None, *args, **kwargs):
            if not value:
                return None
            if isinstance(value, datetime):
                return value
            for format in self.formats:
                try:
                    if settings.USE_TZ:
                        dt = parse_datetime(value)
                    return dt
                except (ValueError, TypeError):
                    continue
            raise ValueError("Enter a valid date/time.")
    

    Option 3 - store timezone per user and apply it during import

    If your users can be in any timezone, but it is not possible to encode tz in the csv, then you can store the User's timezone (e.g. in a custom User model instance), and apply the conversion for the user during import.

    A simplified version of this would be:

    class UserDateTimeWidget(widgets.DateTimeWidget):
        def __init__(self, user_tz_dict):
            """pass in a dict mapping user_id to timezone"""
            super().__init__()
            self.user_dict = user_tz_dict
    
        def clean(self, value, row=None, *args, **kwargs):
            dt = super().clean(value, row, args, kwargs)
            # the row will need to contain a reference to the user id
            user_id = row.get("user_id")
            user_tz = self.user_dict.get(user_id)
            return make_aware(dt, timezone=user_tz)
    

    It's important to note that this relies on the csv timestamp being in a consistent standard, such as UTC.

    Note that there are other ways you could achieve the above, such as overriding the Model save() method to apply the User's timezone at the point of saving the instance. Obviously you have to link the User to the imported object.

    As a side note, the TzDateTimeWidget you reference will only format the date in the defined timezone when the data is exported. This functionality is already present in DateTimeWidget.