API Lấy Thông Tin Khu Vực
Trang đăng tin, trang chủ và trang tìm kiếm đều cần thông tin khu vực của thành phố. Vì trang chủ và trang tìm kiếm có tần suất truy cập cao, chúng ta cần lưu thông tin vào bộ nhớ cache Redis.
Logic phía sau
Thêm tệp view cho mô-đun nhà ở houses.py trong ihome/api_1_0
# ihome/api_1_0/houses.py
import json
from flask import jsonify, current_app
from . import api
from ihome import redis_connect
from ihome.models import Areas
from ihome.utils.response_codes import RET
from ihome.utils import constants
@api.route('/areas')
def get_areas():
# Lấy thông tin khu vực từ cache
try:
khu_vuc = redis_connect.get('khu_vuc').decode()
except Exception as e:
current_app.logger.error(e)
khu_vuc = None
# Nếu không có trong cache thì truy vấn cơ sở dữ liệu
if not khu_vuc:
try:
khu_vuc_obj_list = Areas.query.all()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin khu vực thất bại')
# Chuyển đổi danh sách đối tượng khu vực thành từ điển
khu_vuc_dict = {khu_vuc.id: khu_vuc.name for khu_vuc in khu_vuc_obj_list}
# Chuyển đổi từ điển thành chuỗi JSON
khu_vuc = json.dumps(khu_vuc_dict)
# Lưu dữ liệu vào cache
try:
redis_connect.setex('khu_vuc', constants.AREA_REDIS_EXPIRES, khu_vuc)
print('Đã lưu cache khu_vuc')
except Exception as e:
current_app.logger.error(e)
return f'{{"errno": "0", "data": {khu_vuc}}}', 200, {'content-Type': 'application/json'}
Ghi chú:
- Vì API khu vực được truy cập thường xuyên, nên lưu kết quả JSON vào cache Redis. Redis mặc định lưu kết quả đã mã hóa, cần giải mã thủ công sau khi lấy dữ liệu.
Areas.query.all()trả về danh sách đối tượng mô hình, cuối cùng chúng ta cần trích xuất id và tên khu vực.- Vì kết quả cuối cùng là JSON, nên lưu trực tiếp dữ liệu JSON của khu vực vào cache Redis, thuận tiện cho việc gửi đến frontend và phải đặt thời gian hết hạn cache.
- Chuyển đổi danh sách thành từ điển bằng cách sử dụng biểu thức từ điển, sau đó chuyển đổi thành chuỗi JSON bằng
json.dumps. - Trả về bằng cách sử dụng tuple (nội dung JSON, mã trạng thái HTTP, thông tin header response) vì đã lấy được thông tin JSON của khu vực.
- Vì cần thêm cặp khóa-giá trị
errnovàdatavào dữ liệu JSON khu vực, nên sử dụng cách nối chuỗi định dạng để có được chuỗi JSON cuối cùng. Trong chuỗi định dạng f-string, một cặp ngoặc nhọn biểu thị tham chiếu biến, hai cặp biểu thị hiển thị một cặp ngoặc nhọn. - Loại trả về của response cần chỉ định là
application/json
Logic frontend chọn khu vực khi đăng tin
Phía sau trả về một đối tượng JavaScript, tương tự như:
{1: "Quận Đông Thành", 2: "Quận Tây Thành", 3: "Quận Triều Dương", 4: "Quận Hải Điến", 5: "Quận Xương Bình", 6: "Quận Phong Đài", 7: "Quận Phòng Sơn", 8: "Quận Thông Châu", 9: "Quận Thuận Nghĩa", 10: "Quận Đại Hưng", 11: "Quận Hoài Nhu", 12: "Quận Bình Cốc", 13: "Quận Mật Vân", 14: "Quận Diên Khánh", 15: "Quận Thạch Cảnh Sơn", 16: "Quận Môn Đầu Câu"}
Vì vậy, frontend cần lấy dữ liệu động và hiển thị dưới dạng danh sách chọn bằng select+option, có hai cách để đặt HTML động
Sử dụng jQuery để đặt thẻ HTML
Cách này cũng là cách thường dùng trong mã trước, tức là sau khi lấy được dữ liệu JSON từ backend, đặt thuộc tính của thẻ bằng jQuery. Chỉnh sửa tệp js tương ứng của trang đăng tin newhouse.js, gửi yêu cầu ajax khi trang tải để lấy dữ liệu.
// newhouse.js
$(document).ready(function(){
// $('.popup_con').fadeIn('fast');
// $('.popup_con').fadeOut('fast');
//Lấy thông tin nhà ở
$.get("api/v1.0/areas", function (resp) {
if (resp.errno == '0'){
//Lấy dữ liệu khu vực
var khu_vuc = resp.data;
// Cách 1: Vòng lặp thông thường, thêm thẻ option dưới select
for (var key in khu_vuc){
//<option value="1">Quận Đông</option>
$("#area-id").append('<option value="'+key+'">' + khu_vuc[key] + '</option>');
}
}else {
//Có lỗi
alert(resp.errmsg);
}
}, 'json')
})
Ghi chú:
- Khi trả về bình thường, resp là dữ liệu JSON của khu vực, khi backend trả về lỗi thì
resp.errnomới có giá trị. - Trong js có thể sử dụng vòng lặp for để duyệt đối tượng js, key là chỉ mục, khu_vuc[key] là giá trị.
- Sử dụng
.append()để thêm thẻoptioncho thẻselect, truyền vào chuỗi văn bản HTML của thẻoptioncần thiết.
Sử dụng template frontend: art-template
Trong frontend cũng có các plugin template giống như Django hoặc Flask, art-template là một plugin template. Nó cần dựa trên jQuery.
Nhập mã nguồn template.js
Tải tệp js từ trang web chính thức, sau đó nhập tệp js template vừa tải vào static/js.
Sử dụng template trong tệp html
Chỉnh sửa tệp html của trang đăng tin newhouse.html
<div class="form-group">
<label for="area-id">Khu vực</label>
<select class="form-control" id="area-id" name="area_id">
<script type="text/html" id="area-option">
{{ each khu_vuc }}
<option value = "{{ $index }}">{{ $value }}</option>
{{ /each }}
</script>
</select>
</div>
....
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/jquery.form.min.js"></script>
<script src="/static/js/template.js"></script>
Ghi chú:
- Đầu tiên nhập đường dẫn của
template.jstrong tệp html. - Đặt mã template cần tạo chuỗi văn bản HTML động vào thẻ
<script type="text/html" id="area-option"></script>(id có thể tùy chỉnh), và thẻscriptnày có thể đặt ở bất kỳ vị trí nào trong tệp html, không nhất thiết phải đặt trong thẻselect. - Điều này khác với ngôn ngữ template của Django hoặc Flask, ngôn ngữ template của Django hoặc Flask cần đặt ở nơi cụ thể cần thay thế, cho thấy art-template chỉ dùng để tạo chuỗi văn bản HTML, vị trí cụ thể của chuỗi văn bản HTML được tạo cần chỉ định thêm.
- Các tham số biến được truyền cho template html sau này là
khu_vuc, đây là đối tượng js của khu vực, chúng ta cần duyệt đối tượng này để lấy key và value, và tạo văn bảnoption. - Trong
art-templatecó thể sử dụngeachđể duyệt đối tượng, sử dụng{{$index}}để lấy giá trị key,{{$value}}để lấy giá trị,{{$data}}để lấy đối tượng itself.
Đặt template trong js
Trong tệp js tương ứng newhouse.js
$(document).ready(function(){
// $('.popup_con').fadeIn('fast');
// $('.popup_con').fadeOut('fast');
//Lấy thông tin nhà ở
$.get("api/v1.0/areas", function (resp) {
if (resp.errno == '0'.data){
//Lấy dữ liệu khu vực
var khu_vuc = resp;
// Cách 1: Vòng lặp qua jQuery, thêm thẻ option dưới select
// for (var key in khu_vuc){
// //<option value="1">Quận Đông</option>
// $("#area-id").append('<option value="'+key+'">' + khu_vuc[key] + '</option>');
// }
// Cách 2: Sử dụng template frontend art-template
// Truyền giá trị cho template, trả về chuỗi văn bản html
html = template('area-option', {khu_vuc: khu_vuc});
// Đặt đối tượng văn bản cho select
$('#area-id').html(html);
}else {
//Có lỗi
alert(resp.errmsg);
}
}, 'json')
})
Ghi chú:
- Sử dụng hàm
template()để trả về chuỗi văn bản html được tạo từ template, tham số đầu tiên là giá trị thuộc tính id của thẻ script trong html template, tham số thứ hai là đối tượng js, biểu thị dữ liệu được truyền cho template, key là tên tham số, val là giá trị được truyền. - Đặt chuỗi văn bản html gồm các thẻ option vào thuộc tính html của select bằng jQuery, chỉ định vị trí cụ thể của html.
API Đăng Tin Bất Động Sản
Người dùng nhấp vào nút "Đăng tin mới" trên trang "Nhà của tôi" để vào trang "Đăng tin mới".
Trang này có hai biểu mẫu, một biểu mẫu để đăng thông tin bất động sản, biểu mẫu còn lại để tải lên hình ảnh, biểu mẫu này bị ẩn và chỉ hiển thị sau khi đăng tin thành công.
Logic phía sau đăng tin
Thêm logic giao diện API đăng tin trong tệp view của mô-đun nhà ở houses.py, url là /api/v1.0/houses
@api.route('/houses', methods=['POST'])
@login_required
def create_bds():
# Nhận dữ liệu
data_dict = request.get_json()
if not data_dict:
return parameter_error()
# Trích xuất dữ liệu
tieu_de = data_dict.get('tieu_de')
gia = data_dict.get('gia')
khu_vuc_id = data_dict.get('khu_vuc_id')
dia_chi = data_dict.get('dia_chi')
so_phong = data_dict.get('so_phong')
dien_tich = data_dict.get('dien_tich')
loai_phong = data_dict.get('loai_phong')
suc_chua = data_dict.get('suc_chua')
giuong_ngu = data_dict.get('giuong_ngu')
tien_dat_coc = data_dict.get('tien_dat_coc')
so_ngay_toi_thieu = data_dict.get('so_ngay_toi_thieu')
so_ngay_toi_da = data_dict.get('so_ngay_toi_da')
facilitie_ids = data_dict.get('facilities')
# Kiểm tra dữ liệu
if not all(
[tieu_de, gia, khu_vuc_id, dia_chi, so_phong, dien_tich, loai_phong, suc_chua, giuong_ngu, tien_dat_coc, so_ngay_toi_thieu, facilitie_ids]):
return parameter_error()
# Kiểm tra định dạng tiền tệ, chuyển thành hai chữ số thập phân
try:
gia = round(float(gia), 2)
tien_dat_coc = round(float(tien_dat_coc), 2)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.PARAMERR, errmsg='Định dạng tiền tệ không đúng')
# Kiểm tra kiểu số
try:
so_phong = int(so_phong)
dien_tich = int(dien_tich)
suc_chua = int(suc_chua)
so_ngay_toi_thieu = int(so_ngay_toi_thieu)
so_ngay_toi_da = int(so_ngay_toi_da)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.PARAMERR, errmsg='Diện tích nhà/phòng số/sức chứa/ngày tối thiểu/tối đa phải là số')
# Kiểm tra khu_vuc_id có tồn tại không
try:
khu_vuc = Areas.query.get(khu_vuc_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin khu vực bất thường')
if not khu_vuc:
return jsonify(errno=RET.PARAMERR, errmsg='ID khu vực không tồn tại')
# Ngày tối thiểu và tối đa
if so_ngay_toi_thieu > so_ngay_toi_da | so_ngay_toi_da >= 0:
return jsonify(errno=RET.PARAMERR, errmsg='Ngày tối thiểu không được vượt quá ngày tối đa')
# Tạo đối tượng nhà
bds = Houses(user_id=g.user.id, area_id=khu_vuc_id, title=tieu_de, price=gia, address=dia_chi,
room_count=so_phong, acreage=dien_tich, unit=loai_phong, capacity=suc_chua, beds=giuong_ngu, deposit=tien_dat_coc,
min_days=so_ngay_toi_thieu, max_days=so_ngay_toi_da)
# Thêm thông tin tiện nghi cho nhà
# Cách 1: Vòng lặp kiểm tra từng tiện nghi, sau đó thêm bằng append
# for facilitie_id in facilitie_ids:
# # Vòng lặp kiểm tra ID tiện nghi
# try:
# facilitie = Facilities.query.get(facilitie_id)
# except Exception as e:
# current_app.logger.error(e)
# return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin tiện nghi bất thường')
# if not facilitie:
# return jsonify(errno=RET.DBERR, errmsg=f'ID tiện nghi"{facilitie_id}" không tồn tại')
# # Tạo dữ liệu cài đặt nhà
# bds.facilities.append(facilitie)
# Cách 2: Không vòng lặp, truy vấn tiện nghi bằng in_(list), nhưng không xử lý ID tiện nghi sai
try:
facilitie_list = Facilities.query.filter(Facilities.id.in_(facilitie_ids)).all()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin tiện nghi bất thường')
if facilitie_list:
# Nếu có ID tiện nghi hợp lệ, thêm thông tin tiện nghi cho đối tượng nhà
bds.facilities = facilitie_list
# Gửi session
try:
db.session.add(bds)
db.session.commit()
except IntegrityError as e:
current_app.logger.error(e)
return jsonify(errno=RET.PARAMERR, errmsg='Tiêu đề nhà đã tồn tại')
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lưu thông tin nhà bất thường')
return jsonify(errno=RET.OK, data={'bds_id': bds.id})
Ghi chú:
- Dữ liệu json được gửi có định dạng:
{
"tieu_de": "test3",
"gia": "110",
"khu_vuc_id": "3",
"dia_chi": "Khu dân cư Thượng Hải",
"so_phong": "3",
"dien_tich": "120",
"loai_phong": "Ba phòng một phòng khách",
"suc_chua": "4",
"giuong_ngu": "2 giường đôi",
"tien_dat_coc": "100",
"so_ngay_toi_thieu": "2",
"so_ngay_toi_da": "-1",
"facilities": ["1","2","4"]
}
- Thông tin tiện nghi nhà được gửi bằng danh sách ID tiện nghi
facilities, đây liên quan đến mối quan hệ một-nhiều giữa bảng nhà và bảng tiện nghi, trong tệp models đã định nghĩa bảng trung gian và mối quan hệ logic
# Mối quan hệ nhiều-nhiều giữa bảng nhà và bảng tiện nghi, cách chính thức đề xuất là db.table
bds_facilitie = db.Table('ih_bds_facilitie',
db.Column('bds_id', db.Integer, db.ForeignKey('ih_houses.id'), nullable=False),
db.Column('facilitie_id', db.Integer, db.ForeignKey('ih_facilities.id'), nullable=False))
# Lớp mô hình nhà
class Houses(BasicModel):
__tablename__ = 'ih_houses'
.....
# Định nghĩa mối quan hệ trong lớp mô hình, lưu ý tham số secondary truyền biến bảng trung gian
facilities = db.relationship('Facilities', secondary=bds_facilitie, backref='houses')
- Khi tạo dữ liệu nhà, cũng cần tạo dữ liệu bảng trung gian nhà-tiện nghi, nhưng không cần gán thủ công, chỉ cần gán giá trị cho thuộc tính facilitie của đối tượng house, loại là danh sách đối tượng Facilitie, có hai cách để có được danh sách này
- Cách 1: Vòng lặp qua danh sách ID tiện nghi
facilitiesđược gửi từ frontend, kiểm tra từng ID có đúng không, lấy đối tượng facilitie tương ứng với ID, sau đó thêm đối tượng facilitie vào thuộc tính facilitie của đối tượng house:
for facilitie_id in facilitie_ids:
# Vòng lặp kiểm tra ID tiện nghi
try:
facilitie = Facilities.query.get(facilitie_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin tiện nghi bất thường')
if not facilitie:
return jsonify(errno=RET.DBERR, errmsg=f'ID tiện nghi"{facilitie_id}" không tồn tại')
# Tạo dữ liệu cài đặt nhà
bds.facilities.append(facilitie)
- Cách 2: Không vòng lặp ID danh sách, trực tiếp sử dụng phương thức
in_để truy vấn facilitie tương ứng với ID trong danh sách, lấy danh sách đối tượng facilitie, sau đó gán cho thuộc tính facilitie của đối tượng house
try:
facilitie_list = Facilities.query.filter(Facilities.id.in_(facilitie_ids)).all()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin tiện nghi bất thường')
if facilitie_list:
# Nếu có ID tiện nghi hợp lệ, thêm thông tin tiện nghi cho đối tượng nhà
bds.facilities = facilitie_list
Logic frontend đăng tin
Thêm logic sau trong tệp js tương ứng của trang đăng tin newhouse.js
//Gửi biểu mẫu đăng tin
$('#form-bds-info').submit(function (e) {
//Ngăn chặn mặc định
e.preventDefault();
//Gửi tùy chỉnh
//Lấy dữ liệu
var data = {};
//Lấy tất cả các mục input trong form, sử dụng map để lấy name và value của mỗi input
$(this).serializeArray().map(function (item) {
data[item.name] = item.value;
})
//Lấy các mục tiện nghi được chọn trong checkbox
var facilitie_ids = [];
$('input:checkbox:checked').each(function (index, item) {
facilitie_ids[index] = $(item).val();
})
var data["facilities"] = facilitie_ids;
//Chuyển thành json
var data = JSON.stringify(data);
//Gửi yêu cầu
$.ajax({
url: 'api/v1.0/houses',
type: 'POST',
contentType: 'application/json',
data: data,
headers: {'X-CSRFToken': getCookie('csrf_token')},
dataType: 'json',
success: function (resp) {
if (resp.errno == '0'){
//Đăng thành công
//Đặt bds_id
$('#bds-id').val(resp.data.bds_id);
//Hiển thị biểu mẫu tải lên hình ảnh
$('#form-bds-image').show();
alert('Lưu thành công, vui lòng tải lên hình ảnh nhà');
}else {
//Đăng thất bại
alert(resp.errmsg)
}
}
})
})
Ghi chú:
- Biểu mẫu này có nhiều ô input cần gửi, rõ ràng không phù hợp để lấy một cách riêng lẻ, có thể lấy tất cả các ô input trong form bằng
$('form selector').serializeArray(), trả về danh sách đối tượng gồm name và value của tất cả các ô input, như:
> $('#form-bds-info').serializeArray()
< (16) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
0: {name: "bds_id", value: ""}
1: {name: "tieu_de", value: "test"}
2: {name: "gia", value: "100"}
3: {name: "khu_vuc_id", value: "1"}
4: {name: "dia_chi", value: "Khu dân cư Thượng Hải a"}
5: {name: "so_phong", value: "1"}
6: {name: "dien_tich", value: "50"}
7: {name: "loai_phong", value: "Một phòng một phòng khách"}
8: {name: "suc_chua", value: "2"}
9: {name: "giuong_ngu", value: "Giường đôi:2x1.8x1 cái"}
10: {name: "tien_dat_coc", value: "100"}
11: {name: "so_ngay_toi_thieu", value: "1"}
12: {name: "so_ngay_toi_da", value: "2"}
13: {name: "facilitie", value: "1"}
14: {name: "facilitie", value: "2"}
15: {name: "facilitie", value: "3"}
length: 16
__proto__: Array(0)
Cuối cùng chúng ta cần cặp khóa-giá trị được tạo bởi thuộc tính name và value của mỗi ô input trong danh sách, như:{"tieu_de": "test", "gia": "100"}
Vì vậy, trước tiên tạo một đối tượng js rỗng data, sau đó sử dụng hàm map để duyệt danh sách, lấy name và value của mỗi item, sau đó thêm vào đối tượng data, tham số item của hàm map là mỗi đối tượng js trong danh sách.
2. Tiện nghi đi kèm sử dụng nhiều checkbox, vì vậy có thể lấy checkbox ở trạng thái được chọn bằng selector $('input:checkbox:checked'), giá trị value của nó được thiết kế là ID tương ứng trong bảng tiện nghi ih_facilities của cơ sở dữ liệu.
Tương tự, lấy được một danh sách checkbox, nhưng các phần tử trong danh sách là toàn bộ đối tượng input, chúng ta cũng chỉ cần thuộc tính value của mỗi input.
Vì cuối cùng muốn là mảng gồm các thuộc tính value của mỗi checkbox, như:["3", "4", "7"], nên trước tiên định nghĩa một mảng rỗng facilitie_ids.
Ở đây sử dụng each để duyệt mảng checkbox, each nhận hai tham số: index là chỉ mục của danh sách, item là giá trị phần tử của mảng, vì vậy item đại diện cho toàn bộ đối tượng phần tử checkbox, vì vậy muốn lấy thuộc tính value bằng cách sử dụng jQuery: $(item).val(), sau đó thêm index và value vào facilitie_ids, cuối cùng gán cho thuộc tính facilities của data.
3. Sau khi đăng tin thành công, hiển thị biểu mẫu tải lên hình ảnh nhà, đồng thời nhận ID tin mới và gán cho ô input ẩn bds-id, để tại API tải lên hình ảnh nhà có thể xác định hình ảnh được tải lên thuộc về tin nào.
API Tải Lên Hình Ảnh Nhà
Sau khi đăng nhà thành công, sẽ hiển thị giao diện biểu mẫu tải lên hình ảnh.
Logic phía sau tải lên hình ảnh nhà
Thêm logic giao diện API tải lên hình ảnh nhà trong tệp view của mô-đun nhà ở houses.py, url là /api/v1.0/house/images
@api.route('house/images', methods=['POST'])
@login_required
def create_bds_images():
# Lấy dữ liệu, dữ liệu hình ảnh do trình duyệt gửi, không phải định dạng json
image_data = request.files.get('bds_image')
bds_id = request.form.get('bds_id')
# Kiểm tra dữ liệu
if not all([image_data, bds_id]):
return parameter_error()
# Kiểm tra ID nhà
try:
bds = Houses.query.get(bds_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin nhà bất thường')
if not bds:
return jsonify(errno=RET.PARAMERR, errmsg='ID nhà không tồn tại')
# Gọi API tải lên hình ảnh
ret = storage(image_data)
print(ret)
if ret['status'] != 200:
# Tải lên thất bại
return jsonify(errno=RET.THIRDERR, errmsg=f'Tải lên thất bại:{ret["errmsg"]}')
# Tải lên thành công, tạo dữ liệu hình ảnh
bds_image = HouseImages(house=bds, image_url=ret['url'])
db.session.add(bds_image)
# Lưu hình ảnh mặc định của nhà
if not bds.default_image_url:
bds.default_image_url = ret['url']
db.session.add(bds)
# Gửi dữ liệu
try:
db.session.commit()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lưu thông tin hình ảnh bất thường')
return jsonify(errno=RET.OK, data={'url': ret['url']})
Ghi chú:
- Giống như API tải lên avatar người dùng trước, dữ liệu frontend không được truyền bằng định dạng json, mà sử dụng cách gửi dữ liệu của form trình duyệt, ngoài
request.files.get('bds_image')lấy dữ liệu tệp, còn cần lấyhouse_id = request.form.get('bds_id')từ form. - Lưu url được trả về bởi API tải lên Qiniu vào cơ sở dữ liệu và trả về cho frontend.
Logic frontend tải lên hình ảnh nhà
Thêm logic sau trong tệp js tương ứng của trang đăng tin newhouse.js, tương tự như API tải lên avatar người dùng trước
//Biểu mẫu tải lên hình ảnh nhà
$('#form-bds-image').submit(function (e) {
//Ngăn chặn hành vi gửi mặc định của form
e.preventDefault()
//Hành vi gửi tùy chỉnh
$(this).ajaxSubmit({
url: 'api/v1.0/house/images',
type: 'POST',
headers: {'X-CSRFToken': getCookie('csrf_token')},
dataType: 'json',
success: function (resp) {
if (resp.errno == '0'){
//Tải lên thành công, hiển thị hình ảnh
$('.bds-image-cons').append('<img src="' + resp.data.url + '"></img>')
}else {
//Tải lên thất bại
alert(resp.errmsg)
}
}
})
})
- Ngăn chặn hành vi gửi hoàn chỉnh của form, vì gửi dữ liệu tệp, sử dụng yêu cầu ajax thông thường khó xử lý, trong khi sử dụng phương thức
.ajaxSubmitcó thể giao việc gửi cho hành vi gửi form mặc định của trình duyệt, và hàm callback vẫn có thể tùy chỉnh. - Sau khi tải lên thành công, hiển thị hình ảnh, sử dụng
appendđể thêm thẻimgvàodiv, đặt thuộc tính src là url hình ảnh được trả về.
API Cập Nhật Thông Tin Nhà
Ở đây có một cải tiến, tức là sau khi đăng tin thành công, thay đổi văn bản nút ban đầu thành "Cập nhật thông tin nhà", có thể tiếp tục sửa đổi thông tin nhà ở trên, nhấp vào nút cập nhật để cập nhật dữ liệu nhà.
Trong tệp html đăng tin mới newhouse.html, thêm một ô input ẩn trong form đăng tin để lưu house_id sau khi đăng tin thành công lần đầu, để sử dụng cho thao tác cập nhật lần thứ hai.
<form id="form-bds-info">
......
<div class="form-group">
<input type="hidden" name="bds_id" id="new-bds-id" value="">
<label for="bds-title">Tiêu đề nhà</label>
<input type="text" class="form-control" name="tieu_de" id="bds-title" value="test" required>
</div>
......
Logic phía sau sửa đổi
@api.route('/houses', methods=['POST', 'PUT'])
@login_required
def create_or_update_bds():
# Nhận dữ liệu
data_dict = request.get_json()
if not data_dict:
return parameter_error()
# Trích xuất dữ liệu
tieu_de = data_dict.get('tieu_de')
gia = data_dict.get('gia')
khu_vuc_id = data_dict.get('khu_vuc_id')
dia_chi = data_dict.get('dia_chi')
so_phong = data_dict.get('so_phong')
dien_tich = data_dict.get('dien_tich')
loai_phong = data_dict.get('loai_phong')
suc_chua = data_dict.get('suc_chua')
giuong_ngu = data_dict.get('giuong_ngu')
tien_dat_coc = data_dict.get('tien_dat_coc')
so_ngay_toi_thieu = data_dict.get('so_ngay_toi_thieu')
so_ngay_toi_da = data_dict.get('so_ngay_toi_da')
facilitie_ids = data_dict.get('facilities')
bds_id = data_dict.get('bds_id')
# Kiểm tra dữ liệu
if not all(
[tieu_de, gia, khu_vuc_id, dia_chi, so_phong, dien_tich, loai_phong, suc_chua, giuong_ngu, tien_dat_coc, so_ngay_toi_thieu, facilitie_ids]):
return parameter_error()
# Kiểm tra định dạng tiền tệ, chuyển thành hai chữ số thập phân
try:
gia = round(float(gia), 2)
tien_dat_coc = round(float(tien_dat_coc), 2)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.PARAMERR, errmsg='Định dạng tiền tệ không đúng')
# Kiểm tra kiểu số
try:
so_phong = int(so_phong)
dien_tich = int(dien_tich)
suc_chua = int(suc_chua)
so_ngay_toi_thieu = int(so_ngay_toi_thieu)
so_ngay_toi_da = int(so_ngay_toi_da)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.PARAMERR, errmsg='Diện tích nhà/phòng số/sức chứa/ngày tối thiểu/tối đa phải là số')
# Kiểm tra khu_vuc_id có tồn tại không
try:
khu_vuc = Areas.query.get(khu_vuc_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin khu vực bất thường')
if not khu_vuc:
return jsonify(errno=RET.PARAMERR, errmsg='ID khu vực không tồn tại')
# Thêm thông tin tiện nghi cho nhà
# Cách 1: Vòng lặp kiểm tra từng tiện nghi, sau đó thêm bằng append
# for facilitie_id in facilitie_ids:
# # Vòng lặp kiểm tra ID tiện nghi
# try:
# facilitie = Facilities.query.get(facilitie_id)
# except Exception as e:
# current_app.logger.error(e)
# return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin tiện nghi bất thường')
# if not facilitie:
# return jsonify(errno=RET.DBERR, errmsg=f'ID tiện nghi"{facilitie_id}" không tồn tại')
# # Tạo dữ liệu cài đặt nhà
# bds.facilities.append(facilitie)
# Cách 2: Không vòng lặp, truy vấn tiện nghi bằng in_(list), nhưng không xử lý ID tiện nghi sai
try:
facilitie_list = Facilities.query.filter(Facilities.id.in_(facilitie_ids)).all()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin tiện nghi bất thường')
if not facilitie_list:
return jsonify(errno=RET.PARAMERR, errmsg='Không có ID tiện nghi hợp lệ')
# POST thì tạo đối tượng nhà mới, PUT thì cập nhật nhà
if request.method == 'POST':
# Tạo đối tượng nhà
bds = Houses(user_id=g.user.id, area_id=khu_vuc_id, title=tieu_de, price=gia, address=dia_chi,
room_count=so_phong, acreage=dien_tich, unit=loai_phong, capacity=suc_chua, beds=giuong_ngu, deposit=tien_dat_coc,
min_days=so_ngay_toi_thieu, max_days=so_ngay_toi_da, facilities=facilitie_list)
else:
# Lấy đối tượng nhà
try:
bds = Houses.query.get(bds_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lấy thông tin nhà bất thường')
if not bds:
return jsonify(errno=RET.PARAMERR, errmsg='ID nhà không tồn tại')
# Đặt lại thuộc tính nhà
bds.area_id = khu_vuc_id
bds.title = tieu_de
bds.price = gia
bds.address = dia_chi
bds.room_count = so_phong
bds.acreage = dien_tich
bds.unit = loai_phong
bds.capacity = suc_chua
bds.beds = giuong_ngu
bds.deposit = tien_dat_coc
bds.min_days = so_ngay_toi_thieu
bds.max_days = so_ngay_toi_da
bds.facilities = facilitie_list
# Gửi thông tin nhà
try:
db.session.add(bds)
db.session.commit()
except IntegrityError as e:
current_app.logger.error(e)
return jsonify(errno=RET.PARAMERR, errmsg='Tiêu đề nhà đã tồn tại')
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='Lưu thông tin nhà bất thường')
return jsonify(errno=RET.OK, data={'bds_id': bds.id}
Ghi chú:
- Thêm phương thức
PUTvào phương thức yêu cầu. PUTvàPOSTcó logic nhận/kiểm tra dữ liệu giống nhau, chỉ cần di chuyển việc tạo đối tượnghouseở phía trước sang sau khi đặt đối tượngfacilities, sau đó dựa trên phương thức yêu cầu để xác định là tạo đối tượng house mới hay truy vấn và cập nhật đối tượng house theohouse_id.
Logic frontend sửa đổi
//Gửi biểu mẫu đăng tin
$('#form-bds-info').submit(function (e) {
//Ngăn chặn mặc định
e.preventDefault();
//Gửi tùy chỉnh
//Lấy dữ liệu
var data = {};
//Lấy tất cả các mục input trong form, sử dụng map để lấy name và value của mỗi input
$(this).serializeArray().map(function (item) {
data[item.name] = item.value;
})
//Lấy các mục tiện nghi được chọn trong checkbox
var facilitie_ids = [];
$('input:checkbox:checked').each(function (index, item) {
facilitie_ids[index] = $(item).val();
})
data["facilities"] = facilitie_ids;
//Chuyển thành json
var data = JSON.stringify(data);
//Dựa trên new_bds_id để xác định là cập nhật hay tạo
var newBdsID = $('#new-bds-id').val();
if (newBdsID){
var type = 'PUT';
}
else{
var type = 'POST';
}
$.ajax({
url: 'api/v1.0/houses',
type: type,
contentType: 'application/json',
data: data,
headers: {'X-CSRFToken': getCookie('csrf_token')},
dataType: 'json',
success: function (resp) {
if (resp.errno == '0'){
//Đăng thành công
//Đặt bds_id
$('#bds-id').val(resp.data.bds_id);
$('#new-bds-id').val(resp.data.bds_id);
//Thay đổi gợi ý nút
$('.btn-commit').val('Cập nhật thông tin nhà');
//Hiển thị biểu mẫu tải lên hình ảnh
$('#form-bds-image').show();
alert('Lưu thành công, vui lòng tải lên hình ảnh nhà');
}else {
//Đăng thất bại
alert(resp.errmsg)
}
}
})
})
Ghi chú:
- Sau khi đăng tin thành công cũng cần đặt giá trị cho
new-bds-id. - Đầu tiên lấy giá trị của
new-bds-id, nếu tồn tại thì cần cập nhật, phương thức yêu cầu làPUT, nếu không tồn tại thì cần tạo, phương thức yêu cầu làPOST.