johncosta johncosta - 2 months ago 13
Python Question

How do I configure Tastypie to treat a field as unique?

How do I configure Tastypie to treat a field as unique? My expectation would be to receive some sort of non-500 error (possibly a 409 conflict?) as a response if I try to insert duplicate entries for the field marked as unique.




I've looked through the docs and it looks like it should be obvious to me, but for some reason I'm not getting the response I would expect to see.

Here's the documentation link:

http://readthedocs.org/docs/django-tastypie/en/latest/fields.html?highlight=unique




The sample code is as follows:

urls.py

v1_api = Api(api_name='v1')
v1_api.register(CompanyResource())

urlpatterns = patterns('',
(r'^api/', include(v1_api.urls)),
)


resource.py

class CompanyResource(ModelResource):

CompanyName = fields.CharField(attribute='company_name')
CompanyId = fields.CharField(attribute='company_id', unique=True)
Contact = fields.CharField(attribute='contact')
Email = fields.CharField(attribute='email')
Phone = fields.CharField(attribute='phone')

class Meta:
queryset = Company.objects.all()
authentication = BasicAuthentication()
authorization = Authorization()
allowed_methods = ['get', 'post']


models.py

class Company(models.Model):

company_name = models.TextField(default=None, blank=True, null=True)
company_id = models.CharField(default='', unique=True, db_index=True, max_length=20)
contact = models.TextField(default=None, blank=True, null=True)
email = models.EmailField(default=None, blank=True, null=True)
phone = models.TextField(default=None, blank=True, null=True)





The error I receive is the following (using curl to hit my local service):

curl --dump-header - -H "Content-Type: application/json" -X POST --user user:password --data '{"CompanyName": "company", "CompanyId": "1234567890", "Contact": "John", "Email": "example@example.com", "Phone": "555-555-5555"}' http://localhost:8000/api/v1/company/
HTTP/1.0 500 INTERNAL SERVER ERROR
Date: Thu, 15 Sep 2011 18:25:20 GMT
Server: WSGIServer/0.1 Python/2.7.1
Content-Type: application/json; charset=utf-8

{"error_message": "(1062, \"Duplicate entry '1234567890' for key 'api_company_company_id_uniq'\")",
...<snip>...
raise errorclass, errorvalue\n\nIntegrityError: (1062, \"Duplicate entry '1234567890' for key 'api_company_company_id_uniq'\")\n"}


When I remove
unique=True, db_index=True,
from the Company model, I don't receive the Integrity error, but instead, a new, duplicate resource is created. Again, this isn't the expected result as I would expect unique to preform some validation and cause some non-500 response.

Answer

Here is how I solved the problem:

Based on the documentation for validation, I was able to implement a custom validator that checked the uniqueness of the field for me. http://django-tastypie.readthedocs.org/en/latest/validation.html

In the CompanyResource, I added to the class meta a CustomValidation. I placed the implementation for CustomValidation in a validations.py file. If isValid returns errors, the api will return a 400 with the messages included in errors.

class CompanyResource(ModelResource):
    """
    CompanyIds should be unique
    """     
    CompanyName = fields.CharField(attribute='company_name')     
    CompanyId = fields.CharField(attribute='company_id', unique=True)     
    Contact = fields.CharField(attribute='contact')     
    Email = fields.CharField(attribute='email')     
    Phone = fields.CharField(attribute='phone')    

    class Meta:        
        queryset = Company.objects.all()        
        authentication = BasicAuthentication()        
        authorization = Authorization()        
        allowed_methods = ['get', 'post']                
        validation = CustomValidation()

validations.py

class CustomValidation(Validation):
    """
    The custom validation checks two things:
       1) that there is data
       2) that the CompanyId exists (unique check)
    """
    def is_valid(self, bundle, request=None):
        if not bundle.data:
            return {'__all__': 'Missing data, please include CompanyName, CompanyId, Contact, Email, and Phone.'}

        errors = {}                                    
        company_id=bundle.data.get('CompanyId', None)

        # manager method, returns true if the company exists, false otherwise
        if Company.objects.company_exists(company_id):
            errors['CompanyId']='Duplicate CompanyId, CompanyId %s already exists.' % company_id
        return errors
Comments