tornado web高級開發項目之抽屜官網的頁面登錄驗證、form驗證、點贊、評論、文章分頁處理、發送郵箱驗證碼、登錄驗證碼、註冊、發佈文章、上傳圖片

本博文將一步步帶領你實現抽屜官網的各類功能:包括登錄註冊發送郵箱驗證碼登錄驗證碼頁面登錄驗證發佈文章上傳圖片form驗證點贊、評論、文章分頁處理以及基於tornado的後端和ajax的前端數據處理。javascript

轉載請註明出處http://www.cnblogs.com/wanghzh/p/5806514.html
css

抽屜官網:http://dig.chouti.com/html

1、配置(settings)

settings = {
    'template_path': 'views',    #模板文件路徑
    'static_path': 'statics',        #靜態文件路徑
    'static_url_prefix': '/statics/',  #靜態文件前綴
    'autoreload': True,
    'ui_methods': mt
}

2、路由配置

application = tornado.web.Application([
    (r"/index", home.IndexHandler),    #主頁
    (r"/check_code", account.CheckCodeHandler),  #驗證碼
    (r"/send_msg", account.SendMsgHandler),  #郵箱驗證碼
    (r"/register", account.RegisterHandler),  #註冊
    (r"/login", account.LoginHandler),    #登錄
    (r"/upload_image", home.UploadImageHandler),  #上傳圖片
    (r"/comment", home.CommentHandler),  #評論
    (r"/favor", home.FavorHandler),   #點贊
], **settings)

3、文件夾分類

下面咱們將根據上圖文件目錄由上到下作一一分析:前端

4、準備工做

  本項目全部前端反饋均是經過BaseResponse類實現的:java

class BaseResponse:

    def __init__(self):
        self.status = False    #狀態信息,是否註冊成功,是否登錄成功,是否點同意功、是否評論成功等
        self.code = StatusCodeEnum.Success
        self.data = None        #前端須要展現的數據
        self.summary = None    #錯誤信息
        self.message = {}    #字典類型的錯誤信息

本文參考了大量前端和後端基礎知識,從入門到精通的連接以下:python

前端:mysql

後端:jquery

數據庫:git

緩存:web

5、core:業務處理類handler須要繼承的父類

import tornado.web
from backend.session.session import SessionFactory


class BaseRequestHandler(tornado.web.RequestHandler):

    def initialize(self):

        self.session = SessionFactory.get_session_obj(self)

6、form:用於form驗證的文件,這是一個自定義的tornado form驗證模塊

  fields:包含字符串、郵箱、數字、checkbox、文件類型驗證

  forms:核心驗證處理,返回驗證是否成功self._valid_status、成功後的數據提self._value_dict、錯誤信息self._error_dict

class Field:

    def __init__(self):

        self.is_valid = False
        self.name = None
        self.value = None
        self.error = None

    def match(self, name, value):
        self.name = name

        if not self.required:
            self.is_valid = True
            self.value = value
        else:
            if not value:
                if self.custom_error_dict.get('required', None):
                    self.error = self.custom_error_dict['required']
                else:
                    self.error = "%s is required" % name
            else:
                ret = re.match(self.REGULAR, value)
                if ret:
                    self.is_valid = True
                    self.value = value
                else:
                    if self.custom_error_dict.get('valid', None):
                        self.error = self.custom_error_dict['valid']
                    else:
                        self.error = "%s is invalid" % name
初始化
class StringField(Field):

    REGULAR = "^.*$"

    def __init__(self, custom_error_dict=None, required=True):

        self.custom_error_dict = {}  # {'required': 'IP不能爲空', 'valid': 'IP格式錯誤'}
        if custom_error_dict:
            self.custom_error_dict.update(custom_error_dict)

        self.required = required

        super(StringField, self).__init__()
字符串匹配
class IPField(Field):

    REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"

    def __init__(self, custom_error_dict=None, required=True):

        self.custom_error_dict = {}  # {'required': 'IP不能爲空', 'valid': 'IP格式錯誤'}
        if custom_error_dict:
            self.custom_error_dict.update(custom_error_dict)

        self.required = required
        super(IPField, self).__init__()
ip匹配
class EmailField(Field):

    REGULAR = "^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"

    def __init__(self, custom_error_dict=None, required=True):

        self.custom_error_dict = {}  # {'required': 'IP不能爲空', 'valid': 'IP格式錯誤'}
        if custom_error_dict:
            self.custom_error_dict.update(custom_error_dict)

        self.required = required
        super(EmailField, self).__init__()
郵箱匹配
class IntegerField(Field):

    REGULAR = "^\d+$"

    def __init__(self, custom_error_dict=None, required=True):

        self.custom_error_dict = {}  # {'required': 'IP不能爲空', 'valid': 'IP格式錯誤'}
        if custom_error_dict:
            self.custom_error_dict.update(custom_error_dict)

        self.required = required
        super(IntegerField, self).__init__()
數字匹配
class CheckBoxField(Field):

    REGULAR = "^\d+$"

    def __init__(self, custom_error_dict=None, required=True):

        self.custom_error_dict = {}  # {'required': 'IP不能爲空', 'valid': 'IP格式錯誤'}
        if custom_error_dict:
            self.custom_error_dict.update(custom_error_dict)

        self.required = required
        super(CheckBoxField, self).__init__()

    def match(self, name, value):
        self.name = name

        if not self.required:
            self.is_valid = True
            self.value = value
        else:
            if not value:
                if self.custom_error_dict.get('required', None):
                    self.error = self.custom_error_dict['required']
                else:
                    self.error = "%s is required" % name
            else:
                if isinstance(name, list):
                    self.is_valid = True
                    self.value = value
                else:
                    if self.custom_error_dict.get('valid', None):
                        self.error = self.custom_error_dict['valid']
                    else:
                        self.error = "%s is invalid" % name
