Nếu bạn đọc soure code của core Odoo, đôi khi bạn sẽ tìm thấy safe_eval. Bạn đã bao giờ tự hỏi, safe_eval là gì chưa? Và nó sử dụng để làm gì?
Nếu bạn đọc mã nguồn của Odoo, bạn sẽ thấy rằng, safe_eval thực ra chỉ là một hàm/module được viết bằng python.
Nếu bạn không biết, module eval được sử dụng để xử lý các biểu thức với kiểu dữ liệu là chuỗi, để chúng có thể được đọc dưới dạng
phép toán hoặc thậm chí là 1 đoạn mã python hợp lệ. Giả sử chúng ta có 1 biến với kiểu dữ liệu là chuỗi như bên dưới đây:
my_string = '100 + 5'
Bằng cách sử dụng eval chúng ta có thể biến chuỗi trên thành 1 phép toán hợp lệ như sau:
my_string = '100 + 5'
value = eval(my_string)
print(value)
Như bạn thấy kết quả của phép toán là 105 là kết quả của phép tính 100 + 5.
Module eval cũng có thể được sử dụng ở những trường hợp biểu thức được gán vào các biến như sau:
a = 100
b = 5
my_string = 'a + b'
value = eval(my_string)
Hoặc chúng ta cũng có thể biết như sau:
my_string = 'a + b'
value = eval(my_string, {'a': 100, 'b': 5})
Như mình đã viết trước đó, module safe_eval thực sự chỉ là thư viện tích hợp eval của python và sửa đổi một chút.
Do đó, đoan mã trên cũng có thể được viết bằng module safe_eval như sau:
from odoo.tools.safe_eval import safe_eval
my_string = 'a + b'
value = safe_eval(my_string, {'a': 100, 'b': 5})
Vậy sự khác biệt khi chúng ta sử dụng eval và safe_eval ở đây là gì?
safe_eval sẽ đưa vào danh sách cấm 1 số biểu thức, nhưng với eval thì với những biểu thức như vậy thì không vấn đề gì?
Hàm đầu tiên mà safe_eval đưa vào danh sách cấm là import. Mình có 1 ví dụ ở dưới đây:
my_string = "__import__('odoo').tools.float_round(a/b,pricision)"
Nhưng đối với eval biểu thức trên lại không gây ra lỗi như sau:
eval_value = eval(my_string, {'a': 15, 'b': 2, 'pricision': 0})
Nhưng nếu chuyển biểu thức này sang bên safe_eval thì lại gây ra lỗi:
safe_eval_value = safe_eval(my_string, {'a': 15, 'b': 2, 'pricision': 0}
Sẽ có lỗi như hình dưới đây
Chúng ta cũng có thể thay đổi chế độ safe_eval, theo mặc định khi chúng ta gọi safe_eval, odoo sẽ thực thi module eval của python.
Nhưng chúng ta cũng có thể thay thế module eval bằng 1 module khác, ví dụ như đoạn code dưới đây:
my_string = "c = a + b"
my_value = {'c': 1}
print('my_value==before==', my_value)
# my_value==before== {'c': 1}
safe_eval(my_string, {'a': 4, 'b': 7}, my_value, mode="exec", nocopy=True)
print('my_value==after==', my_value)
# my_value==after== {'c': 11}
Trong trường hợp bạn không rõ, nếu chúng ta truyền biểu thức c = a + b vào module eval, nó sẽ gây ra lỗi bởi vì c = a + b
không phải là 1 biểu thức. Nhưng nếu bản truyền mode=“exec thì đoạn code trên sẽ không gây ra lỗi. Nhưng với mode=“exec”
thì sẽ không trả về giá trị, do đó chúng ta phải truyền vào đối số thứ 3 là biến my_value để lưu trữ giá trị tính toán.
Lưu ý chút là nếu như bạn gọi module safe_eval ở chế độ thực thi, chúng ta phải đặt giá trị của đối số no_copy = True, nếu không
giá trị my_value sẽ không thay đổi.
Vậy, trong Odoo safe_eval được sử dụng chính để làm gì?.
Đầu tiên safe_eval được sử dụng để đánh giá, validate 1 miền chuỗi. Ví dụ trong module pos_loyalty trong odoo 14 enterprise. Hoặc trong
module mình có viết ở đây để giới hạn động quyền truy cập nút in.
Trong module này mình có viết 1 field là condition với kiểu dữ liệu là Char, trong view mình cho hiển thị lên trường này với
widget là domain mặc định của odoo. Widget này cho mọi người viết domain một cách dễ dàng và domain người dùng nhập vào vẫn được lưu dưới dạng Char
Bởi vì tên miền được lưu dưới dạng Char trong cơ sở dữ liệu, nên tất nhiên chúng ta không thể chuyển tên miền trực tiếp đến phương thức tìm kiếm hoặc
bất kỳ mật phương thức vào khác được. Vì vậy chúng ta phải thay đổi nó thành 1 domain hợp lệ là 1 List, chúng ta có thể sử dụng điều này bằng cách sử
dụng safe_eval. Tuy nhiên đối với Odoo, cách convert từ string qua List cũng không nhất quán, như ở trong module coupon việc này
sử dụng bởi module ast cũng là một module có sẵn trong python.
Một trường hợp khác mà Odoo sử dụng safe_eval đó là việc tính toán báo cáo lãi lỗ và bảng cân đối kế toán trong Odoo enterprise. Bạn có thể
tìm thấy nó theo Kế toán » Cấu hình »> Báo cáo tài chính » Lãi lỗ
Mình sẽ ví dụ thêm như sau:
my_string = 'S00006 + S00007'
Chúng ta sẽ sử lý chuỗi này, lấy nó ra để tính toán cũng như tìm kiếm. Đầu tiên mình sẽ tạo ra 1 class được kế thừa từ lớp Dict
class DataSet(dict):
def __init__(self, model, field_to_search, field_to_calculate):
super().__init__()
self.model = model
self.field_to_search = field_to_search
self.field_to_calculate = field_to_calculate
def __getitem__(self, item):
record = self.model.search([(self.field_to_search,'=',item)],limit=1)
if record:
return getattr(record, self.field_to_calculate)
else:
return 0
Sau đó khi chúng ta gọi safe_eval, chúng ta có thể sử dụng lớp Dataset làm đối số như đoạn code dưới đây
my_string = 'S00006 + S00007'
value = safe_eval(my_string,DataSet(self.env['sale.order'],'name','amount_total'), nocopy=True)
Khi safe_Eval cố gắng lấy giá trị biểu thức, safe_Eval sẽ kích hoạt phương thức get_item_. Đây là lý do chúng ta ghi đè phương thức để này để trả về giá trị động.
Nếu chúng ta sử dụng safe_eval cho các mục đích khác nhau, chẳng hạn như lấy giá trị tổng phụ của đơn hàng, như đoạn code dưới đây:
my_string = 'P00006 + P00007'
value = safe_eval(my_string,DataSet(self.env['purchase.order'],'name','amount_untaxed'), nocopy=True)
Đó là tất cả những gì mình viết về sale_eval, ngay bản thân tên của safe_eval đã chứng minh rằng safe_eval sẽ đưa một số biểu thức vào danh sách đen, mình
hy vọng khi sử dụng , ứng dụng mình viết có thể an toàn hơn 😄. Bài viết còn nhiều thiếu sót, mong các bạn thông cảm nha.