Django datetime format different from DRF serializer datetime format - python

I am trying to understand why this is happening. I have a Django DateTime field and Django Rest Framework serializer that uses the field.
I am trying to compare the dates for both of them and get the following results from JSON endpoint and model result:
DRF: 2018-12-21T19:17:59.353368Z
Model field: 2018-12-21T19:17:59.353368+00:00
Is there a way to make them similar? So, either to make both of them be "Z" or "+00:00."

It's because django rest framework uses it's own datetime formating. To change that, in your settings.py file, there should exist a dict variable called REST_FRAMEWORK (if not create it) and add this:
REST_FRAMEWORK = {
...
'DATETIME_FORMAT': "%Y-%m-%d - %H:%M:%S",
...
}
Also check USE_TZ variable state too in your settings.py

I came here from google looking for a quick fix to get this (e.g. copy and paste). So, borrowing from #RezaTorkamanAhmadi's answer, for anyone looking to get a DRF serializer DateTimeField to have the format 2018-12-21T19:17:59.353368+00:00 (the same format as the default models.DateTimeField so that your serialized values match your model values -- the OP's question and mine too) you're looking for either:
# settings.py
REST_FRAMEWORK = {
...
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S.%f%z",
...
}
or if you just want it for a specific DateTimeField serializer field you're looking for
from rest_framework import serializers
class MySerializer(serializers.Serializer):
some_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S.%f%z")
Sources:
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
https://www.django-rest-framework.org/api-guide/fields/#datetimefield
https://stackoverflow.com/a/53893377/10541855 (Reza Torkaman
Ahmadi's answer)

Apart from previous answer, also you can change DateTime format in your serializer.
from rest_framework import serializers
class YourSerializer(serializers.ModelSerializer):
your_datetime_field = serializers.DateTimeField(format="%Y-%m-%dT%H:%M:%S")
class Meta:
model = YourModel
fields = '__all__'

Related

DRF deserializing data from API to django model

I'm using serializers of django-rest-framework (DRK)
I'm fetch data from an external API, and I want to convert this datas into an internal model (here Period)
The thing is that the field's format of the external api are like this :
{"DateFrom": "2020-02-10T00:00:00"}
I want to rename into "date_from" field.
Here what I tried :
Serializer :
class PeriodSerializer(serializers.ModelSerializer):
date_from = serializers.DateTimeField(write_only=True, source='DateFrom')
class Meta:
model = Period
fields = ('date_from',)
Notice that I tried with "write_only=True, source='DateFrom'"
And then in my code :
json = {"DateFrom": "2020-02-10T00:00:00"}
serializer = PeriodSerializer(data=json)
serializer.is_valid() # This is returning False
print(serializer.errors)
And then the output is :
{'date_from': [ErrorDetail(string='This field is required.', code='required')]}
How handle that ? (in the best way (good practice))
I think you have something backwards here. Given your model and what you expose in your API, you would want to do:
class PeriodSerializer(serializers.ModelSerializer):
DateFrom = serializers.DateTimeField(write_only=True, source='date_from')
source specifies the data source on the model, while the name of the serializer field is what the field will be named when serialized.

convert string to timezone iso format in python