checkbox匹配
class FileField(Field):

    REGULAR = "^(\w+\.pdf)|(\w+\.mp3)|(\w+\.py)$"

    def __init__(self, custom_error_dict=None, required=True):

        self.custom_error_dict = {}  # {'required': 'IP不能爲空', 'valid': 'IP格式錯誤'}
        if custom_error_dict:
            self.custom_error_dict.update(custom_error_dict)

        self.required = required

        super(FileField, self).__init__()

    def match(self, name, file_name_list):
        flag = True
        self.name = name

        if not self.required:
            self.is_valid = True
            self.value = file_name_list
        else:
            if not file_name_list:
                if self.custom_error_dict.get('required', None):
                    self.error = self.custom_error_dict['required']
                else:
                    self.error = "%s is required" % name
                flag = False
            else:
                for file_name in file_name_list:
                    if not file_name or not file_name.strip():
                        if self.custom_error_dict.get('required', None):
                            self.error = self.custom_error_dict['required']
                        else:
                            self.error = "%s is required" % name
                        flag = False
                        break
                    else:
                        ret = re.match(self.REGULAR, file_name)
                        if not ret:
                            if self.custom_error_dict.get('valid', None):
                                self.error = self.custom_error_dict['valid']
                            else:
                                self.error = "%s is invalid" % name
                            flag = False
                            break

            self.is_valid = flag

    def save(self, request, upload_to=""):

        file_metas = request.files[self.name]
        for meta in file_metas:
            file_name = meta['filename']
            file_path_name = os.path.join(upload_to, file_name)
            with open(file_path_name, 'wb') as up:
                up.write(meta['body'])

        upload_file_path_list = map(lambda path: os.path.join(upload_to, path), self.value)
        self.value = list(upload_file_path_list)
文件匹配

核心驗證處理:

from backend.form import fields


class BaseForm:

    def __init__(self):
        self._value_dict = {} #數據字典
        self._error_dict = {} #錯誤信息字典
        self._valid_status = True #是否驗證成功

    def valid(self, handler):

        for field_name, field_obj in self.__dict__.items():
            if field_name.startswith('_'):  #過濾私有字段
                continue

            if type(field_obj) == fields.CheckBoxField:  #checkbox處理
                post_value = handler.get_arguments(field_name, None) 
            elif type(field_obj) == fields.FileField:  #文件處理
                post_value = []
                file_list = handler.request.files.get(field_name, [])
                for file_item in file_list:
                    post_value.append(file_item['filename'])
            else:
                post_value = handler.get_argument(field_name, None)

            field_obj.match(field_name, post_value)  #匹配
            if field_obj.is_valid:                    #若是驗證成功
                self._value_dict[field_name] = field_obj.value  #提取數據
            else:
                self._error_dict[field_name] = field_obj.error  #錯誤信息
                self._valid_status = False
        return self._valid_status    #返回是否驗證成功

7、如何應用上述form驗證模塊:

以註冊爲例:

前端:

<div class="header">
                <span>註冊</span>
                <div class="dialog-close" onclick="CloseDialog('#accountDialog');">X</div>
            </div>
            <div class="content">
                <div style="padding: 0 70px">
                    <div class="tips">
                        <span>輸入註冊信息</span>
                    </div>
                    <div id="register_error_summary" class="error-msg">

                    </div>
                    <div class="inp">
                        <input name="username" type="text" placeholder="請輸入用戶名" />

                    </div>
                    <div class="inp">
                        <input name="email" id="email" type="text" placeholder="請輸入郵箱" />
                    </div>
                    <div class="inp">
                        <input name="email_code" class="email-code" type="text" placeholder="請輸入郵箱驗證碼" />
                        <a id="fetch_code" class="fetch-code" href="javascript:void(0);">獲取驗證碼</a>
                    </div>
                    <div class="inp">
                        <input name="password" type="password" placeholder="請輸入密碼" />
                    </div>
                    <div class="inp">
                        <div class="submit" onclick="SubmitRegister(this);">
                            <span>註冊</span>
                            <span class="hide">
                                <img src="/statics/images/loader.gif" style="height: 16px;width: 16px">
                                <span>正在註冊</span>
                            </span>
                        </div>
                    </div>
                </div>
            </div>
html
js:
/*
        點擊註冊按鈕
        */
        function SubmitRegister(ths){
            $('#register_error_summary').empty();
            $('#model_register .inp .error').remove();

            $(ths).children(':eq(0)').addClass('hide');
            $(ths).addClass('not-allow').children(':eq(1)').removeClass('hide');

            var post_dict = {};
            $('#model_register input').each(function(){
                post_dict[$(this).attr("name")] = $(this).val();  #將全部input標籤內容提取出來,以對應name爲key,值爲value放入post_dict字典
            });

            $.ajax({
                url: '/register', #提交的url
                type: 'POST',    #提交方式
                data: post_dict,  #提交數據
                dataType: 'json',  #數據格式
                success: function(arg){
                    if(arg.status){
                        window.location.href = '/index'; #驗證成功跳轉至主頁
                    }else{
                        $.each(arg.message, function(k,v){
                            //<span class="error">s</span>
                            var tag = document.createElement('span'); #驗證失敗建立標籤
                            tag.className = 'error'; 
                            tag.innerText = v; #存入錯誤信息
                            $('#model_register input[name="'+ k +'"]').after(tag); #將標籤加入html中
                        })
                    }
                }
            });

            $(ths).removeClass('not-allow').children(':eq(1)').addClass('hide');
            $(ths).children(':eq(0)').removeClass('hide');
        }

 後臺處理:

首先須要編寫RegisterForm:

class RegisterForm(BaseForm):  #須要繼承上面的form驗證核心處理類

    def __init__(self): #初始化每個input標籤的name
        self.username = StringField()  #input標籤name=對應類型的類
        self.email = EmailField()
        self.password = StringField()
        self.email_code = StringField()

        super(RegisterForm, self).__init__()

後臺RegisterHandler:

class RegisterHandler(BaseRequestHandler):
    def post(self, *args, **kwargs):
        rep = BaseResponse()   #總的返回前端的類,包含是否註冊成功的狀態、錯誤信息
        form = account.RegisterForm()  #實例化RegisterForm
        if form.valid(self):  #調用baseform核心驗證處理函數valid,返回是否驗證成功
            current_date = datetime.datetime.now()
            limit_day = current_date - datetime.timedelta(minutes=1)
            conn = ORM.session()  #獲取數據庫session對象
        #查看驗證碼是否過時 is_valid_code = conn.query(ORM.SendMsg).filter(ORM.SendMsg.email == form._value_dict['email'], ORM.SendMsg.code == form._value_dict['email_code'], ORM.SendMsg.ctime > limit_day).count() if not is_valid_code: rep.message['email_code'] = '郵箱驗證碼不正確或過時' self.write(json.dumps(rep.__dict__)) return has_exists_email = conn.query(ORM.UserInfo).filter(ORM.UserInfo.email == form._value_dict['email']).count()#郵箱是否存在 if has_exists_email: rep.message['email'] = '郵箱已經存在' self.write(json.dumps(rep.__dict__)) return has_exists_username = conn.query(ORM.UserInfo).filter( ORM.UserInfo.username == form._value_dict['username']).count() #用戶名是否存在 if has_exists_username: rep.message['email'] = '用戶名已經存在' self.write(json.dumps(rep.__dict__)) return
        #按數據庫表的列訂製form._value_dict
       form._value_dict['ctime'] = current_date form._value_dict.pop('email_code') obj = ORM.UserInfo(**form._value_dict) conn.add(obj)
