django - 自定义模型的 Django to_python 方法。DateTimeField 没有收到正确的值
问题描述
我有一个基于包含自定义 DateTimeField 的模型的 ModelForm。唯一的自定义是覆盖 to_python 方法,以便我可以将“AM/PM”格式的字符串转换为 Django 将验证的 24 小时时间格式。我在表单上指定了一个 DateTimeInput 小部件,因此我可以格式化为 AM/PM 表示法(format=('%m/%d/%Y %H:%M %p')),并将其初始化为当前日期时间。表单显示正确,例如 '10/10/2021 04:33 PM' 但是当调用自定义字段的 to_python 函数时,传递给它的值不包括时间;只有日期。另外,我不明白为什么当另一个字段上的侦听器创建 AJAX 调用而不是单击提交按钮时,该字段的 to_python 方法被调用(两次)。我查看了单击提交时发送的请求数据,它确实有完整的“10/10/2021 04:33 PM”。(我意识到在 to_python 中处理 AM/PM 的实际转换根本没有完成;我还没有做到这一点)。我尝试使用几个流行的日期时间选择器,但没有一个能解决这个问题。
模型.py:
class ampmDateTimeField (models.DateTimeField):
def to_python(self, value):
print ('initial_date_time = ',value)
# Here is where the code will go to do the actual conversion
# for now, just see what the super's conversion is doing
converted_date_time = super().to_python(value)
print ('converted_date_time = ',converted_date_time)
return converted_date_time
class Encounter(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE
encounter_date = ampmDateTimeField()
animal = models.ForeignKey(Animal, null=True, on_delete=models.SET_NULL)
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
handling_time = models.BigIntegerField(blank=True, null=True)
crate_time = models.BigIntegerField(blank=True, null=True)
holding_time = models.BigIntegerField(blank=True, null=True)
comments = models.TextField(blank=True, null=True)
def __str__(self) -> str:
return (self.user.username + '/' + self.animal.Name)
def get_absolute_url(self):
return reverse("encounter-detail", kwargs={"pk": self.pk})
表格.py:
class Open_Encounter_Form(ModelForm):
numPerDayField = CharField(label='Today\'s uses')
#aNumField = CharField(name='Today',max_length=4)
class Meta:
model = Encounter
fields = ['encounter_date','animal','numPerDayField','user','handling_time','crate_time','holding_time','comments']
widgets = {
'comments': Textarea(attrs={'rows': 4, 'cols': 40}),
'encounter_date': DateTimeInput(format=('%m/%d/%Y %H:%M %p'), attrs={'size':'24'}),
}
视图.py:
def open_encounter(request):
if request.method == 'POST':
print('request: ', request.POST)
form = Open_Encounter_Form(request.POST)
if form.is_valid():
#save the data
aRecord=form.save()
return HttpResponseRedirect(reverse('index'))
else:
current_user = request.user
form=Open_Encounter_Form(initial={'encounter_date': datetime.datetime.today(),'user': current_user})
return render(request, 'openencounter.html', {'form': form})
openencounter.html:
{% extends "base_generic.html" %}
{% block content %}
<h1>New Encounter</h1>
<form action="/encounters/openencounter/" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$("#id_animal").change(function () {
var url = "{% url 'animal-data-API' %}"
console.log('url:',url)
var animalId = $("#id_animal").val();
console.log('animalID: ', animalId);
$.ajax({
url: url,
data: {
'animal': animalId
},
success: function (data) {
console.log('returned data:',data);
uses = data['uses'];
theMax = data['theMax'];
if (uses > theMax) {
$("#id_numPerDayField").css("color", "red")
} else if (uses == theMax) {
$("#id_numPerDayField").css("color", "blue")
} else {
$("#id_numPerDayField").css("color", "black")
}
theStr = uses.toString() + ' (out of ' + theMax.toString() + ')'
$("#id_numPerDayField").val(theStr)
}
});
})
</script>
{% endblock %}
终端输出:
- 打开表单时:
[10/Oct/2021 13:16:25] "GET /encounters/openencounter/ HTTP/1.1" 200 4508
[10/Oct/2021 13:16:25] "GET /static/styles.css HTTP/1.1" 200 77
- 当用户在导致 AJAX 调用的选择字段下拉列表中进行选择时:
initial_date_time = 2021-10-10
converted_date_time = 2021-10-10 00:00:00
initial_date_time = 2021-10-10 00:00:00
converted_date_time = 2021-10-10 00:00:00
[10/Oct/2021 13:18:00] "GET /encounters/api/load_animal_uses/?animal=5 HTTP/1.1" 200 24
- 当用户单击提交按钮时:
request: <QueryDict: {'csrfmiddlewaretoken': ['LOH28mXa5QTdomcPdJBPNbuJisY6TgykeI6yYzKseujglK7PX1HtoOm4QWmjlVCN'], 'encounter_date': ['10/10/2021 13:18 PM'], 'animal': ['5'], 'numPerDayField': ['0 (out of 4)'], 'user': ['1'], 'handling_time': [''], 'crate_time': [''], 'holding_time': [''], 'comments': ['']}>
[10/Oct/2021 13:19:29] "POST /encounters/openencounter/ HTTP/1.1" 200 4590
[10/Oct/2021 13:19:29] "GET /static/styles.css HTTP/1.1" 200 77
并且表单显示错误消息:“输入有效的日期/时间。”
解决方案
问题原来是在调用字段的 to_python 方法value_from_datadict
之前调用了 DateTimeInput 小部件。因此,当文档说: “字段上的 to_python() 方法是每次验证的第一步”时,文档有点误导。 即使 DateTimeInput 小部件接受 AM/PM 格式字符串并根据该字符串显示日期时间,它也会删除时间部分并仅返回日期。所以解决方案是将输入小部件子类化并为其编写一个新方法。这是我的初稿,它有效:value_from_datadict
class ampmDateTimeInput(DateTimeInput):
def value_from_datadict(self, data, files, name):
theRawDate = data['encounter_date']
theConvertedDate = theRawDate
# check if there is an AM or PM on the end
if (len(theRawDate) > 2):
theStr = theRawDate[-2:]
print ('theStr: ', theStr)
if (theStr == 'PM'):
#convert to 24 hr format
#get the hours
theHrsStr = theRawDate[12:14]
theHrs = int(theHrsStr)
theHrs = theHrs + 12
#pad with leading zero if needed
theHrsStr = str(theHrs)
if (len(theHrsStr) == 1):
theHrsStr = '0' + theHrsStr
theConvertedDate = theConvertedDate[0:11] + theHrsStr + theConvertedDate[14:17]
elif (theStr == 'AM'):
#just strip the AM
theConvertedDate = theConvertedDate[0:17]
#else just pass the existing string; the user has removed the AM or PM manually
# create a mutable instance of the data
aNewData = data.copy()
aNewData['encounter_date'] = theConvertedDate
return(super().value_from_datadict(aNewData, files, name))
推荐阅读
- python - 从 Heroku 读取 gmail-imap 的访问权限刚刚开始失败
- oracle - Oracle 中的 SQLCODE 等价于 Postgres
- c++ - 静态成员变量的链接错误。在模板结构中
- jquery - 制作暗模式时出现 Jquery 问题
- node.js - 如何在后台以编程方式执行 Node 应用程序
- c++ - “新”运算符未分配足够的内存
- sql - 优化按日期时间字段过滤的 Postgresql 查询大于
- sql - 如何通过在表 A 的列中查找逗号分隔值来从表 B 返回值
- configuration - 在 Jacoco 覆盖率报告中排除文件
- php - 多个 mysqli 准备好的查询导致最新执行多次