在Django中動態(tài)地過濾查詢集的實(shí)現(xiàn)
簡介
要建立一個允許過濾和分頁的列表頁,你必須讓一些獨(dú)立的東西一起工作。Django的對象關(guān)系映射器(ORM)和內(nèi)置的分頁類使開發(fā)者在不了解如何處理數(shù)據(jù)庫和SQL的情況下,也能輕松地提高工作效率。在本指南中,你將學(xué)習(xí)如何使用AJAX動態(tài)地過濾查詢集。
在本文的例子中,我采用了Spotify上按國家劃分的前50首歌的數(shù)據(jù)集。你也可以從這里下載同樣的數(shù)據(jù)集。像往常一樣,本指南中使用的代碼可以在GitHub上找到。你可以在本指南的結(jié)尾處找到這個鏈接。
開始使用
要開始,請像這樣啟動一個新的Django項(xiàng)目。
django-admin startproject my_proj
然后,創(chuàng)建一個示例應(yīng)用程序。
cd my_proj python manage.py startapp my_app
更新settings.py 。
INSTALLED_APPS += [
'my_app'
]這里是你在指南中要遵循的目錄結(jié)構(gòu)。
├── db.sqlite3 ├── manage.py ├── my_app/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations/ │ ├── models.py │ ├── templates/ │ │ ├── base.html │ │ └── index.html │ ├── tests.py │ └── views.py ├── my_proj/ │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── top50contry.csv └── requirements.txt
數(shù)據(jù)準(zhǔn)備
在跳轉(zhuǎn)到實(shí)際代碼之前,我們首先需要將所有數(shù)據(jù)推送到數(shù)據(jù)庫。
我已經(jīng)創(chuàng)建了一個名為TopSongPoularity 的基本模型來存儲數(shù)據(jù)集的必要信息。
下面是my_app 的models.py 。
## my_app/models.py
from django.db import models
class TopSongPoularity(models.Model):
title = models.CharField(max_length = 220)
artist = models.CharField(max_length = 220)
top_genre = models.CharField(max_length = 220)
year = models.IntegerField()
pop = models.IntegerField()
duration = models.IntegerField()
country = models.CharField(max_length = 100)
def __str__(self):
return self.title現(xiàn)在你已經(jīng)創(chuàng)建了模型,用下面的方法將其遷移到數(shù)據(jù)庫中。
python manage.py makemigrations python manage.py migrate
接下來,我們要把所有的CSV數(shù)據(jù)推送到數(shù)據(jù)庫中,所以我們要用shell來執(zhí)行一個腳本。
python manage.py shell
在shell中運(yùn)行下面的腳本,將CSV數(shù)據(jù)推送到數(shù)據(jù)庫中。
#Django Shell
import csv
from datetime import datetime
from my_app.models import TopSongPoularity
with open('top50contry.csv', 'r') as fin:
reader = csv.reader(fin)
headers = next(reader, None)
for row in reader:
obj = {
"title": row[1],
"artist": row[2],
"top_genre": row[3],
"year": int(row[4]),
"pop": int(row[15]),
"duration": int(row[12]),
"country": row[16]
}
TopSongPoularity.objects.create(**obj)創(chuàng)建視圖
接下來,讓我們來編寫視圖。ListTopSongs 是一個基于類的視圖(CBV),它繼承了View 類。在該類的get() 方法中,它接受查詢參數(shù)并相應(yīng)地過濾QuerySet。在QuerySet被過濾后,它再調(diào)用get_paginated_context() ,以獲得序列化格式的分頁數(shù)據(jù)。
getCountries() 是一個基于函數(shù)的視圖(FBV),它為數(shù)據(jù)庫中所有獨(dú)特的國家返回JSON輸出。
#my_app/views.py
import json
from django.core.paginator import Paginator
from django.core.serializers import serialize
from django.http import JsonResponse
from django.shortcuts import render
from django.views import View
from .models import TopSongPoularity
def index(request):
return render(request, "index.html", {})
class ListTopSongs(View):
# set default page limit as 10
page_limit = 10 # default
'''
Helper method to get the pagination context
out of queryset of given page number with limit.
Args:
queryset: Filtered queryset object
page: a number representing the page number
limit: the result count, per page.
Returns the JSON of queryset for the given page,
with pagination meta info.
'''
def get_paginated_context(self, queryset, page, limit):
if not page: page = 1 # if no page provided, set 1
# if limit specified, set the page limit
if limit:
self.page_limit = limit
# instantiate the paginator object with queryset and page limit
paginator = Paginator(queryset, self.page_limit)
# get the page object
page_obj = paginator.get_page(page)
# serialize the objects to json
serialized_page = serialize("json", page_obj.object_list)
# get only required fields from the serialized_page json.
serialized_page = [obj["fields"] for obj in json.loads(serialized_page)]
# return the context.
return {
"data": serialized_page,
"pagination": {
"page": page,
"limit": limit,
"has_next": page_obj.has_next(),
"has_prev": page_obj.has_previous(),
"total": queryset.count()
}
}
'''
GET method for this View.
'''
def get(self, request, *args, **kwargs):
# fetch the query params
page = request.GET.get('page')
limit = request.GET.get('limit')
country = request.GET.get('country')
start = request.GET.get('start')
end = request.GET.get('end')
sort_by = request.GET.get('sort_by')
# get all results from DB.
queryset = TopSongPoularity.objects.all()
'''filter the queryset object based on query params'''
# 1. on basis of country
if country and country != "all":
queryset = queryset.filter(country=country)
# 2. On basis of date (start and end date)
if start and end:
if start != "0" and end != "0":
queryset = queryset.filter(
year__gte = start,
year__lte = end
)
# 3. Sorting the filtered queryset
if sort_by and sort_by != "0":
queryset = queryset.order_by(sort_by)
# return the serialized output by
# calling method 'get_paginated_context'
to_return = self.get_paginated_context(queryset, page, limit)
return JsonResponse(to_return, status = 200)
def getCountries(request):
# get Countries from the database
# excluding null and blank values
if request.method == "GET" and request.is_ajax():
country = TopSongPoularity.objects.all().\
values_list('country').distinct()
country = [c[0] for c in list(country)]
return JsonResponse({
"country": country,
}, status = 200)創(chuàng)建URL
現(xiàn)在,讓我們對視圖進(jìn)行路由。
#my_proj/urls.py
from django.urls import path
from my_app.views import ListTopSongs, index, getCountries
urlpatterns = [
path('api/get/top_songs', ListTopSongs.as_view()),
path('api/get/countries', getCountries, name = "get_countries"),
path('', index)
]創(chuàng)建模板
現(xiàn)在后端代碼已經(jīng)完成,讓我們轉(zhuǎn)到前端。
我使用了一個基本模板(base.html),包括Bootstrap和jQuery庫。
<!--templates/base.html-->
<!--doctype HTML-->
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:locale" content="en_US" />
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Log rocket</title>
<!-- css cdn includes -->
<link rel="stylesheet" >
{% block style %}
{% endblock style %}
</head>
<body>
{% block content %}
{% endblock %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
{% block javascript %}
{% endblock javascript %}
</body>
</html>現(xiàn)在,讓我們創(chuàng)建index.html ,顯示帶有過濾器的表格。這個模板文件繼承了base.html ,并創(chuàng)建了一個帶有標(biāo)題和空主體的表格。最后,它還包含兩個 "下一步 "和 "上一步 "的按鈕。
index.html 的其余部分,即JavaScript部分,將在下面解釋。
<!--templates/index.html-->
{% extends 'base.html' %}
{% block content %}
<section>
<div class="container-fluid">
<div class="row">
<div class="col-sm-2 col-2">
<div class="form-group">
<label for="country">Country</label>
<select class="form-control" id="countries" url={% url 'get_countries' %}>
</select>
</div>
</div>
<div class="col-sm-2 col-2">
<div class="form-group">
<label for="year">Year</label>
<select class="form-control" id="year">
<option value="0" start=0 end=0>All years</option>
<option value="1" start=2019 end=2020>2019-2020</option>
<option value="2" start=2018 end=2019>2018-2019</option>
<option value="3" start=2016 end=2018>2016-2018</option>
<option value="4" start=2010 end=2016>2010-2016</option>
<option value="5" start=1900 end=2010>1900-2010</option>
</select>
</div>
</div>
<div class="col-sm-2 col-2">
<div class="form-group">
<label for="sort">Sort By</label>
<select class="form-control" id="sort">
<option value="0">No option selected</option>
<option value="duration">Duration</option>
<option value="pop">Pop</option>
<option value="year">Year</option>
</select>
</div>
</div>
</div>
</div>
</section>
<section>
<div class="container">
<div class="row justify-content-center table-responsive">
<div id="result-count" class="text-right">
<span class='font-weight-bold'></span> results found.
</div>
<div id="page-count" class="text-right">Page:
<span class='font-weight-bold'></span>
</div>
<table class="table table-light table-bordered table-hover" id="hero_table" data-toggle="table">
<thead class="thead-dark">
<tr>
<th data-field="title">Title</th>
<th data-field="country">Country</th>
<th data-field="top_genre">Top Genre</th>
<th data-field="artist">Artist</th>
<th data-field="duration">Duration</th>
<th data-field="pop">Pop</th>
<th data-field="year">Year</th>
</tr>
</thead>
<tbody id="table_body">
</tbody>
</table>
</div>
<div class="row justify-content-center">
<nav aria-label="navigation">
<ul class="pagination">
<li class="page-item">
<button class="btn btn-primary page-link" id = "previous">Previous</button>
</li>
<li class="page-item pull-right">
<button class="btn btn-primary page-link" id="next">Next</button>
</li>
</ul>
</nav>
</div>
</div>
</section>
{% endblock content %}創(chuàng)建客戶端腳本
本指南的最后一部分是使用AJAX連接前端和后端。請參考下面代碼片斷中提到的注釋。
<!---templates/index.html--->
{% block javascript %}
<script>
// maintaining the state of each variable.
var current_page = 1; // maintains the current page
var page_limit = 10; // the limit of results shown on page.
var sort_by = ""; // maintains the select option for sort_by
var country = ""; // maintains the select option for country
var start_year = ""; // maintains the select option for start_yr
var end_year = ""; // maintains the select option for end_yr
function get_list_url(page) {
// returns the consructed url with query params.
return `api/get/top_songs?page=${page}&limit=${page_limit}&country=${country}&sort_by=${sort_by}&start=${start_year}&end=${end_year}`;
}
function getCountries() {
// call the ajax and populates the country select options
$.ajax({
method: 'GET',
url: $("#countries").attr("url"),
success: function (response) {
countries_option = "<option value='all' selected>All Countries</option>";
$.each(response["country"], function (a, b) {
countries_option += "<option>" + b + "</option>"
});
$("#countries").html(countries_option)
},
error: function (response) {
console.log(response)
}
});
}
// On select change of the country select, call the getAPIData
$("#countries").on("change", function (e) {
current_page = 1;
country = this.value
getAPIData(get_list_url(current_page));
});
// On select change of the year select, call the getAPIData
$("#year").on("change", function (e) {
current_page = 1;
start_year = $(this).find(':selected').attr("start");
end_year = $(this).find(':selected').attr("end");
getAPIData(get_list_url(current_page));
})
// On select change of the sort select, call the getAPIData with sortby.
$("#sort").on("change", function (e) {
current_page = 1;
sort_by = this.value
getAPIData(get_list_url(current_page));
})
// Helper method that popluates the html table with next and prev
// url, and current page number.
function putTableData(response) {
// creating table row for each response and
// pushing to the html cntent of table body of table_body table
let row;
$("#table_body").html("");
if (response["data"].length > 0) {
$.each(response["data"], function (a, b) {
row = "<tr> <td>" + b.title + "</td>" +
"<td>" + b.country + "</td>" +
"<td>" + b.top_genre + "</td>" +
"<td>" + b.artist + "</td>" +
"<td>" + b.duration + "</td>" +
"<td>" + b.pop + "</td>" +
"<td>" + b.year + "</td>" +
$("#table_body").append(row);
});
}
else{
// if there is no results found!
$("#table_body").html("No results found.");
}
if (response.pagination.has_prev) {
// sets the previous page url.
$("#previous").attr("data-url", get_list_url(current_page - 1));
$("#previous").attr("disabled", false);
} else {
// if there is no prev page available, disable the btn.
$("#previous").attr("disabled", true);
}
if (response.pagination.has_next) {
// sets the next page url.
$("#next").attr("data-url", get_list_url(current_page + 1));
$("#next").attr("disabled", false);
} else {
// if there is no next page available, disable the btn.
$("#next").attr("disabled", true)
}
}
// On click of next/prev button, call the getAPIData with the given url.
$(".page-link").click(function (e) {
e.preventDefault();
let url = $(this).attr("data-url");
getAPIData(url);
})
// Main method which calls AJAX to get the data from backend.
function getAPIData(url) {
$.ajax({
method: 'GET',
url: url,
success: function (response) {
current_page = parseInt(response.pagination.page)
putTableData(response);
// put the total result count.
$("#result-count span").html(response.pagination.total)
$("#page-count span").html(response.pagination.page)
},
error: function (response) {
$("#hero_table").hide();
}
});
}
//on page load, call this two methods.
getAPIData(get_list_url(current_page));
getCountries()
</script>
{% endblock javascript %}結(jié)語
在本指南中,你已經(jīng)學(xué)會了如何使用AJAX以及如何與后端進(jìn)行異步通信。過濾表格數(shù)據(jù)是一個常見的處理場景,我希望本指南能讓你更好地了解如何處理過濾數(shù)據(jù)。
如果你愿意,你也可以使用REST框架,如Django REST框架來保持簡單。
如果你在遵循本指南的過程中遇到任何問題,你可以隨時查看我的Github倉庫來查看整個項(xiàng)目。
到此這篇關(guān)于在Django中動態(tài)地過濾查詢集的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Django動態(tài)過濾查詢集內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Pycharm中import?torch報錯,python中import?torch不報錯的解決
這篇文章主要介紹了Pycharm中import?torch報錯,python中import?torch不報錯的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
python實(shí)現(xiàn)數(shù)據(jù)預(yù)處理之填充缺失值的示例
下面小編就為大家分享一篇python實(shí)現(xiàn)數(shù)據(jù)預(yù)處理之填充缺失值的示例。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12
Python 由字符串函數(shù)名得到對應(yīng)的函數(shù)(實(shí)例講解)
下面小編就為大家?guī)硪黄狿ython 由字符串函數(shù)名得到對應(yīng)的函數(shù)(實(shí)例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
python 連接各類主流數(shù)據(jù)庫的實(shí)例代碼
下面小編就為大家分享一篇python 連接各類主流數(shù)據(jù)庫的實(shí)例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Python實(shí)現(xiàn)圖片灰度化以及圖片顯示的兩種方法
這篇文章給大家介紹了Python實(shí)現(xiàn)圖片,灰度化以及圖片顯示的兩種方法并通過代碼示例和圖文結(jié)合的方式給大家講解的非常詳細(xì),需要的朋友可以參考下2024-02-02
Pandas?DataFrame數(shù)據(jù)修改值的方法
本文主要介紹了Pandas?DataFrame修改值,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03