conn.flush() conn.refresh(obj) #將自增id也提取出來 user_info_dict = {'nid': obj.nid, 'email': obj.email, 'username': obj.username} conn.query(ORM.SendMsg).filter_by(email=form._value_dict['email']).delete()#刪除本次郵箱驗證碼 conn.commit() conn.close() self.session['is_login'] = True #註冊成功後定義登錄成功 self.session['user_info'] = user_info_dict 用戶信息寫入session rep.status = True else: rep.message = form._error_dict #錯誤信息 self.write(json.dumps(rep.__dict__)) #返回給前端,前端ajax success接收並處理,在前端頁面展現

8、session ,本session是基於tornado的自定義session  

1.應用工廠方法模式定義session保存的位置,用戶只需在配置文件修改便可

class SessionFactory:

    @staticmethod
    def get_session_obj(handler):
        obj = None

        if config.SESSION_TYPE == "cache": #緩存
            obj = CacheSession(handler)
        elif config.SESSION_TYPE == "memcached": #memcached
      obj = MemcachedSession(handler) 
    elif config.SESSION_TYPE == "redis": #radis
      obj = RedisSession(handler)
    return obj

2.緩存session

class CacheSession:
    session_container = {}
    session_id = "__sessionId__"

    def __init__(self, handler):
        self.handler = handler
        client_random_str = handler.get_cookie(CacheSession.session_id, None)
        if client_random_str and client_random_str in CacheSession.session_container:
            self.random_str = client_random_str
        else:
            self.random_str = create_session_id()
            CacheSession.session_container[self.random_str] = {}

        expires_time = time.time() + config.SESSION_EXPIRES
        handler.set_cookie(CacheSession.session_id, self.random_str, expires=expires_time)

    def __getitem__(self, key):
        ret = CacheSession.session_container[self.random_str].get(key, None)
        return ret

    def __setitem__(self, key, value):
        CacheSession.session_container[self.random_str][key] = value

    def __delitem__(self, key):
        if key in CacheSession.session_container[self.random_str]:
            del CacheSession.session_container[self.random_str][key]
緩存session

3.memcache session

import memcache

conn = memcache.Client(['192.168.11.119:12000'], debug=True, cache_cas=True)

class MemcachedSession:
    session_id = "__sessionId__"

    def __init__(self, handler):
        self.handler = handler
        # 從客戶端獲取隨機字符串
        client_random_str = handler.get_cookie(CacheSession.session_id, None)
        # 若是從客戶端獲取到了隨機字符串
        #
        if client_random_str and conn.get(client_random_str):
            self.random_str = client_random_str
        else:
            self.random_str = create_session_id()
            conn.set(self.random_str, json.dumps({}), config.SESSION_EXPIRES)
            #CacheSession.session_container[self.random_str] = {}

        conn.set(self.random_str, conn.get(self.random_str), config.SESSION_EXPIRES)

        expires_time = time.time() + config.SESSION_EXPIRES
        handler.set_cookie(MemcachedSession.session_id, self.random_str, expires=expires_time)

    def __getitem__(self, key):
        # ret = CacheSession.session_container[self.random_str].get(key, None)
        ret = conn.get(self.random_str)
        ret_dict = json.loads(ret)
        result = ret_dict.get(key,None)
        return result

    def __setitem__(self, key, value):
        ret = conn.get(self.random_str)
        ret_dict = json.loads(ret)
        ret_dict[key] = value
        conn.set(self.random_str, json.dumps(ret_dict), config.SESSION_EXPIRES)

        # CacheSession.session_container[self.random_str][key] = value

    def __delitem__(self, key):
        ret = conn.get(self.random_str)
        ret_dict = json.loads(ret)
        del ret_dict[key]
        conn.set(self.random_str, json.dumps(ret_dict), config.SESSION_EXPIRES)
memcache session

4.radis session

import redis

pool = redis.ConnectionPool(host='192.168.11.119', port=6379)
r = redis.Redis(connection_pool=pool)

class RedisSession:
    session_id = "__sessionId__"

    def __init__(self, handler):
        self.handler = handler
        # 從客戶端獲取隨機字符串
        client_random_str = handler.get_cookie(CacheSession.session_id, None)
        # 若是從客戶端獲取到了隨機字符串
        if client_random_str and r.exists(client_random_str):
            self.random_str = client_random_str
        else:
            self.random_str = create_session_id()
            r.hset(self.random_str,None,None)

            # conn.set(self.random_str, json.dumps({}), config.SESSION_EXPIRES)
            # CacheSession.session_container[self.random_str] = {}
        r.expire(self.random_str, config.SESSION_EXPIRES)
        # conn.set(self.random_str, conn.get(self.random_str), config.SESSION_EXPIRES)


        expires_time = time.time() + config.SESSION_EXPIRES
        handler.set_cookie(RedisSession.session_id, self.random_str, expires=expires_time)

    def __getitem__(self, key):
        # ret = CacheSession.session_container[self.random_str].get(key, None)
        result = r.hget(self.random_str,key)
        if result:
            ret_str = str(result, encoding='utf-8')
            try:
                result = json.loads(ret_str)
            except:
                result = ret_str
            return result
        else:
            return result

    def __setitem__(self, key, value):
        if type(value) == dict:
            r.hset(self.random_str, key, json.dumps(value))
        else:
            r.hset(self.random_str, key, value)

        # CacheSession.session_container[self.random_str][key] = value

    def __delitem__(self, key):
        r.hdel(self.random_str,key)
radis session

9、驗證碼:

注:驗證碼須要依賴session。

#!/usr/bin/env python
#coding:utf-8

import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter

