TemporalWolf
TemporalWolf

Reputation: 7952

MySQL On Update not triggering for Django/TastyPie REST API

We have a resource table which has a field last_updated which we setup with mysql-workbench to have the following properties:

Datatype: TIMESTAMP

NN (NotNull) is checked

Default: CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

When I modify a row through the workbench and apply it, the last_updated field properly updates.

When I use the REST api we've setup, and issue a put:

update = requests.put('http://127.0.0.1:8000/api/resources/16',
            data=json.dumps(dict(status="/api/status/4", timeout=timeout_time)),
            headers=HEADER)

I can properly change any of the values (including status and timeout, and receive a 204 response), but last_updated does not update.

Django's model documentation says in this case it should be sending an UPDATE.

Anyone have and ideas on why it's missing these updates?

I can provide further details regarding our specific Django/tastypie setup, but as long as they are issuing an UPDATE, they should be triggering the databases ON UPDATE.

Upvotes: 0

Views: 180

Answers (2)

TemporalWolf
TemporalWolf

Reputation: 7952

With the added information from spencer7593's answer, I was able to track down how to do this through tastypie:

The BaseModelResource.save() (from tastypie/resources.py):

def save(self, bundle, skip_errors=False):
    if bundle.via_uri:
        return bundle

    self.is_valid(bundle)

    if bundle.errors and not skip_errors:
        raise ImmediateHttpResponse(response=self.error_response(bundle.request, bundle.errors))

    # Check if they're authorized.
    if bundle.obj.pk:
        self.authorized_update_detail(self.get_object_list(bundle.request), bundle)
    else:
        self.authorized_create_detail(self.get_object_list(bundle.request), bundle)

    # Save FKs just in case.
    self.save_related(bundle)

    # Save the main object.
    obj_id = self.create_identifier(bundle.obj)

    if obj_id not in bundle.objects_saved or bundle.obj._state.adding:
        bundle.obj.save()
        bundle.objects_saved.add(obj_id)

    # Now pick up the M2M bits.
    m2m_bundle = self.hydrate_m2m(bundle)
    self.save_m2m(m2m_bundle)
    return bundle

Needs to be overridden in your class, so that you can change the Django save(), which has the update_fields parameter we want to modify:

    if obj_id not in bundle.objects_saved or bundle.obj._state.adding:
        bundle.obj.save()

To, for example:

class ResourcesResource(ModelResource):
    # ...
    def save(self, bundle, skip_errors=False):
        # ...
        if obj_id not in bundle.objects_saved or bundle.obj._state.adding:
            resource_fields = [field.name for field in Resources._meta.get_fields() 
                               if not field.name in ['id', 'last_updated']]
            bundle.obj.save(update_fields=resource_fields)
        # ...

This properly excludes the last_updated column from the sql UPDATE.

Upvotes: 0

spencer7593
spencer7593

Reputation: 108480

I suspect that the UPDATE statement issued by Django may be including an assignment to the last_updated column. This is just a guess, there's not enough information provided.

But if the Django model contains the last_updated column, and that column is fetched from the database into the model, I believe a save() will assign a value to the last_updated column, in the UPDATE statement.

https://docs.djangoproject.com/en/1.9/ref/models/instances/#specifying-which-fields-to-save


Consider the behavior when we issue an UPDATE statement like this:

 UPDATE mytable
    SET last_updated = last_updated
      , some_col = 'some_value'
  WHERE id = 42 

Because the UPDATE statement is assigning a value to the last_updated column, the automatic assignment to the timestamp column won't happen. The value assigned in the statement takes precedence.

To get the automatic assignment to last_updated, that column has to be omitted from the SET clause, e.g.

 UPDATE mytable
    SET some_col = 'some_value'
  WHERE id = 42 

To debug this, you'd want to inspect the actual SQL statement.

Upvotes: 1

Related Questions