i'm trying to convert a string with this form "2019-07-17T16:00:50.282203+01:30" to timezone format so i can update models.DateTimeField field.
models.py:
from django.utils import timezone
class task(models.Model):
title = models.CharField(max_length=100)
create_date = models.DateTimeField(default=timezone.now)
.
.
.
request will be like this:
{
"title": "editeddd task",
"create_date" : "2019-07-17T16:00:50.282203+01:30"
}
being stuck in converting UTC part.
view.py:
.
.
.
create_date = datetime.strptime(self.request.data['expiration_date'], '%Y-%m-%dT%H:%M:%S.%f%z')
.
.
‍‍the error is for %z part. it can converting "2019-07-17T16:00:50.282203+0130" but not "2019-07-17T16:00:50.282203+01:30" (notice to : in UTC part)
As you are referencing request.data, it looks as though you are using Django REST Framework. In that case the most straightforward way is to use DRF's serializers to parse / validate the incoming data.
from rest_framework import serializers
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ('title', 'create_date')
If you just want to retrieve the validated data you can do:
>>> serializer = TaskSerializer(data=request.data)
>>> serializer.is_valid(raise_exception=True)
>>> serializer.validated_data
OrderedDict([('title', 'editeddd task'), ('create_date', datetime.datetime(2019, 7, 17, 14, 30, 50, 282203, tzinfo=<UTC>))])
Or if you want to save a new task:
task = serializer.save()
It's important to validate all incoming data and validating each field manually by reaching into request.POST or request.data tends to be error prone. DRF's serializers (or Django's forms) have a lot of pre-built logic and make the task significantly easier and safer.
Being strict while sending and tolerant while receiving, you should do a little string mangling on that create_date string before passing it on to strptime.
My suggestions is to work with a regular expression, or to have look at rreplace - How to replace the last occurrence of an expression in a string? to get rid of that last :.
Hope this helps!

Displaying the local time for datetime attribute of a model object in Django

I'm trying to make it so the datetime field attribute of a model object is displayed in the template as the local time of the timezone of the current user. The default timezone in my settings is UTC. Here is an example model:
models.py
class Basic(models.Model):
name = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now_add=True
The data I want to display is in a table made with django-tables2. However, I already tried two methods and both of them did not work:
tables.py attempt 1:
class ShipperDataFileDocumentTable(tables.Table):
created_at = tables.TemplateColumn('''
{% load tz %}
{% localtime on %}
{{ record.created_at }}
{% endlocaltime %}
''')
class Meta:
model = Basic
fields = ('name', 'created_at')
tables.py attempt 2:
class ShipperDataFileDocumentTable(tables.Table):
created_at = tables.TemplateColumn('''
{% load tz %}
{{ record.created_at|localtime }}
''')
class Meta:
model = Basic
fields = ('name', 'created_at')
Both of these methods ended up not changing the time at all. For example, I made an object at 12:00 PM EST. Normally, the template would display it as 4:00 PM in UTC. However, even with those edits, it still displayed the time as 4:00 PM. I'm not sure what I'm doing wrong.
EDIT: Is there a way to detect the user's current timezone? I already tried django-easy-timezones, but for some reason that doesn't work.
Django provide a way to handle this:
Enable timezone support:
Set UZE_TZ = True in your settings file.
Then implement a way for selecting the user time zone:
An example with a form and a middleware.
But a simpler(than the explicit form) solution IMO is to detect it in js, then put it in the user session/cookie.
For example with moment.js:
Session['tz'] = moment.tz.guess()
Then render the localtime in the template.
Or if you don't want to handle it server side and prefer to do it client side make django set the time zone to iso 8601 in the template then convert it in js or with jQuery.
Plenty of solution but still a PITA...
Another poorly answered question that needs my help. Using JavaScript to convert the time would be a valid way to go about it, but due to its complexity I would recommend a library like moment.js. There is a simpler way however, than learning a new JS framework. django-tables2 thinks you want the datetime object in GMT, but you can instead tell it to use local for the display. If you read the python documentation, UZE_TZ = True is a bad idea for production grade code because your datetimes will be stored as a local time, making synchronization across timezones next to impossible. What you want is a datetime object that's timezone aware, like this 2018-03-07 08:34:32.212841-05. Your code created_at = models.DateTimeField(auto_now_add=True) creates just such an object, so step one is done correctly. Step two, display the time to the user in local time. Add this to your model (I hard-coded Eastern for demonstration, if you read the pytz docs, there's also a localize function):
def localTimeCreated(self):
return self.created_at.astimezone(pytz.timezone('US/Eastern')).strftime("%H:%M:%S %p")
Now we need to exclude the old created_at and reference the new method. Go to your table class, add this property, and change Meta:
createdFormatted = tables.Column(accessor='localTimeCreated', verbose_name='Created') # verbose_name = column header
class Meta:
model = Basic
fields = ('name', 'createdFormatted')
exclude = ('created_at')
sequence = ('createdFormatted', 'name' ) #default: extra columns at the end
attrs = {"class" : "table-striped table-bordered"}
empty_text = "User not found."
The last 2 properties are unrelated but good for a professional look.

Overriding create method in Django RestFramework to POST data