_letter_cases = "abcdefghjkmnpqrstuvwxy"  # 小寫字母,去除可能干擾的i,l,o,z
_upper_cases = _letter_cases.upper()  # 大寫字母
_numbers = ''.join(map(str, range(3, 10)))  # 數字
init_chars = ''.join((_letter_cases, _upper_cases, _numbers))

def create_validate_code(size=(120, 30),
                         chars=init_chars,
                         img_type="GIF",
                         mode="RGB",
                         bg_color=(255, 255, 255),
                         fg_color=(0, 0, 255),
                         font_size=18,
                         font_type="Monaco.ttf",
                         length=4,
                         draw_lines=True,
                         n_line=(1, 2),
                         draw_points=True,
                         point_chance = 2):
    '''
    @todo: 生成驗證碼圖片
    @param size: 圖片的大小,格式(寬,高),默認爲(120, 30)
    @param chars: 容許的字符集合,格式字符串
    @param img_type: 圖片保存的格式,默認爲GIF,可選的爲GIF,JPEG,TIFF,PNG
    @param mode: 圖片模式,默認爲RGB
    @param bg_color: 背景顏色,默認爲白色
    @param fg_color: 前景色,驗證碼字符顏色,默認爲藍色#0000FF
    @param font_size: 驗證碼字體大小
    @param font_type: 驗證碼字體,默認爲 ae_AlArabiya.ttf
    @param length: 驗證碼字符個數
    @param draw_lines: 是否劃干擾線
    @param n_lines: 干擾線的條數範圍,格式元組,默認爲(1, 2),只有draw_lines爲True時有效
    @param draw_points: 是否畫干擾點
    @param point_chance: 干擾點出現的機率,大小範圍[0, 100]
    @return: [0]: PIL Image實例
    @return: [1]: 驗證碼圖片中的字符串
    '''

    width, height = size # 寬, 高
    img = Image.new(mode, size, bg_color) # 建立圖形
    draw = ImageDraw.Draw(img) # 建立畫筆

    def get_chars():
        '''生成給定長度的字符串,返回列表格式'''
        return random.sample(chars, length)

    def create_lines():
        '''繪製干擾線'''
        line_num = random.randint(*n_line) # 干擾線條數

        for i in range(line_num):
            # 起始點
            begin = (random.randint(0, size[0]), random.randint(0, size[1]))
            #結束點
            end = (random.randint(0, size[0]), random.randint(0, size[1]))
            draw.line([begin, end], fill=(0, 0, 0))

    def create_points():
        '''繪製干擾點'''
        chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]

        for w in range(width):
            for h in range(height):
                tmp = random.randint(0, 100)
                if tmp > 100 - chance:
                    draw.point((w, h), fill=(0, 0, 0))

    def create_strs():
        '''繪製驗證碼字符'''
        c_chars = get_chars()
        strs = ' %s ' % ' '.join(c_chars) # 每一個字符先後以空格隔開

        font = ImageFont.truetype(font_type, font_size)
        font_width, font_height = font.getsize(strs)

        draw.text(((width - font_width) / 3, (height - font_height) / 3),
                    strs, font=font, fill=fg_color)

        return ''.join(c_chars)

    if draw_lines:
        create_lines()
    if draw_points:
        create_points()
    strs = create_strs()

    # 圖形扭曲參數
    params = [1 - float(random.randint(1, 2)) / 100,
              0,
              0,
              0,
              1 - float(random.randint(1, 10)) / 100,
              float(random.randint(1, 2)) / 500,
              0.001,
              float(random.randint(1, 2)) / 500
              ]
    img = img.transform(size, Image.PERSPECTIVE, params) # 建立扭曲

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 濾鏡,邊界增強(閾值更大)

    return img, strs
check_code.py
class CheckCodeHandler(BaseRequestHandler):
    def get(self, *args, **kwargs):
        stream = io.BytesIO()
        img, code = check_code.create_validate_code()
        img.save(stream, "png")
        self.session["CheckCode"] = code  #利用session保存驗證碼
        self.write(stream.getvalue())

  路由配置:(r"/check_code", account.CheckCodeHandler),

  前端:

 

<img class="check-img" src="/check_code" alt="驗證碼" onclick="ChangeCode(this);">

  js:

<script>
                    function ChangeCode(ths) {
                        ths.src += '?';
                    }
                </script>

10、發送郵箱驗證碼

前端:

<div class="inp">
        <input class="regiter-temp" name="code" class="email-code" type="text" placeholder="請輸入郵箱驗證碼" />
        <a onclick="SendCode(this);" class="fetch-code" >獲取驗證碼</a>
</div>

js:

 function SendCode(ths) {
//            var email = $(ths).prev().val();
            var email = $('#email').val();
            $.ajax({
                url: '/send_code',
                type: 'POST',
                data: {em: email},
                success: function (arg) {
                    console.log(arg);
                },
                error: function () {
                    
                }
                
            });
        }

路由配置:

(r"/send_code", account.SendCodeHandler),

後臺handler:

class SendCodeHandler(BaseRequestHandler):
    def post(self, *args, **kwargs):
        ret = {'status': True, "data": "", "error": ""}
        email = self.get_argument('em', None)
        if email:
            code = commons.random_code()  #獲取隨機驗證碼
            message.email([email,], code) #發送驗證碼到郵箱
            conn = chouti_orm.session()   #獲取數據庫session對象
            obj = chouti_orm.SendCode(email=email,code=code, stime=datetime.datetime.now()) #寫入數據庫
            conn.add(obj)
            conn.commit()
        else:
            ret['status'] = False
            ret['error'] = "郵箱格式錯誤"

        self.write(json.dumps(ret))

 發送郵箱驗證碼函數:

import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr


def email(email_list, content, subject="抽屜新熱榜-用戶註冊"):  #email_list郵件列表,content郵件內容,subject:發送標題
    msg = MIMEText(content, 'plain', 'utf-8')
    msg['From'] = formataddr(["抽屜新熱榜",'wptawy@126.com'])
    msg['Subject'] = subject

    server = smtplib.SMTP("smtp.126.com", 25) 郵箱引擎
    server.login("wptawy@126.com", "JUEmimima") #郵箱名,密碼
    server.sendmail('wptawy@126.com', email_list, msg.as_string())
    server.quit()

11、郵箱驗證碼之過時時間

案例:

html:

<a id="fetch_code" class="fetch-code" href="javascript:void(0);">獲取驗證碼</a>

