Xây dựng nhanh chóng API với Django Tastypie [Phần 3]
Bài đăng này đã không được cập nhật trong 6 năm
Tiếp theo phần 1 và phần 2, phần 3 này mình sẽ giới thiệu về cách sử dụng Non-ORM Data Sources
trong Tastypie
Requirement trước khi đọc phần này là bạn phải đọc qua phần 1 tại đây
Okay, vì sao mình lại giới thiệu phần này? Khi bạn đã cảm thấy quá bí bách với Tastypie. Khi bạn cảm thấy quá nhàm chán. Khi bạn không muốn response có những dữ liệu mà thông qua tính toán không lưu trong model, blabla...
Tastypie định hướng được xây dựng để xử lý Non-ORM
data. Không dài dòng nữa, bắt đầu nào!
Approach
Tastypie cung cấp class Resource
. Class này cung cấp rất nhiều thứ sẵn có cho bạn tương tự như ModelResource
:
authentication
authorization
caching
serialization
...
Chúng ta sẽ có 9 method cần phải implement nếu cần full read-write một API:
detail_uri_kwargs
get_object_list
obj_get_list
obj_get
obj_create
obj_update
obj_delete_list
obj_delete
rollback
Tất nhiên, nếu bạn không cần full read-write, bạn có thể chỉ cần implement một số methods thôi.
Usage
Bài toán: Tạo endpoint ProfileUser đáp ứng các nhu cầu:
- Hiển thị list profile
- Hiển thị một profile
- Create mới một profile
- Update một profile
- Delete một profile
Đầu tiên mình tạo 2 class:
class ProfileObject(object):
def __init__(self, initial=None):
self.__dict__['_data'] = {}
if hasattr(initial, 'items'):
self.__dict__['_data'] = initial
def __getattr__(self, name):
return self._data.get(name, None)
def __setattr__(self, name, value):
self.__dict__['_data'][name] = value
def to_dict(self):
return self._data
class ProfileResource(Resource):
user_id = IntegerField(attribute='user_id')
username = CharField(attribute='username')
slug = CharField(attribute='slug')
avatar = CharField(attribute='avatar')
email = CharField(attribute='email')
phone = CharField(attribute='phone')
star = IntegerField(attribute='star')
class Meta:
resource_name = 'profile'
object_class = ProfileObject
Trong ví dụ trên:
ProfileObject
: class này để chúng ta đẩy dữ liệu vào và lấy dữ liệu raMessageResource
: class này sẽ define các field tương ứng với data type của từng field
Ok. Mình sẽ đi vào từng feature:
List profile
def obj_get_list(self, bundle, **kwargs):
return self.get_object_list(bundle.request)
def get_object_list(self, request):
fix_data = [
{
"user_id": 1,
"username": "minhhahao",
"slug": "/minhhahao",
"avatar": "https://google.com",
"email": "minhhahao@abc.com",
"phone": "0123456789",
"star": 9999
},
{
"user_id": 2,
"username": "ha.hao.minh",
"slug": "/ha.hao.minh",
"avatar": "https://google.com",
"email": "ha.hao.minh@abc.com",
"phone": "0987654321",
"star": 8888
},
{
"user_id": 3,
"username": "nguyenthivan",
"slug": "/nguyenthivan",
"avatar": "https://google.com",
"email": "nguyenthivan@abc.com",
"phone": "0987654321123",
"star": 7777
}
]
results = [ProfileObject(initial=data) for data in fix_data]
return results
Ở đây mình sẽ override 2 methods mình đã liệt kê bên đầu bài đó là: get_object_list
, obj_get_list
.
Biến fix_data
là dữ liệu mình hard code ra. Sau đó mình chuyển dữ liệu đó vào ProfileObject
. Cuối cùng trả về list ProfileObject
.
Ở đây, với từng bài toán cụ thể bạn có thể query các kiểu để có dữ liệu. Vì là demo nên mình sử dụng fix data.
Ngoài ra, do mặc định Tastypie, get list object sẽ luôn trả về resource_uri
. Nhưng mình không muốn hiển thị cái này ở response. Vậy nên mình sẽ disable nó đi :
...
class Meta:
resource_name = 'profile'
object_class = ProfileObject
include_resource_uri = False
....
Nếu bạn muốn hiển thị resource_uri
để có thể dẫn link vào detail. Bạn cần override lại method thứ 3: detail_uri_kwargs
def detail_uri_kwargs(self, bundle_or_obj):
kwargs = {}
if isinstance(bundle_or_obj, Bundle):
kwargs['pk'] = bundle_or_obj.obj.user_id
else:
kwargs['pk'] = bundle_or_obj.user_id
return kwargs
Ở đây, url detail mình trả lại lấy pk
dựa vào user_id
Ảnh demo:
Detail profile
def obj_get(self, bundle, **kwargs):
# Todo: handle get data using bundle and kwargs
user_profile = kwargs.get("pk")
if user_profile and int(user_profile) == 1:
fix_data = {
"user_id": 1,
"username": "minhhahao",
"slug": "/minhhahao",
"avatar": "https://google.com",
"email": "minhhahao@abc.com",
"phone": "0123456789",
"star": 9999
}
return ProfileObject(initial=fix_data)
Ở feature này, mình cũng override method obj_get
. Mình sử dụng kwargs.get("pk")
để get pk
từ request. Từ pk
đó bạn có thể handle những thứ tiếp theo như ý đồ của bạn.
Như mình, mình trả về đoạn fix data như trên :
Ảnh demo:
Create/Update profile
def obj_create(self, bundle, **kwargs):
# Todo: handle save data using bundle and kwargs
fix_data = {
"user_id": 1,
"username": "minhhahao",
"slug": "/minhhahao",
"avatar": "https://google.com",
"email": "minhhahao@abc.com",
"phone": "0123456789",
"star": 9999
}
bundle.obj = ProfileObject(**fix_data)
Để create được object bạn cần override lại method obj_create
. Data từ request body sẽ được Tastypie build vào trong bundle
và kwargs
.
Khi có được data input, bạn có thể tự implement theo bài toán của mình.
Còn update thì bạn sử override lại method: obj_update
. Mình có thể xử lý chung update và create vào một function
def obj_update(self, bundle, **kwargs):
return self.obj_create(bundle, **kwargs)
Ảnh demo:
Delete profile
def obj_delete_list(self, bundle, **kwargs):
bucket = self._bucket()
for key in bucket.get_keys():
obj = bucket.get(key)
obj.delete()
def obj_delete(self, bundle, **kwargs):
bucket = self._bucket()
obj = bucket.get(kwargs['pk'])
obj.delete()
Delete profile có 2 lựa chọn là xóa 1 list và xóa 1 profile. Tương ứng mình phải override 2 method: obj_delete_list
và obj_delete
Trên đây mình đã giới thiệu phần 3 về Tastypie. Trong phần tiếp theo mình sẽ giới thiệu nhiều thứ hay ho về nó nữa. Cảm ơn các bạn đã đọc!
All rights reserved