I'm currently using Django RestFramework to create APIs that use both GET and POST to retrieve/insert data within my Django application. I currently have a database model called TransactionDateTime that has a field called start_dt (see below), which takes DateTimeField. The challenge is that I'm passing a string in my json POST data structure (see below) and I need to override the create method in order to loop through the JSON structure to convert the string to the appropriate datetime structure. I know the logic to be used to successfully convert string to datetime, because I was able to perform it in the Django shell (see below), but I don't know how to override the create method and write the appropriate code within the create method to make this happen. Please assist. Below is a copy of my view that is successfully returning JSON data structure via GET, with a BIG question within the create method
TransactionDateTime model from models.py
class TransactionDateTime(models.Model):
room = models.ForeignKey(Room, on_delete = models.CASCADE)
start_dt = models.DateTimeField('start_dateTime')
end_dt = models.DateTimeField('end_dateTime', blank=True, null=True)
def __str__(self):
return str(self.start_dt)
Data Structure to be used on POST
[
{
"start_dt": "2015-01-28 03:00:00"
},
{
"start_dt": "2015-01-28 05:30:00"
}
]
Logic to convert string to datetime
from datetime import datetime
my_date = datetime.strptime('2015-01-28 05:30:00', '%Y-%m-%d %H:%M:%S')
Django Mixin and View
class DateTimeMixin(object):
serializer_class = SimpleSerializer4
permission_classes = (permissions.IsAuthenticated,)
class DateTimeViewSet(DateTimeMixin, generics.BulkModelViewSet):
def get_queryset(self):
num = self.kwargs['dt_rm']
num2 = self.kwargs['id']
r1 = Room.objects.get(id = num)
s1 = Schedule.objects.get(pk=num2)
u= self.request.user.pk
usr = User.objects.get(pk=u)
if(s1.user.username == usr.username):
queryset = r1.transactiondatetime_set.all()
return queryset
else: raise Http404("User does not exist")
def get_serializer_context(self):
num = self.kwargs['id']
s1 = Schedule.objects.get(pk=num)
var = s1.user.username
context = super(DateTimeViewSet, self).get_serializer_context()
return {'request' : var}
def created(self, request, *args, **kwargs):
???
I think the proper way to go about it is not to write your own custom create method, but rather to teach your serializer how to accept the date format you use, e.g.
class SimpleSerializer4(something_here):
...
start_dt = serializers.DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=['%Y-%m-%d %H:%M:%S'])
...
Then later all you have to do is to add CreateModelMixin to you ViewSet and it should work, e.g.
from rest_framework.mixins import CreateModelMixin
...
class DateTimeViewSet(DateTimeMixin, CreateModelMixin, generics.BulkModelViewSet):
...
Docs on DateTimeField here
Docs on extending view sets here
And I found it often very helpful to look how things are done in the rest_framework itself, so here is the link to the source on GitHub

Accept datetime isoformat in Django form

There is an API server build on top of Django. Model Author has DateTimeField which is serialised to iso formatted string, such as 2015-04-10T07:28:45.571039+00:00.
class Author(models.Model):
created = models.DateTimeField()
Client is implemented in Javascript. It builds model with given datetime field, so to update the model, I would like to use field in the same format.
To process request on server side, there is a model form
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
POST/PUT request handler raises exception: "Enter a valid date/time.". Reason for that is allowed DATETIME_INPUT_FORMATS. There is no way to accept timezone information with colon inside.
Given that I don't want to remove timezone support (USE_TZ) and would like to accept data with timezone information, what is the way to implement server side? I currently have two solutions: 1) exclude field at all 2) use CharField in form with dateutil.parser.parse function inside form clean method.
Do you have any suggestions how to deal with it?
Python (pre 3.7) can't handle an ISO8601 string with +00:00. The colon, specifically, is the problem (see: https://bugs.python.org/issue15873, https://bugs.python.org/msg169952). Django does, however, provide a solution via django.utils.dateparse.parse_datetime (see: https://stackoverflow.com/a/30449246/5043802)
To solve this problem, you can roll out your own DateTimeField that uses parse_datetime instead.
https://gist.github.com/elnygren/da7a1b7496a598f95bc8d8277f420b2d
from django import forms
from django.utils.dateparse import parse_datetime
from django.utils.encoding import force_str
from django.forms.widgets import DateTimeInput
from django.utils.translation import gettext_lazy as _, ngettext_lazy
class ISODateTimeField(forms.Field):
"""DateTimeField that uses django.utils.dateparse.parse_datetime.
More precisely, this DateTimeField accepts ISO 8601 datetime strings
that specify timezone with +00:00 syntax.
https://en.wikipedia.org/wiki/ISO_8601
https://code.djangoproject.com/ticket/11385
https://bugs.python.org/issue15873
https://bugs.python.org/msg169952
"""
widget = DateTimeInput
default_error_messages = {
'invalid': _('Enter a valid date/time.'),
}
def to_python(self, value):
value = value.strip()
try:
return self.strptime(value, format)
except (ValueError, TypeError):
raise forms.ValidationError(self.error_messages['invalid'], code='invalid')
def strptime(self, value, format):
""" stackoverflow won't let me save just an indent! """
return parse_datetime(force_str(value))
Your storage is unlikely to accept timezone aware datetimes, is your django app set up for timezones? from the docs:
When support for time zones is enabled, Django stores datetime
information in UTC in the database, uses time-zone-aware datetime
objects internally, and translates them to the end user’s time zone in
templates and forms.
also if you're not using postgresql:
Other backends store datetimes without time zone information. If you
switch from USE_TZ = False to USE_TZ = True, you must convert your
data from local time to UTC – which isn’t deterministic if your local
time has DST.
Bascially it's not completely done for you just be setting USE_TZ in settings.py
i use #elnygren code and i have a bug with a form that ISODateTimeField field is not required, it tries to validate all the time, i fix this way:
def to_python(self, value):
if value in self.empty_values:
return None
value = value.strip()
for _format in self.input_formats:
try:
return self.strptime(value, _format)
except (ValueError, TypeError):
continue
raise forms.ValidationError(self.error_messages["invalid"], code="invalid")

Categories