js:

 function BindSendMsg(){
            $("#fetch_code").click(function(){
                $('#register_error_summary').empty();  #清空錯誤信息
                var email = $('#email').val(); #獲取郵箱地址
                if(email.trim().length == 0){  #判斷是否輸入郵箱
                    $('#register_error_summary').text('請輸入註冊郵箱');
                    return;
                }
                if($(this).hasClass('sending')){  #判斷是否已經發送
                    return; 
                }
                var ths = $(this);
                var time = 60; 設置倒計時時間爲60s

                $.ajax({
                    url: "/send_msg", 
                    type: 'POST',
                    data: {email: email},
                    dataType: 'json',
                    success: function(arg){
                        if(!arg.status){  #是否發送成功
                            $('#register_error_summary').text(arg.summary); #不成功顯示錯誤信息
                        }else{
                            ths.addClass('sending');      #成功後顯示已發送狀態
                            var interval = setInterval(function(){
                            ths.text("已發送(" + time + ")");
                            time -= 1;    #定時器每運行一次,計數器減1
                            if(time <= 0){
                                clearInterval(interval); #一分鐘過完,清除定時器
                                ths.removeClass('sending');# 移除已發送狀態
                                ths.text("獲取驗證碼");# 恢復未發送狀態
                            }
                        }, 1000);#定時器每隔1s運行一次
                        }
                    }
                });

            });
        }
附:一些常見模塊:
1.隨機驗證碼獲取:
def random_code():
    code = ''
    for i in range(4):
        current = random.randrange(0,4)
        if current != i:
            temp = chr(random.randint(65,90))
        else:
            temp = random.randint(0,9)
        code += str(temp)
    return code
隨機驗證碼

2.md5加密

def generate_md5(value):
    r = str(time.time())
    obj = hashlib.md5(r.encode('utf-8'))
    obj.update(value.encode('utf-8'))
    return obj.hexdigest()
md5加密

12、分頁功能,該功能是基於tornado的自定義分頁功能

案例:

前端:

  <div class="pagination">
           {% raw str_page%}  #展現原生html
  </div>

url配置:

(r"/index/(?P<page>\d*)", IndexHandler),

分頁模塊:

#!/usr/bin/env python
# -*- coding:utf-8 -*-


class Pagination:
    def __init__(self, current_page, all_item):
        try:
            page = int(current_page)
        except:
            page = 1
        if page < 1:
            page = 1

        all_pager, c = divmod(all_item, 10)
        if c > 0:
            all_pager += 1

        self.current_page = page
        self.all_pager = all_pager

    @property
    def start(self):
        return (self.current_page - 1) * 10

    @property
    def end(self):
        return self.current_page * 10

    def string_pager(self, base_url="/index/"):
        list_page = []
        if self.all_pager < 11:
            s = 1
            t = self.all_pager + 1
        else:  # 總頁數大於11
            if self.current_page < 6:
                s = 1
                t = 12
            else:
                if (self.current_page + 5) < self.all_pager:
                    s = self.current_page - 5
                    t = self.current_page + 5 + 1
                else:
                    s = self.all_pager - 11
                    t = self.all_pager + 1
        # 首頁
        # first = '<a href="%s1">首頁</a>' % base_url
        # list_page.append(first)
        # 上一頁
        # 當前頁 page
        if self.current_page == 1:
            prev = '<a href="javascript:void(0);">上一頁</a>'
        else:
            prev = '<a href="%s%s">上一頁</a>' % (base_url, self.current_page - 1,)
        list_page.append(prev)

        for p in range(s, t):  # 1-11
            if p == self.current_page:
                temp = '<a class="active" href="%s%s">%s</a>' % (base_url,p, p)
            else:
                temp = '<a href="%s%s">%s</a>' % (base_url,p, p)
            list_page.append(temp)
        if self.current_page == self.all_pager:
            nex = '<a href="javascript:void(0);">下一頁</a>'
        else:
            nex = '<a href="%s%s">下一頁</a>' % (base_url, self.current_page + 1,)

        list_page.append(nex)

        # 尾頁
        last = '<a href="%s%s">尾頁</a>' % (base_url, self.all_pager,)
        list_page.append(last)

        # 跳轉
        jump = """<input type='text' /><a onclick="Jump('%s',this);">GO</a>""" % ('/index/', )
        script = """<script>
            function Jump(baseUrl,ths){
                var val = ths.previousElementSibling.value;
                if(val.trim().length>0){
                    location.href = baseUrl + val;
                }
            }
            </script>"""
        list_page.append(jump)
        list_page.append(script)
        str_page = "".join(list_page)
        return str_page
分頁模塊

注:Pagination實例化接收兩個參數:當前頁current_page、新聞總數all_item,其中current_page通常經過url分組元素直接獲取

 後臺handler:

class IndexHandler(BaseRequestHandler):
    def get(self, page=1):
        conn = ORM.session()  #獲取數據庫session對象
        all_count = conn.query(ORM.News).count()#計算新聞總數
        obj = Pagination(page, all_count)  #實例化pagination對象
        current_user_id = self.session['user_info']['nid'] if self.session['is_login'] else 0  #若是登錄獲取用戶id,不然,用戶id=0,下面的查詢結果也爲空
        result = conn.query(ORM.News.nid,
                            ORM.News.title,
                            ORM.News.url,
                            ORM.News.content,
                            ORM.News.ctime,
                            ORM.UserInfo.username,
                            ORM.NewsType.caption,
                            ORM.News.favor_count,
                            ORM.News.comment_count,
                            ORM.Favor.nid.label('has_favor')).join(ORM.NewsType, isouter=True).join(ORM.UserInfo, isouter=True).join(ORM.Favor, and_(ORM.Favor.user_info_id == current_user_id, ORM.News.nid == ORM.Favor.news_id), isouter=True)[obj.start:10] #從每頁開始向下取10條,即每頁顯示10條新聞
        conn.close()
        str_page = obj.string_pager('/index/') 獲取頁碼的字符串格式html
        self.render('home/index.html', str_page=str_page, news_list=result)

十3、頁面登錄驗證(裝飾器方式實現)

1.普通登錄驗證

def auth_login_redirect(func):

    def inner(self, *args, **kwargs):
        if not self.session['is_login']:
            self.redirect(config.LOGIN_URL)
            return
        func(self, *args, **kwargs)
    return inner

2.ajax提交數據的登錄驗證

