I'm using Tastypie to create a REST API for a Django app and want to be able to create new objects and related objects in one POST. The related objects are specified by a name used to look them up, and I want to create new ones if the name is not found.
Given Django models like this:
class Product(Model):
name = CharField(max_length=32)
class Release(Model):
product = ForeignKey(to=Product, related_name='releases')
version = CharField(max_length=32)
And these Tastypie resources:
class ProductResource(ModelResource):
class Meta:
queryset = Product.objects.all()
resource_name = 'product'
class ReleaseResource(ModelResource):
class Meta:
queryset = Release.objects.all()
resource_name = 'release'
def hydrate_product(self, bundle):
"""Replace product name with id of existing/created one."""
product = Product.objects.get_or_create(name=bundle.data['product'])
bundle.data['product'] = product.id
return bundle
And an empty database
When I POST this data to my tastypie REST API:
POST /api/release {
"product": "Cool Widget",
"version": "1.2.3",
}
Then I want these model objects to be created:
product = Product(name="Cool Widget")
release = Release(product=product, version="1.2.3")
But I get an exception something like this:
IntegrityError: null value in column "product_id" violates not-null constraint
DETAIL: Failing row contains (1, null, 1.2.3).
And the hydrate_product() method is not called.
When I add this class property to ReleaseResource:
product = fields.ToOneField(ProductResource, 'product')
Then I get something like this:
NotFound: An incorrect URL was provided 'Cool Widget' for the 'ProductResource' resource.
How do I replace the product name in the bundle with the URI of the created/existing Product object having that name?
I think my mistake is in trying to modify the purpose of relation fields. They should be left to work normally if my resource actually includes them.
I should just declare ReleaseResource.product
as a CharField
and implement Release.obj_create
to use Product.objects.get_or_create
for the related Product
object when creating the new Release
.
class ReleaseResource(ModelResource):
product = CharField(attribute='product__name')
class Meta:
queryset = Release.objects.all()
resource_name = 'release'
def obj_create(self, bundle, **kwargs)
product = Product.objects.get_or_create(name=bundle.data['product'])[0]
super(ModelResource, self).obj_create(bundle, product=product, **kwargs)
It doesn't seem clear enough in the doco that no relation fields are populated on a ModelResource
automatically, you have to explicitly declare all relation fields, not just reverse ones.