大家好,对于 Web 开发非常陌生,但我陷入了困境。
我正在使用 Flask 和 Nextjs 构建一个应用程序,该应用程序显示数据网格并允许用户对数据点进行注释并将其发送回服务器。我可以很好地显示数据,因此 GET 功能可以工作,但我添加了一个“保存更改”按钮,它是一个 POST 请求,并且它一直给我一个 CORS 或 500 INTERNAL SERVER ERROR。
我附上了几张来自控制台和网络的图像。如果我需要添加任何有助于解决问题的内容,请告诉我。
这是我当前的代码。注释掉的行是我尝试过的 CORS 解决方案的其他变体,但对我不起作用。
Flask 应用程序.py
from flask import Flask, jsonify, request, make_response, Response
from flask_cors import CORS, cross_origin
import sqlalchemy as sa
import pandas as pd
import pyodbc
import json
from os import environ
app = Flask(__name__) # create app instance
# enable cors
cors = CORS(app, resources=r'/api/*', headers='Content-Type', origins='http://localhost:3000')
# app.config['CORS_HEADERS'] = 'Content-Type'
sqlalchemy_engine = 'connection string'
engine = sa.create_engine(sqlalchemy_engine, echo=False)
# create a route (api endpoint to get and post the data)
@app.route('/api/assetrentals', methods=['GET','POST','OPTIONS'])
@cross_origin(origin='*')
def asset_rental_data():
if request.method == 'GET':
# res = make_response('')
# res.headers.add('Access-Control-Allow-Origin', '*')
conn = engine.connect()
statement = sa.text("""
SELECT * FROM shortTermAssetRentals
""")
results = conn.execute(statement)
df = pd.DataFrame(results)
df['rental_start_date'] = df['rental_start_date'].astype(str)
df['rental_end_date'] = df['rental_end_date'].astype(str)
df.sort_values(by=['rental_start_date','ra_number'], axis=0, ascending=False, inplace=True, ignore_index=True)
return jsonify(json.loads(df.to_json(orient='records')))
elif request.method == 'POST':
# res = make_response('')
# res.headers.add('Access-Control-Allow-Origin', '*')
# Just trying to display the data first, then worry about sending to server
print(request.get_json())
else:
# res = make_response('')
# res.headers.add('Access-Control-Allow-Origin', '*')
return 'Error'
# code required to run the app
# remove debug=True from production
if __name__ == '__main__':
app.run(debug=True, port=8080)
# to run the app in terminal: python app.py
这是我的 Nextjs 网格
'use client';
import React, { useState, useEffect } from 'react';
import { AgGridReact } from 'ag-grid-react';
import { ColDef } from 'ag-grid-community'
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-quartz.css';
import { Button } from '@headlessui/react';
import { CheckCircleIcon } from '@heroicons/react/16/solid';
import NextCors from 'nextjs-cors';
const Grid: React.FC = () => {
interface AssetRentals {
ra_number: string;
unit: string;
rental_type_custom: string;
lease_unit_custom: string;
notes_custom: string;
lease_unit: string;
status: string;
vendor: string;
rental_type: string;
unit_type: string;
unit_description: string;
service_center: string;
region: string;
vendor_location: string;
rental_start_date: string;
rental_end_date: string;
days_used: number;
};
const [colDefs, setColDefs] = useState<ColDef<AssetRentals>[]>([
{ field: 'ra_number', headerName: 'RA Number', filter: true, sortable: true, editable: false },
{ field: 'unit', headerName: 'Unit', filter: true, sortable: true, editable: false },
{ field: 'rental_type_custom', headerName: 'Rental Type (Custom)', filter: true, sortable: true, editable: true },
{ field: 'lease_unit_custom', headerName: 'Lease Unit (Custom)', filter: true, sortable: true, editable: true },
{ field: 'notes_custom', headerName: 'Notes (Custom)', filter: true, sortable: true, editable: true },
{ field: 'lease_unit', headerName: 'Lease Unit', filter: true, sortable: true, editable: false },
{ field: 'status', headerName: 'Status', filter: true, sortable: true, editable: false },
{ field: 'vendor', headerName: 'Vendor', filter: true, sortable: true, editable: false },
{ field: 'rental_type', headerName: 'Rental Type', filter: true, sortable: true, editable: false },
{ field: 'unit_type', headerName: 'Unit Type', filter: true, sortable: true, editable: false },
{ field: 'unit_description', headerName: 'Unit Description', filter: true, sortable: true, editable: false },
{ field: 'service_center', headerName: 'Service Center', filter: true, sortable: true, editable: false },
{ field: 'region', headerName: 'Region', filter: true, sortable: true, editable: false },
{ field: 'vendor_location', headerName: 'Vendor Location', filter: true, sortable: true, editable: false },
{ field: 'rental_start_date', headerName: 'Rental Start Date', filter: true, sortable: true, editable: false },
{ field: 'rental_end_date', headerName: 'Rental End Date', filter: true, sortable: true, editable: false },
{ field: 'days_used', headerName: 'Days Used', filter: true, sortable: true, editable: false },
]);
const [rowData, setRowData] = useState([]);
useEffect(() => {
fetch('http://localhost:8080/api/assetrentals', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
}, })
.then(response => response.json())
.then((data) => {
setRowData(data)
});
}, []);
const updateData = async () => {
try {
const res = await fetch('http://localhost:8080/api/assetrentals', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({rowData}),
});
if (!res.ok) {
throw new Error('Something went wrong');
}
const jsonData = await res.json();
setRowData(jsonData);
} catch (error) {
console.error('Something went wrong:', error);
}
};
return (
<div>
<div className='px-0.5'>
<Button onClick={updateData} className="inline-flex items-center gap-2 rounded-md bg-slate-900 py-1.5 px-3 text-sm/6 font-semibold text-white shadow-inner shadow-white/10 focus:outline-none data-[hover]:bg-gray-600 data-[open]:bg-gray-700 data-[focus]:outline-1 data-[focus]:outline-white">
<CheckCircleIcon className="size-4 fill-white" />Save Changes</Button>
</div>
<div className='py-2.5' />
<div className={"ag-theme-quartz-dark"} style={{ width: "100%", height: "80vh" }}>
<AgGridReact rowData={rowData} columnDefs={colDefs} />
</div>
</div>
)
}
export default Grid;
这是我的 Nextjs 页面
import React from 'react';
import type { Metadata } from 'next'
import Grid from './grid';
export const metadata: Metadata = {
title: "Data Portal | Asset Rentals",
description: 'Short Term Asset Rentals',
};
export default function page() {
return (
<div>
<Grid />
</div>
)
}
Nextjs 配置
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
module.exports = {
crossOrigin: 'anonymous',
}
对于 CORS,您可以删除标头并将所有来源列入白名单(在本例中为 localhost:3000)。确保您的 nextjs 在端口 3000 上运行。
此外,当您为整个应用程序配置了 CORS 设置时,请勿对特定路由使用通配符条目。
创建对象时尝试此操作。
cors = CORS(app, resources={r"/api/*": {"origins": "http://localhost:3000"}})
当您发出请求时,由于它是 GET 请求,您可以直接调用您的 API,无需任何标头。浏览器将为您处理所有必要的标头。
useEffect(() => {
fetch('http://localhost:8080/api/assetrentals')
.then(response => response.json())
.then((data) => {
setRowData(data)
});
}, []);