def auth_login_json(func):

    def inner(self, *args, **kwargs):
        if not self.session['is_login']:
            rep = BaseResponse()
            rep.summary = "auth failed"
            self.write(json.dumps(rep.__dict__))
            return
        func(self, *args, **kwargs)
    return inner

十4、上傳文件

前端:

<form style="display: inline-block" id="upload_img_form" name="form" action="/upload_image" method="POST"  enctype="multipart/form-data" >
  <a id="fakeFile" class="fake-file">
    <span>上傳圖片</span>
    <input type="file" name="img" onchange="UploadImage(this);"/>
    <input type="text" name="url" class="hide" />
  </a>
  <iframe id='upload_img_iframe' name='upload_img_iframe' src=""  class="hide"></iframe>
</form>

 js:

 function UploadImage(ths){
            document.getElementById('upload_img_iframe').onload = UploadImageComplete;  #頁面加載完成後執行UploadImageComplete函數
            document.getElementById('upload_img_form').target = 'upload_img_iframe';  設置form提交到iframe
            document.getElementById('upload_img_form').submit(); #提交到iframe
        }

        /*
        上傳圖片以後回掉函數
        */
        function UploadImageComplete(){
            var origin = $("#upload_img_iframe").contents().find("body").text();#獲取圖片數據
            var obj = JSON.parse(origin); #轉換成JavaScript對象
            if(obj.status){  #若是上傳成功
                var img = document.createElement('img'); #建立img標籤
                img.src = obj.data; 圖片地址
                img.style.width = "200px";
                img.style.height = "180px";
                $("#upload_img_form").append(img);添加圖片
                $('#fakeFile').addClass('hide');
                $('#reUploadImage').removeClass('hide');
                $('#fakeFile').find('input[type="text"]').val(obj.data);#保存圖片地址到隱藏的input標籤中
            }else{
                alert(obj.summary);#不然顯示錯誤信息
            }
        }

路由配置:

(r"/upload_image", home.UploadImageHandler),

後臺handler:

class UploadImageHandler(BaseRequestHandler):
    @decrator.auth_login_json  #上傳前登錄驗證
    def post(self, *args, **kwargs):
        rep = BaseResponse() 前端迴應類
        try:
            file_metas = self.request.files["img"] 獲取圖片列表
            for meta in file_metas:
                file_name = meta['filename'] #圖片名
                file_path = os.path.join('statics', 'upload', commons.generate_md5(file_name)) #保存地址
                with open(file_path, 'wb') as up:
                    up.write(meta['body']) #在服務器寫入圖片
            rep.status = True #寫入成功
            rep.data = file_path 
        except Exception as ex:
            rep.summary = str(ex)#錯誤信息
        self.write(json.dumps(rep.__dict__)) #反饋給前端

十5、文章發佈

 

1.定義須要驗證的form類

class IndexForm(BaseForm):

    def __init__(self):
        self.title = StringField()  #標題
        self.content = StringField(required=False) 內容
        self.url = StringField(required=False)  圖片url
        self.news_type_id = IntegerField()   新聞類型

        super(IndexForm, self).__init__()

2.前端html:

<div class="f4">
  <a class="submit right" id="submit_img">提交</a>
  <span class="error-msg right"></span>
</div>

3.js

