上一篇介紹 API 測試程式,還缺少頁面程式,和測試程式,基本上這邊會等前端把頁面寫好,才會交由後端來串接,這個專案是一個簡單的專案,實作最基本的頁面程式,並寫頁面測試程式,現在網站設計都不會使用重新載入整頁,因為這樣很浪費流量,都使用 ajax 的方式向後端 API 更新資料,拿到資料後才會更新網頁,這樣就不會浪費流量了.
串接頁面其實會需要用到html、css、javascript、jquery 等前端技術,html和css只要會基本就好,比較著重javascript和jqery的使用,有時候會和前端工程師配合調整頁面,使整體使用體驗更好.
layout.jinja2 : html樣板
<!DOCTYPE html>
<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="{{request.static_url('ithome_pellok_2018:static/pyramid-16x16.png')}}">
<title>Alchemy Scaffold for The Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('ithome_pellok_2018:static/theme.css')}}" rel="stylesheet">
</head>
<body>
{% block content %}
<p>No content</p>
{% endblock content %}
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
{% block javascript %}
{% endblock javascript %}
</body>
</html>
home.jinja2 : 首頁 html 程式
{% extends "layout.jinja2" %}
{% block content %}
<div class="content">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<h1>Hello World Mock Server</h1>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<form class="form-inline">
<div class="form-group" style="display:inline-flex;">
<label style="margin:5px 10px; ">name:</label>
<input type="text" class="form-control" name="name" id="add_name" placeholder="name">
<label style="margin:5px 10px;">value:</label>
<input type="text" class="form-control" name="value" id="add_value" placeholder="value">
</div>
<button type="submit" class="btn btn-primary add_btn">Add</button>
</form>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<table class="table table-hover">
<thead>
<tr>
<th>name</th>
<th>value</th>
</tr>
</thead>
<tbody class="api_table">
</tbody>
</table>
</div>
</div>
{% endblock content %}
{% block javascript %}
<script>
// read data
$(document).ready(function(){
search();
});
$(document).on('click', '.add_btn', function() {
var form_data = new FormData();
form_data.append('name', $('#add_name').val());
form_data.append('value', $('#add_value').val());
create(form_data);
});
function search(){
$.ajax({
url: "{{ request.route_url('api')}}",
type: 'GET',
contentType: false,
processData: false,
beforeSend: function () {
},
error: function (xhr) {
alert('Ajax request 發生錯誤');
},
success: function (response) {
if (response['status']) {
{#alert("搜尋成功");#}
$('.api_table tr').remove();
var html = "";
response['response'].forEach(function (value, index) {
html +="<tr><td>"+value['name']+"</td><td>"+value['value']+"</td></tr>"
});
$('.api_table').append(html);
console.log(response);
} else {
}
},
complete: function () {
}
});
}
function create(form_data) {
$.ajax({
url: "{{ request.route_url('api')}}",
type: 'POST',
data: form_data,
contentType: false,
processData: false,
beforeSend: function () {
$('.add_btn').attr('disabled', true);
},
error: function (xhr) {
alert('Ajax request 發生錯誤');
},
success: function (response) {
if (response['status']) {
{#alert("創建成功");#}
console.log(response);
$('.add_btn').attr('disabled', false);
} else {
$('.add_btn').attr('disabled', false);
}
},
complete: function () {
$('.add_btn').attr('disabled', false);
search()
}
});
}
</script>
{% endblock javascript %}
page/init.py : 定義頁面路由
from . import page
def includeme(config):
config.add_route('page.home', '/')
config.add_view(
page.PageView, attr='home',
route_name='page.home',
renderer='../../templates/home.jinja2'
)
page.py : 定義頁面主邏輯
# coding=utf8
from __future__ import unicode_literals
from pyramid.response import Response
class PageView(object):
def __init__(self, request):
super(PageView, self).__init__()
self.request = request
def home(self):
"""
首頁
"""
try:
return {}
except Exception as e:
return Response(str(e), content_type='text/plain', status=500)
test_function.py : 頁面測試
import transaction
import unittest
import webtest
from ithome_pellok_2018.models.meta import Base
from ithome_pellok_2018.models import get_tm_session
from ithome_pellok_2018 import main
class FunctionalTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
settings = {
'sqlalchemy.url': 'sqlite://',
'auth.secret': 'seekrit',
}
app = main({}, **settings)
cls.testapp = webtest.TestApp(app)
session_factory = app.registry['dbsession_factory']
cls.engine = session_factory.kw['bind']
Base.metadata.create_all(bind=cls.engine)
with transaction.manager:
dbsession = get_tm_session(session_factory, transaction.manager)
@classmethod
def tearDownClass(cls):
Base.metadata.drop_all(bind=cls.engine)
def test_home(self):
res = self.testapp.get('/', status=200)
self.assertTrue(b'Hello World Mock Server' in res.body)
在串接頁面的時候會先把公用性高的主樣式程式(header、footer、sildebar)抽出來,寫在loayout.jinja2,每頁面都會使用套用這個樣式,只要更動頁面內容就好,向 home.jinja2 的內容只是首頁內容資料,這邊只實作到基礎創建和搜尋功能.
bitbucket Repostory
Template Jinja2