Accept datetime isoformat in Django form - python

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")

Related

Django datetime format different from DRF serializer datetime format

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__'

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.

DRF - filter list by DateField

I have a Ride model:
class Ride(models.Model):
driver = models.ForeignKey('auth.User', related_name='rides_as_driver')
destination=models.ForeignKey(Destination, related_name='rides_as_final_destination')
leaving_time=models.TimeField()
leaving_date=models.DateField(default=datetime.date.today)
num_of_spots=models.IntegerField()
passengers=models.ManyToManyField('auth.User', related_name="rides_as_passenger")
mid_destinations=models.ManyToManyField(Destination, related_name='rides_as_middle_destination')
and I am trying to filter the rides_as_driver field by today's date:
def get(self, request):
user=self.request.user
driverRides = user.rides_as_driver.filter(leaving_time=datetime.date.today)
The filter line throws an exception, saying:
RemovedInDjango19Warning: Passing callable arguments to queryset is deprecated.
value, lookups, used_joins = self.prepare_lookup_value(value, lookups, can_reuse, allow_joins)
I also tried with get: driverRides = user.rides_as_driver.get(leaving_time=datetime.date.today), didn't work.
How do I filter a list of objects by field value?
Thanks!
First, leaving_time is a TimeField which stores datetime.time values, while you are trying to filter by a datetime.datetime object. You have leaving_date in your code which you should apparently filter by instead.
Second, the error says that you are passing a function (datetime.date.today) as a filter argument and this is dropped in Django 1.9.
So what you want to do is:
driverRides = user.rides_as_driver.get(leaving_date=datetime.datetime.now().date())
Also check out documentation on Time zones if you have to handle users from multiple time zones in your application.

django timeuntil ignoring timezone

I am testing django 1.4 features. for me the most important is the TZ control.
I am using PG and the DateTimeField is working perfect saving the datetime having in count settings.TIME_ZONE also Im setting other differents timezones with pytz and looks good.
but the problem is with timesince tag, is ignoring the TZ:
from django.utils import timezone
from django.utils.timesince import timeuntil
class Foo(models.Model):
date = models.DateTimeField()
def remaining(self):
res = timeuntil(self.date, datetime.datetime.now(tz=timezone.get_current_timezone()))
return res
foo.remaining() is always returning the remaining count with the default timezone.
thanks in advance.

Django, how to see session data in the admin interface

I'm using Django sessions and I would like a way of seeing the session data in the admin interface. Is this possible?
I.e. for each session I want to see the data stored in the session database (which is essentially a dictionary as far as I can gather).
Currently I can just see a hash in the Session data field, such as:
gAJ9cQEoVQ5zb3J0aW5nX2Nob2ljZXECVQJQT3EDVQxnYW1lc19wbGF5ZWRxBH1xBVgLAAAAcG9z
dG1hbi1wYXRxBksDc1UKaXBfYWRkcmVzc3EHVQkxMjcuMC4wLjFxCFUKdGVzdGNvb2tpZXEJVQZ3
b3JrZWRxClUKZ2FtZV92b3Rlc3ELfXEMdS4wOGJlMDY3YWI0ZmU0ODBmOGZlOTczZTUwYmYwYjE5
OA==
I have put the following into admin.py to achieve this:
from django.contrib.sessions.models import Session
...
admin.site.register(Session)
In particular I was hoping to be able to see at least an IP address for each session. (Would be nice too if I could count how many sessions per IP address and order the IPs based on number of sessions in total for each.)
Thank you for your help :-)
You can do something like this:
from django.contrib.sessions.models import Session
class SessionAdmin(ModelAdmin):
def _session_data(self, obj):
return obj.get_decoded()
list_display = ['session_key', '_session_data', 'expire_date']
admin.site.register(Session, SessionAdmin)
It might be even that get_decoded can be used directly in list_display. And in case there's some catch that prevents this from working ok, you can decode the session data yourself, based on the linked Django source.
Continuing from Tomasz's answer, I went with:
import pprint
from django.contrib.sessions.models import Session
class SessionAdmin(admin.ModelAdmin):
def _session_data(self, obj):
return pprint.pformat(obj.get_decoded()).replace('\n', '<br>\n')
_session_data.allow_tags=True
list_display = ['session_key', '_session_data', 'expire_date']
readonly_fields = ['_session_data']
exclude = ['session_data']
date_hierarchy='expire_date'
admin.site.register(Session, SessionAdmin)
Session data is contained in a base64 encoded pickled dictionary. That's is what you're seeing in the admin because that data is stored in a TextField in the Session model.
I don't think any distributed django code stores the ip address in the session but you could do it yourself if you can access it.
In order to display the real session information, you may write your own form field that presents the decoded information. Keep in mind that you'll have to also overwrite the save method if you want to modify it. You can take a look at the encode and decode methods in django/contrib/sessions/models.py.
EB's otherwise great answer left me with the error "Database returned an invalid value in QuerySet.dates(). Are time zone definitions and pytz installed?". (I do have db tz info and pytz installed, and my app uses timezones extensively.) Removing the 'date_hierarchy' line resolved the issue for me. So:
import pprint
from django.contrib.sessions.models import Session
class SessionAdmin(admin.ModelAdmin):
def _session_data(self, obj):
return pprint.pformat(obj.get_decoded()).replace('\n', '<br>\n')
_session_data.allow_tags=True
list_display = ['session_key', '_session_data', 'expire_date']
readonly_fields = ['_session_data']
exclude = ['session_data']
admin.site.register(Session, SessionAdmin)
Adding to previous answers, We can also show the user for that session which is helpful for identifying the session of users.
class SessionAdmin(admin.ModelAdmin):
def user(self, obj):
session_user = obj.get_decoded().get('_auth_user_id')
user = User.objects.get(pk=session_user)
return user.email
def _session_data(self, obj):
return pprint.pformat(obj.get_decoded()).replace('\n', '<br>\n')
_session_data.allow_tags = True
list_display = ['user', 'session_key', '_session_data', 'expire_date']
readonly_fields = ['_session_data']

Categories