function BindPublishSubmit(){
            $('#submit_link,#submit_text,#submit_img').click(function(){ // 獲取輸入內容並提交 var container = $(this).parent().parent(); var post_dict = {}; container.find('input[type="text"],textarea').each(function(){ post_dict[$(this).attr('name')] =$(this).val(); }); post_dict['news_type_id'] = container.find('.news-type .active').attr('value'); $.ajax({ url: '/index', type: 'POST', data: post_dict, dataType: 'json', success: function (arg) { if(arg.status){ window.location.href = '/index'; }else{ console.log(arg); } } }) }); }

後臺handler:

@decrator.auth_login_json  #發佈前登錄驗證
    def post(self, *args, **kwargs):
        rep = BaseResponse()

        form = IndexForm()#實例化Indexform
        if form.valid(self):
            # title,content,href,news_type,user_info_id

#寫入數據庫 input_dict = copy.deepcopy(form._value_dict) input_dict['ctime'] = datetime.datetime.now() input_dict['user_info_id'] = self.session['user_info']['nid'] conn = ORM.session() conn.add(ORM.News(**input_dict)) conn.commit() conn.close() rep.status = True #寫入成功 else: rep.message = form._error_dict #錯誤信息 self.write(json.dumps(rep.__dict__))

十6、點贊功能

前端html:

<a href="javascript:void(0);" class="digg-a" title="推薦" onclick="DoFavor(this,{{item[0]}});">
  {% if item[9] %}                       #是否已點過贊
    <span class="hand-icon icon-digg active"></span>
  {% else %}
    <span class="hand-icon icon-digg"></span>
  {% end %}
   <b id="favor_count_{{item[0]}}">{{item[7]}}</b>    #點贊數量
</a>

js:

function DoFavor(ths, nid) {

            if($('#action_nav').attr('is-login') == 'true'){           #登錄狀態才能點贊
                $.ajax({
                    url: '/favor',
                    type: 'POST',
                    data: {news_id: nid},   #攜帶當前新聞id
                    dataType: 'json',
                    success: function(arg){
                        if(arg.status){
                            var $favorCount = $('#favor_count_'+nid);
                            var c = parseInt($favorCount.text());#獲取當前點贊數量
                            if(arg.code == 2301){      #當前用戶之前沒點過贊
                                $favorCount.text(c + 1);  #點贊數量加1
                                $(ths).find('span').addClass('active'); 已經點過贊變深顏色
                                AddFavorAnimation(ths); #+1動態效果
                            }else if(arg.code == 2302){ #該用戶之前對該新聞點過贊
                                $favorCount.text(c - 1); #點贊數量減1
                                $(ths).find('span').removeClass('active'); 取消點贊顏色變淺
                                MinusFavorAnimation(ths); #-1動態效果
                            }else{

                            }

                        }else{

                        }
                    }
                })
            }else{
                $('#accountDialog').removeClass('hide');
                $('.shadow').removeClass('hide');
            }
        } 

後臺handler:

class FavorHandler(BaseRequestHandler):

    @decrator.auth_login_json #點贊前登錄驗證
    def post(self, *args, **kwargs):
        rep = BaseResponse()

        news_id = self.get_argument('news_id', None) #獲取新聞id
        if not news_id:
            rep.summary = "新聞ID不能爲空."
        else:
            user_info_id = self.session['user_info']['nid']  #獲取當前用戶id
            conn = ORM.session()
      #查詢當前用戶是否對該新聞點過贊 has_favor = conn.query(ORM.Favor).filter(ORM.Favor.user_info_id == user_info_id, ORM.Favor.news_id == news_id).count() if has_favor:   #若是已經點過贊,刪除數據庫點贊表favor點贊數據,更新數據庫新聞表news點贊數量-1 conn.query(ORM.Favor).filter(ORM.Favor.user_info_id == user_info_id, ORM.Favor.news_id == news_id).delete() conn.query(ORM.News).filter(ORM.News.nid == news_id).update( {"favor_count": ORM.News.favor_count - 1}, synchronize_session="evaluate") rep.code = StatusCodeEnum.FavorMinus    #返回已經點過讚的狀態嗎 else: conn.add(ORM.Favor(user_info_id=user_info_id, news_id=news_id, ctime=datetime.datetime.now())) conn.query(ORM.News).filter(ORM.News.nid == news_id).update( {"favor_count": ORM.News.favor_count + 1}, synchronize_session="evaluate") rep.code = StatusCodeEnum.FavorPlus conn.commit() conn.close() rep.status = True   #操做成功 self.write(json.dumps(rep.__dict__))  #返回給前端

  點贊+1和-1動態效果js:

/*
        點贊+1效果
         */
        function AddFavorAnimation(ths){
            var offsetTop = -10;
            var offsetLeft = 20;
            var fontSize = 24;
            var opacity = 1;
            var tag = document.createElement('i');
            tag.innerText = "+1";
            tag.style.position = 'absolute';
            tag.style.top = offsetTop + 'px';
            tag.style.left = offsetLeft + 'px';
            tag.style.fontSize = fontSize + "px";
            tag.style.color = "#5cb85c";
            $(ths).append(tag);

            var addInterval = setInterval(function(){
                    fontSize += 5;
                    offsetTop -= 15;
                    offsetLeft += 5;
                    opacity -= 0.1;
                    tag.style.top = offsetTop+ 'px';
                    tag.style.left = offsetLeft+ 'px';
                    tag.style.fontSize = fontSize + 'px';
                    tag.style.opacity = opacity;
                if(opacity <= 0.5){
                    tag.remove();
                    clearInterval(addInterval);
                }
            },40)
        }
點贊+1效果
 /*
        點贊-1效果
         */
        function MinusFavorAnimation(ths){
            var offsetTop = -10;
            var offsetLeft = 20;
            var fontSize = 24;
            var opacity = 1;
            var tag = document.createElement('i');
            tag.innerText = "-1";
            tag.style.position = 'absolute';
            tag.style.top = offsetTop + 'px';
            tag.style.left = offsetLeft + 'px';
            tag.style.fontSize = fontSize + "px";
            tag.style.color = "#787878";
            $(ths).append(tag);

            var addInterval = setInterval(function(){
                    fontSize += 5;
                    offsetTop -= 15;
                    offsetLeft += 5 ;
                    opacity -= 0.1;
                    tag.style.top = offsetTop+ 'px';
                    tag.style.left = offsetLeft+ 'px';
                    tag.style.fontSize = fontSize + 'px';
                    tag.style.opacity = opacity;
                if(opacity <= 0.5){
                    tag.remove();
                    clearInterval(addInterval);
                }
            },40)
        }
點贊-1效果

十7、評論功能

案例:

 前端html:

 <div class="box-r">
  <a href="javascript:void(0);" class="pub-icons add-pub-btn add-pub-btn-unvalid" onclick="DoComment({{item[0]}})">評論</a>  #攜帶新聞id
  <a href="javascript:void(0);" class="loading-ico loading-ico-top pub-loading-top hide">發佈中...</a>
</div> 
{% raw tree(comment_tree) %}

建立評論樹字典函數:

def build_tree(comment_list):

    comment_dic = collections.OrderedDict()

    for comment_obj in comment_list:
        if comment_obj[2] is None:
            # 若是是根評論,添加到comment_dic[評論對象] = {}
            comment_dic[comment_obj] = collections.OrderedDict()
        else:
            # 若是是回覆的評論,則須要在 comment_dic 中找到其回覆的評論
            tree_search(comment_dic, comment_obj)
    return comment_dic
build_tree

遞歸生成評論樹函數:

def tree_search(d_dic, comment_obj):
    # 在comment_dic中一個一個的尋找其回覆的評論
    # 檢查當前評論的 reply_id 和 comment_dic中已有評論的nid是否相同,
    #   若是相同,表示就是回覆的此信息
    #   若是不一樣,則須要去 comment_dic 的全部子元素中尋找,一直找,若是一系列中未找,則繼續向下找
    for k, v_dic in d_dic.items():
        # 找回復的評論,將本身添加到其對應的字典中,例如: {評論一: {回覆一:{},回覆二:{}}}
        if k[0] == comment_obj[2]:
            d_dic[k][comment_obj] = collections.OrderedDict()
            return
        else:
            # 在當前第一個跟元素中遞歸的去尋找父親
            tree_search(d_dic[k], comment_obj)
tree_search

前端調用uimethod,生成評論html:

TEMP1 = """
<li class="items" style='padding:8px 0 0 %spx;'>
    <span class="folder" id='comment_folder_%s'>
        <div class="comment-L comment-L-top">
            <a href="#" class="icons zhan-ico"></a>
            <a href="/user/moyujian/submitted/1">
                <img src="/statics/images/1.jpg">
            </a>
        </div>
        <div class="comment-R comment-R-top" style="background-color: rgb(246, 246, 246);">
            <div class="pp">
                <a class="name" href="/user/moyujian/submitted/1">%s</a>
                <span class="p3">%s</span>
                <span class="into-time into-time-top">%s</span>
            </div>
            <div class="comment-line-top">
                <div class="comment-state">
                    <a class="ding" href="javascript:void(0);">
                        <b>頂</b>
                        <span class="ding-num">[0]</span>
                    </a>
                    <a class="cai" href="javascript:void(0);">
                        <b>踩</b>
                        <span class="cai-num">[0]</span>
                    </a>
                    <span class="line-huifu">|</span>
                    <a class="see-a jubao" href="javascript:void(0);">舉報</a>
                    <span class="line-huifu">|</span>
                    <a class="see-a huifu-a" href="javascript:void(0);" onclick="reply(%s,%s,'%s')"  id='comment_reply_%s' >回覆</a>
                </div>
            </div>
        </div>
    </span>

"""
def tree(self, comment_dic):
    html = ''
    for k, v in comment_dic.items():
        html += TEMP1 %(0,k[0], k[3],k[1],k[4],k[7],k[0], k[3],k[0])
        html += generate_comment_html(v, 16)
        html += "</li>"

    return html

def generate_comment_html(sub_comment_dic, margin_left_val):
    # html = '<ul style="background: url(&quot;/statics/images/pinglun_line.gif&quot;) 0px -10px no-repeat scroll transparent;margin-left:3px;">'
    html = '<ul>'
    for k, v_dic in sub_comment_dic.items():
        html += TEMP1 %(margin_left_val,k[0], k[3],k[1],k[4],k[7],k[0], k[3],k[0])
        if v_dic:
            html += generate_comment_html(v_dic, margin_left_val)
        html += "</li>"
    html += "</ul>"
    return html
uimethod

後臺handler:

class CommentHandler(BaseRequestHandler):
    def get(self, *args, **kwargs): #展現評論信息 # comment_list須要按照時間從小到大排列 nid = self.get_argument('nid', 0) conn = ORM.session() comment_list = conn.query( ORM.Comment.nid, ORM.Comment.content, ORM.Comment.reply_id, ORM.UserInfo.username, ORM.Comment.ctime, ORM.Comment.up, ORM.Comment.down, ORM.Comment.news_id ).join(ORM.UserInfo, isouter=True).filter(ORM.Comment.news_id == nid).all() conn.close() """ comment_list = [ (1, '111',None), #評論id,評論內容,回覆id,若是是None,則表明回覆新聞 (2, '222',None), (3, '33',None), (9, '999',5), (4, '444',2), (5, '555',1), (6, '666',4), (7, '777',2), (8, '888',4), ] """ comment_tree = commons.build_tree(comment_list) #最終傳遞給前端的是一個字典 self.render('include/comment.html', comment_tree=comment_tree) @decrator.auth_login_json #提交評論前作登錄驗證 def post(self, *args, **kwargs): #提交評論信息 rep = BaseResponse() #前端反饋類 form = CommentForm() #評論的form驗證 if form.valid(self):  #若是驗證成功 form._value_dict['ctime'] = datetime.datetime.now() #獲取當前時間 conn = ORM.session() #獲取數據庫session對象
       # 將評論寫入數據庫 obj = ORM.Comment(user_info_id=self.session['user_info']['nid'], news_id=form._value_dict['news_id'], reply_id=form._value_dict['reply_id'], content=form._value_dict['content'], up=0, down=0, ctime=datetime.datetime.now()) conn.add(obj) conn.flush() conn.refresh(obj)  #同時獲取評論的自增id rep.data = { 'user_info_id': self.session['user_info']['nid'], 'username': self.session['user_info']['username'], 'nid': obj.nid, 'news_id': obj.news_id, 'ctime': obj.ctime.strftime("%Y-%m-%d %H:%M:%S"), 'reply_id': obj.reply_id, 'content': obj.content, }       #更新數據庫的評論數量 conn.query(ORM.News).filter(ORM.News.nid == form._value_dict['news_id']).update( {"comment_count": ORM.News.comment_count + 1}, synchronize_session="evaluate") conn.commit() conn.close() rep.status = True  #評論成功的狀態信息 else: rep.message = form._error_dict  #錯誤信息 print(rep.__dict__) self.write(json.dumps(rep.__dict__)) #反饋給前端

js:

function DoComment(nid){
            var content = $("#comment_content_"+nid).val();
            var reply_id = $("#reply_id_"+nid).attr('target');

            if($('#action_nav').attr('is-login') == 'true'){  #已經登陸才發ajax請求
                $.ajax({
                    url: '/comment',
                    type: 'POST',
                    data: {content: content, reply_id:reply_id, news_id: nid},#發送評論內容,回覆的評論id,新聞id
                    dataType: 'json',
                    success: function(arg){
                        // 獲取評論信息,將內容添加到指定位置
                        console.log(arg);
                        if(arg.status){
                            $('#comment_content_' + arg.data.news_id).val('');
                            var a = '<ul><li class="items" style="padding:8px 0 0 16px;"><span class="folder" id="comment_folder_';
                            var b = arg.data.nid;
                            var c = '"><div class="comment-L comment-L-top"><a href="#" class="icons zhan-ico"></a><a href="/user/moyujian/submitted/1"><img src="/statics/images/1.jpg"></a></div><div class="comment-R comment-R-top" style=""><div class="pp"><a class="name" href="/user/moyujian/submitted/1">';
                            var d = arg.data.username;
                            var e = '</a><span class="p3">';
                            var f = arg.data.content;
                            var g= '</span><span class="into-time into-time-top">';
                            var h = arg.data.ctime;
                            var i = '</span></div><div class="comment-line-top"><div class="comment-state"><a class="ding" href="javascript:void(0);"><b>頂</b><span class="ding-num">[0]</span></a><a class="cai" href="javascript:void(0);"><b>踩</b><span class="cai-num">[0]</span></a><span class="line-huifu">|</span> <a class="see-a jubao" href="javascript:void(0);">舉報</a> <span class="line-huifu">|</span> <a class="see-a huifu-a" href="javascript:void(0);" onclick="';
                            var j = "reply(" + arg.data.news_id + "," +arg.data.nid+",'"+arg.data.username+"')";
                            var k = '">回覆</a></div></div></div></span></li></ul>';
                            var tag = a+b+c+d+e+f+g+h+i+j+k;
                            console.log(arg,tag);
                            if(arg.data.reply_id){
                                $comment_folder = $('#comment_folder_' + arg.data.reply_id);
                                $comment_folder.after(tag);
                            }else{
                                $('#comment_list_'+arg.data.news_id).append(tag);
                            }


                        }else{
                            alert('error');
                        }
                    }
                })
            }else{
                $('#accountDialog').removeClass('hide');
                $('.shadow').removeClass('hide');
            }
        }

   總結:本博客是基於tornado實現的較完整版web開發項目,聚集了form驗證、點贊、評論等高級功能,歡迎你們參考並提出相關問題,本人會在看到的第一時間回覆,最後,若是您以爲本文對您有參考價值,歡迎推薦,謝謝!

相關文章
相關標籤/搜索