Метод Django to_python пользовательского models.DateTimeField не получает правильное значение

У меня есть ModelForm, основанная на модели, которая включает в себя настроенное поле DateTimeField. Единственной настройкой является переопределение метода to_python, чтобы я мог преобразовать строку в формате 'AM/PM' в формат 24-часового времени, который Django будет проверять. Я указал виджет DateTimeInput на форме, чтобы я мог форматировать в нотацию AM/PM (format=('%m/%d/%Y %H:%M %p')), которую я инициализирую текущим временем. Форма отображается правильно, например, '10/10/2021 04:33 PM', но когда вызывается функция to_python пользовательского поля, переданное ей значение не включает время; только дату. Также я не понимаю, почему метод to_python этого поля вызывается (дважды), когда слушатель другого поля создает вызов AJAX, а не когда нажимается кнопка Submit. Я просмотрел данные запроса, отправленные при нажатии кнопки Submit, и в них действительно есть полное '10/10/2021 04:33 PM'. (Я понимаю, что фактическое преобразование в to_python для обработки AM/PM не выполняется вообще; я еще не дошел до этого). Я пробовал использовать несколько популярных методов подбора времени даты, но ни один из них не решил эту проблему.

models.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})

forms.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'}),
        }

views.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 %}

вывод терминала:

  1. when form is opened:
[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
  1. when user makes a selection in a choice field dropdown causing AJAX call:
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
  1. when user clicks Submit button:
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

и форма выдает сообщение об ошибке: "Введите действительную дату/время."

Проблема оказалась в том, что виджет DateTimeInput вызывал value_from_datadict до того, как был вызван метод to_python поля. Поэтому docs немного вводят в заблуждение, когда говорят: "Метод 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))
Вернуться на верх