You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Django DataTable实现服务端AJAX加载海量数据?

Django DataTables服务端AJAX优化失败问题排查与解决

问题背景

开发的Django DataTables因数据量达数千条导致页面加载缓慢,尝试通过服务端AJAX模式优化性能,但无AJAX请求触发,仅返回字典列表形式的响应,无法实现分页加载。


原有实现代码

视图函数(live_equity_balance_original)

def live_equity_balance_original(request):
    if request.method == 'POST':
        mt4_checked = 'mt4' in request.POST.getlist('platform')
        mt5_checked = 'mt5' in request.POST.getlist('platform')
        ctrader_checked = 'ctrader' in request.POST.getlist('platform')
        platforms = [var_name for var_name, var_value in [('mt4', mt4_checked), ('mt5', mt5_checked), ('ctr', ctrader_checked)] if var_value]
        variable_names = {
            'mt4': 'balance_equity_mt4_query',
            'mt5': 'balance_equity_mt5_query',
            'ctr': 'balance_equity_ctr_query'
            }

        for platform in platforms:
            if platform in variable_names:
                module_name = 'queries.queries'
                variable_name = variable_names[platform]
                module = importlib.import_module(module_name)
                balance_equity_query = getattr(module, variable_name)
                if platform == 'mt4':
                    df = my_library.get_mt4_data(balance_equity_query)
                    df = df.loc[df.login.astype(str).str.len() > 5]
                    exclude_strings = ['MANAGER','tEsT','COVER']
                    df = df[~df['group'].str.contains('|'.join(exclude_strings),case=False)]
                    df = df.loc[(df['balance']!=0)|(df['equity']!=0)]
                    df = df.head(100)
        headers = df.columns
        json_records = df.reset_index(drop=True).to_json(orient ='records')
        data = json.loads(json_records)
        context = {'headers': headers,'data': data}
        return render(request, 'live_equity_balance.html', context)

模板代码(live_equity_balance.html)

{% load custom_filters %}
<div class="container">
    <br><br><br>
    <table class="table row-border cell-border display compact" id="example" style="text-align: center;">
        <thead class="table-success">
        <tr>
            {% for header in headers %}
                <th>{{ header }}</th>
            {% endfor %}
        </tr>
      </thead>
      <tbody>
        {% for item in data %}
        <tr>
            {% for header in headers %}
                <td>{{ item|get_value:header }}</td>
            {% endfor %}
        </tr>
    {% endfor %}
      </tbody>
    </table>
  </div>
  <script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
  <script src="https://cdn.datatables.net/v/dt/dt-1.13.4/b-2.3.6/b-html5-2.3.6/datatables.min.js"></script>

  <script>
    $('#example').DataTable({
        paging: true,
        pageLength:25,
        lengthChange:true,
        autoWidth:true,
        searching:true,
        bInfo:true,
        bSort:true,
        //buttons
        dom:'lfrtipB',
        language: {
                        lengthMenu: 'Show _MENU_' // Customize the "Show entries" text here
                    },
        buttons:[
            {
            extend: 'csv',
            text:'<i class="fas fa-file-excel">CSV</i>',
            className:'btn btn-secondary',
            titleAttr:'CSV',
            exportOptions: {
                modifier: {
                    search: 'none'
                }
            }
        }
        ],
        // Adjusted dom option
        initComplete: function () {
            var buttons = $('.dt-buttons').addClass('buttons-container');
            $('.dataTables_length').after(buttons);
        },
    });
    </script>

自定义过滤器(custom_filters.py)

from django import template

register = template.Library()

@register.filter
def get_value(item, key):
    return item.get(key, '')

尝试修改后的代码

修改后的视图函数片段

headers = list(df.columns)
data=[]
data.extend(df.to_dict(orient='records'))
response = {'data':data,'headers':headers}
return JsonResponse(response)

修改后的模板代码

{% load custom_filters %}
<div class="container">
    <br><br><br>
    <table class="table row-border cell-border display compact" id="example" style="text-align: center;">
        <thead class="table-success">
        <tr>
            <th>platform</th>
            <th>login</th>
            <th>balance</th>
            <th>currency</th>
            <th>group</th>
            <th>equity</th>
            <th>leverage</th>
        </tr>
      </thead>
      <tbody>
            <!--JSON RESPONSE FROM BACKEND-->
      </tbody>
    </table>
  </div>
  <script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
  <script src="https://cdn.datatables.net/v/dt/dt-1.13.4/b-2.3.6/b-html5-2.3.6/datatables.min.js"></script>

  <script>
    $('#example').DataTable({
        serverSide: true,
        ajax:"{% url 'live-equity-balance' %}",
        "columns":[
            {"data":"platform"},
            {"data":"login"},
            {"data":"balance"},
            {"data":"currency"},
            {"data":"group"},
            {"data":"equity"},
            {"data":"leverage"}

        ],
        paging: true,
        pageLength:25,
        lengthChange:true,
        autoWidth:true,
        searching:true,
        bInfo:true,
        bSort:true,
        //buttons
        dom:'lfrtipB',
        language: {
                        lengthMenu: 'Show _MENU_' // Customize the "Show entries" text here
                    },
        buttons:[
            {
            extend: 'csv',
            text:'<i class="fas fa-file-excel">CSV</i>',
            className:'btn btn-secondary',
            titleAttr:'CSV',
            exportOptions: {
                modifier: {
                    search: 'none'
                }
            }
        }
        ],
        // Adjusted dom option
        initComplete: function () {
            var buttons = $('.dt-buttons').addClass('buttons-container');
            $('.dataTables_length').after(buttons);
        },
    });
    </script>

