如何配置Tastypie使字段视为唯一?
我该如何配置Tastypie,让某个字段被视为唯一的呢?我希望如果我尝试插入重复的条目,系统能给我一个非500的错误提示(可能是409冲突?)作为回应。
我查阅了文档,感觉应该很简单,但不知道为什么我没有得到我期待的回应。
这是文档链接:
http://readthedocs.org/docs/django-tastypie/en/latest/fields.html?highlight=unique
下面是示例代码:
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)
我收到的错误信息是这样的(使用curl访问我的本地服务):
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"}
当我从Company模型中移除unique=True, db_index=True,
时,我没有收到完整性错误,而是创建了一个新的重复资源。再次强调,这不是我期待的结果,因为我希望唯一性能够进行一些验证,并导致一些非500的响应。
3 个回答
我想说的是,我找到了一种稍微不同的解决方案,对我来说效果更好。这是基于thoslin的回答。
我发现e.args[0] == 1062这个检查对我来说不管用。我很确定这是MySQL的错误,而我用的是Postgres。
我还把这个实现放在了obj_create方法里,这样它就可以处理所有对象的创建,而不仅仅是通过post_list创建的对象。
from tastypie.exceptions import ImmediateHttpResponse
from tastypie.http import HttpConflict
from django.db import IntegrityError
...
class MyBaseResource(ModelResource):
def obj_create(self, bundle, **kwargs):
try:
return super(MyBaseResource, self).obj_create(bundle, **kwargs)
except IntegrityError, e:
if e.args[0] == 1062 or e.args[0].startswith('duplicate key'):
raise ImmediateHttpResponse(HttpConflict())
...
class MyResource(MyBaseResource):
[usual resource stuff]
我今天也遇到了同样的问题。这里是我解决它的方法:
在你的资源定义中,重写 [request_method]_[request_type] 方法。例如,我在 FooResource 中重写了 post_list:
def post_list(self, request, **kwargs):
from django.db import IntegrityError
try:
return super(FooResource, self).post_list(request, **kwargs)
except IntegrityError, e:
if e.args[0] == 1062:
return http.HttpConflict()
希望这个方法对你有用。
这是我解决问题的方法:
根据验证的文档,我实现了一个自定义的验证器,用来检查字段的唯一性。
在CompanyResource类中,我在类的元信息里添加了一个CustomValidation。我把CustomValidation的实现放在了一个叫validations.py的文件里。如果isValid返回错误,API就会返回一个400的错误码,并且会把错误信息包含在返回的消息里。
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