Zope.Schema/Plone - How can I set the value of a Datetime field

ghz 7hours ago ⋅ 3 views

Zope.Schema/Plone - How can I set the value of a Datetime field in an updateWidget function?

On a plone site I am working on, I have a form that is used to edit records of objects stored mapped to a table in the backend database. In the interface class, one of the fields is a schema.Datetime field.

class ICalibration(Interface):
"""Interface class for calibration records
"""
...
    Last_Calibration = schema.Datetime(title=u"Last Calibration"
                                       description=u"date of last calibration",


                                      )
...

In my updateWidgets function, in the edit form, I try to set the value of the widget,

class EditCalibration(form.Form):
    grok.name('edit-calibration')
    grok.require('zope2.View')
    grok.context(ISiteRoot)

    def updateWidgets(self):
        super(EditCalibration, self).updateWidgets()
        id = self.request.get('id',None)

        if id:
            currentCal = session.query(Calibration).filter(Calibration.Calibration_ID == id).one()

            ...
            self.widgets["Last_Calibration"].value = currentCal.Last_Calibration
        ...

but I get this error:

"TypeError: 'datetime.datetime' object has no attribute 'getitem'.

I did try some things that were sort of interesting. I printed the value of cal.Last_Calibration and its coming out as the date I put in when I added the record. I tried printing the type of object that cal.Last_Calibration was and it was python's datetime (as opposed to zope's I believe?). I tried setting the field equal to today's date: datetime.today() and got the same error. Just for kicks, I also tried converting currentCal.Last_Calibration to a string and passing it into the field, although that just put random numbers inside the fields.

For the record, I imported python's datetime as:

from datetime import datetime

Also, adding a record/calibration works fine, so its not an issue with the database or the sqlalchemy schema I am using.

If it's possible, what is the appropriate way of setting the schema field's value in a updateWidgets function?

Should I use a different widget? If so, all I really need for my form is the date. The add/update function will take a datetime object, so I could create a datetime object from the data, regardless of the type I believe.

Answer

The issue you're encountering is likely because the Last_Calibration field in your form is expecting a zope.schema.datatypes.DateTime object, but you are passing a native Python datetime.datetime object from SQLAlchemy. This mismatch causes the error you're seeing, where Python tries to access the datetime.datetime object using dictionary-style indexing (getitem), which is not possible.

To fix this issue, you need to ensure that you're passing the correct type to the form widget, which expects a datetime.datetime object but wrapped in a zope.schema.datatypes.DateTime format if necessary.

Here’s how to address this:

Step 1: Ensure that currentCal.Last_Calibration is a datetime object

First, confirm that currentCal.Last_Calibration is indeed a datetime.datetime object and not something else like a string. You can print the type to verify this:

print(type(currentCal.Last_Calibration))

If it prints something like <class 'datetime.datetime'>, you're good to go. If it's something like a string, you will need to convert it into a datetime.datetime object first, for example:

from datetime import datetime
# If it's a string in the form 'YYYY-MM-DD HH:MM:SS'
currentCal.Last_Calibration = datetime.strptime(currentCal.Last_Calibration, "%Y-%m-%d %H:%M:%S")

Step 2: Set the Last_Calibration widget value

Now that you’ve confirmed that currentCal.Last_Calibration is a datetime object, the next step is to properly set the value for the Last_Calibration widget. The updateWidgets function allows you to set values for the widgets programmatically.

The following should work as expected:

from datetime import datetime

class EditCalibration(form.Form):
    grok.name('edit-calibration')
    grok.require('zope2.View')
    grok.context(ISiteRoot)

    def updateWidgets(self):
        super(EditCalibration, self).updateWidgets()
        id = self.request.get('id', None)

        if id:
            # Fetch the record using the ID
            currentCal = session.query(Calibration).filter(Calibration.Calibration_ID == id).one()

            # Assuming currentCal.Last_Calibration is a datetime object:
            self.widgets["Last_Calibration"].value = currentCal.Last_Calibration

Here, we directly assign the datetime object (currentCal.Last_Calibration) to the widget’s value.

Step 3: Handle conversion between different formats

In case you are working with forms and widgets where the date might be in a different format (for example, string format), you can convert it like so:

from datetime import datetime

class EditCalibration(form.Form):
    grok.name('edit-calibration')
    grok.require('zope2.View')
    grok.context(ISiteRoot)

    def updateWidgets(self):
        super(EditCalibration, self).updateWidgets()
        id = self.request.get('id', None)

        if id:
            # Fetch the record using the ID
            currentCal = session.query(Calibration).filter(Calibration.Calibration_ID == id).one()

            # If the currentCal.Last_Calibration is a string, convert it to a datetime object
            if isinstance(currentCal.Last_Calibration, str):
                currentCal.Last_Calibration = datetime.strptime(currentCal.Last_Calibration, "%Y-%m-%d %H:%M:%S")

            # Now set the widget's value to the datetime object
            self.widgets["Last_Calibration"].value = currentCal.Last_Calibration

In this case, if the Last_Calibration field is a string, it will be converted to a datetime.datetime object using datetime.strptime().

Step 4: Handle empty fields

If there is a possibility that currentCal.Last_Calibration could be None (i.e., no value set), you can add a check for this and set the widget value to None or leave it empty:

if currentCal.Last_Calibration:
    self.widgets["Last_Calibration"].value = currentCal.Last_Calibration
else:
    self.widgets["Last_Calibration"].value = None

Conclusion

In summary, ensure that you are passing a valid datetime.datetime object to the Last_Calibration widget. If necessary, convert any string representations of the datetime to the appropriate datetime object before setting it to the widget. This will resolve the issue you're seeing with the getitem error.