问题分析与解决方案

核心问题

  1. 视图仅处理POST请求:DataTables服务端模式默认发送GET请求,现有视图未处理该请求,导致AJAX请求被忽略。
  2. 响应格式不符合要求:DataTables服务端模式要求返回包含draw、recordsTotal、recordsFiltered、data的标准结构,当前返回格式无法被识别。
  3. 未处理服务端参数:未处理DataTables传递的分页、排序、搜索参数,无法实现真正的分段加载。

修复步骤

1. 修改视图函数支持GET请求并处理服务端参数

import pandas as pd
import importlib
from django.http import JsonResponse

def live_equity_balance(request):
    if request.method in ['GET', 'POST']:
        # 获取平台筛选条件
        if request.method == 'POST':
            platforms = request.POST.getlist('platform')
        else:
            platforms = request.GET.getlist('platform', [])
        
        variable_names = {
            'mt4': 'balance_equity_mt4_query',
            'mt5': 'balance_equity_mt5_query',
            'ctr': 'balance_equity_ctr_query'
        }
        df = None
        
        # 加载多平台数据
        for platform in platforms:
            if platform in variable_names:
                module = importlib.import_module('queries.queries')
                balance_equity_query = getattr(module, variable_names[platform])
                if platform == 'mt4':
                    temp_df = my_library.get_mt4_data(balance_equity_query)
                    temp_df['platform'] = 'mt4'
                    # 数据过滤
                    temp_df = temp_df.loc[temp_df.login.astype(str).str.len() > 5]
                    exclude_strings = ['MANAGER','tEsT','COVER']
                    temp_df = temp_df[~temp_df['group'].str.contains('|'.join(exclude_strings),case=False)]
                    temp_df = temp_df.loc[(temp_df['balance']!=0)|(temp_df['equity']!=0)]
                    df = pd.concat([df, temp_df]) if df is not None else temp_df
        
        # 空数据处理
        if df is None:
            df = pd.DataFrame(columns=['platform', 'login', 'balance', 'currency', 'group', 'equity', 'leverage'])
        
        # 处理DataTables服务端参数
        draw = int(request.GET.get('draw', 1))
        start = int(request.GET.get('start', 0))
        length = int(request.GET.get('length', 25))
        search_value = request.GET.get('search[value]', '')
        
        # 搜索过滤
        if search_value:
            mask = df['login'].astype(str).str.contains(search_value, case=False) | df['group'].str.contains(search_value, case=False)
            df = df[mask]
        
        # 排序处理
        order_col_idx = int(request.GET.get('order[0][column]', 0))
        order_dir = request.GET.get('order[0][dir]', 'asc')
        sort_col = df.columns[order_col_idx]
        df = df.sort_values(by=sort_col, ascending=(order_dir == 'asc'))
        
        # 分页处理
        total_records = len(df)
        df_paginated = df.iloc[start:start+length]
        
        # 构造标准响应格式
        response = {
            'draw': draw,
            'recordsTotal': total_records,
            'recordsFiltered': total_records,
            'data': df_paginated.to_dict(orient='records')
        }
        return JsonResponse(response)
    return JsonResponse({'error': 'Method not allowed'}, status=405)

2. 调整模板确保AJAX请求正常触发

  • 保留现有表头配置,确保ajax路径在urls.py中正确映射。
  • 若需保留平台筛选功能,添加表单提交后的DataTables更新逻辑:
<form id="platform-filter-form" method="post">
    {% csrf_token %}
    <input type="checkbox" name="platform" value="mt4"> MT4
    <input type="checkbox" name="platform" value="mt5"> MT5
    <input type="checkbox" name="platform" value="ctr"> cTrader
    <button type="submit">筛选</button>
</form>

<script>
$('#platform-filter-form').submit(function(e) {
    e.preventDefault();
    var formData = $(this).serialize();
    $('#example').DataTable().ajax.url("{% url 'live-equity-balance' %}?" + formData).load();
});
</script>

3. 验证关键配置

  • 确认serverSide: true已开启,这是触发服务端AJAX请求的核心。
  • 打开浏览器开发者工具Network标签,检查是否有向目标接口发送GET请求,且响应符合标准格式。

内容的提问来源于stack exchange,提问作者Kosmas Diamantis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 20:07:31