GVKun编程网logo

Django 系列博客(十一)(django blog)

7

对于想了解Django系列博客的读者,本文将是一篇不可错过的文章,我们将详细介绍十一,并且为您提供关于Anaconda+django写出第一个webapp(十一)、DjangoRESTframewor

对于想了解Django 系列博客的读者,本文将是一篇不可错过的文章,我们将详细介绍十一,并且为您提供关于Anaconda+django写出第一个web app(十一)、Django REST framework+Vue 打造生鲜超市(十一)、Django 框架(二十一)—— Django rest_framework - 权限组件、Django 系列博客(一)的有价值信息。

本文目录一览:

Django 系列博客(十一)(django blog)

Django 系列博客(十一)(django blog)

<h1 id="django-系列博客十一">Django 系列博客(十一)
<h2 id="前言">前言

本篇博客介绍使用 ORM 来进行多表的操作,当然重点在查询方面。

实例:

出版商模型:出版商有名称,所在城市。

书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。

from django.db import models

Create your models here.

class Book(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5,decimal_places=2)
publish = models.ForeignKey(to='Publish',to_field='id',on_delete=models.CASCADE)
pub_date = models.DateField()
authors = models.ManyToManyField(to='Author')

class Author(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField(default=18)

class Publish(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)

class AuthorDetail(models.Model):
id = models.AutoField(primary_key=True)
addr = models.CharField(max_length=32)
author = models.OnetoOneField(to=Author,to_field='id')


<h3 id="表关联关系如下">表关联关系如下

注意:

  1. 表的名称为 app名字_modelName,是根据模型中的元数据自动生成的,也可以重写为别的名称;
  2. id 字段如果没有定义,会自动添加;
  3. 对于外键字段,Django 会自动在字段名前面加上‘’_id‘’来创建数据库中的列名;
  4. 这个例子中的 CREATE TABLE sql语句使用 Postgresql 语法格式,要注意的是 Django 会根据 settings 中指定的数据库类型来使用相应的 sql 语句;
  5. 定义好模型后,需要告诉 Django 使用这些模型,你要做的是修改配置文件中的 INSTALL_APPSZ 中设置,在其中添加 models.py所在应用的名称;
  6. 外键字段 ForeignKey 有一个 null=True的设置(允许外键接受空值 NULL),你可以赋给它空值 None。

添加表记录

方式一:

publish_obj=Publish.objects.get(nid=1) book_obj=Book.objects.create(title="",publishDate="2012-12-12",price=100,publish=publish_obj)

方式二:

book_obj=Book.objects.create(title="",publish_id=1)

注意:

关键点:
一 book_obj.publish=Publish.objects.filter(id=book_obj.publish_id).f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t()

二 book_obj.authors.all()
   关键点:book.authors.all()  # 与这本书关联的作者集合

    1 book.id=3
    2 book_authors
        id  book_id  author_ID
        3      3             1
        4      3             2

    3  author
       id   name
       1   musibii
       2   thales

book_obj.authors.all()    ------->   [musibii,thales]</code></pre>

<pre># -----一对多添加
pub=Publish.objects.create(name='北京出版社',email='445676@qq.com',city='北京')
print(pub)

# 为book表绑定和publish的关系
import datetime,time
<a href="https://www.jb51.cc/tag/Now/" target="_blank">Now</a>=datetime.datetime.<a href="https://www.jb51.cc/tag/Now/" target="_blank">Now</a>().__str__()
<a href="https://www.jb51.cc/tag/Now/" target="_blank">Now</a> = datetime.datetime.<a href="https://www.jb51.cc/tag/Now/" target="_blank">Now</a>().st<a href="https://www.jb51.cc/tag/rft/" target="_blank">rft</a>ime('%Y-%m-%d')
print(type(<a href="https://www.jb51.cc/tag/Now/" target="_blank">Now</a>))
print(<a href="https://www.jb51.cc/tag/Now/" target="_blank">Now</a>)
# 日期类型必须是日期对象或者字符串形式的2018-09-12(2018-9-12),其它形式不行
Book.objects.create(name='海燕3',price=333.123,publish_date=<a href="https://www.jb51.cc/tag/Now/" target="_blank">Now</a>,publish_id=2)
Book.objects.create(name='海3燕3',price=35.123,publish_date='2018/02/28',publish=pub)
pub=Publish.objects.filter(nid=1).f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t()
book=Book.objects.create(name='测试书籍',price=33,publish_date='2018-7-28',publish=pub)
print(book.publish.name)
# <a href="https://www.jb51.cc/tag/chaxun/" target="_blank">查询</a>出版了红楼梦这本书出版社的邮箱
book=Book.objects.filter(name='红楼梦').f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t()
print(book.publish.email)</code></pre>

<h3 id="多对多">多对多


<pre># 当前生成的书籍对象
book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1)

为书籍绑定的做作者对象

yuan=Author.objects.filter(name="shao").f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t() # 在Author表中主键为2的纪录
egon=Author.objects.filter(name="zuan").f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t() # 在Author表中主键为1的纪录

# 绑定多对多关系,即向关系表book_authors中<a href="https://www.jb51.cc/tag/tianjia/" target="_blank">添加</a>纪录
book_obj.authors.add(shao,zuan)    #  将某些特定的 model 对象<a href="https://www.jb51.cc/tag/tianjia/" target="_blank">添加</a>到被关联对象集合中。   =======    book_obj.authors.add(*[])</code></pre>

<pre>book = Book.objects.filter(name='红楼梦').first()
musibii=Author.objects.filter(name='musibii').first()
eureka=Author.objects.filter(name='eureka').first()

1 没有返回值,直接传对象

book.authors.add(musibii,eureka)
# 2 直接传作者id
book.authors.add(1,3)
# 3 直接传列表,会打散
book.authors.add(*[1,2])
# 解除多对多关系
book = Book.objects.filter(name='红楼梦').f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t()
# 1 传作者id
book.authors.remove(1)
# 2 传作者对象
egon = Author.objects.filter(name='musibii').f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t()
book.authors.remove(musibii)
#3 传*列表
book.authors.remove(*[1,2])
#4 <a href="https://www.jb51.cc/tag/shanchu/" target="_blank">删除</a>所有
book.authors.clear()
# 5 拿到与 这本书关联的所有作者,结果是queryset对象,作者列表
ret=book.authors.all()
# print(ret)
# 6 queryset对象,又可以继续点(<a href="https://www.jb51.cc/tag/chaxun/" target="_blank">查询</a>红楼梦这本书所有作者的名字)
ret=book.authors.all().values('name')
print(ret)
# 以上总结:
# (1)
# book=Book.objects.filter(name='红楼梦').f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t()
# print(book)
# 在点publish的时候,其实就是拿着publish_id又去app01_publish这个表里查数据了
# print(book.publish)
# (2)book.authors.all()</code></pre>

<h3 id="多对多关系常用-api">多对多关系常用 API
<pre>book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除。 ====== book_obj.authors.remove(*[])
book_obj.authors.clear() #清空被关联对象集合
book_obj.authors.set() #先清空再设置 

<h2 id="基于对象的跨表查询">基于对象的跨表查询
<h3 id="一对多查询">一对多查询

正向查询按字段

# 查询主键为1的书籍的出版社所在的城市 book_obj=Book.objects.filter(pk=1).first() # book_obj.publish 是主键为1的书籍对象关联的出版社对象 print(book_obj.publish.city)

反向查询按表名小写_set

publish=Publish.objects.get(name="苹果出版社") #publish.book_set.all() : 与苹果出版社关联的所有书籍对象集合 book_list=publish.book_set.all() for book_obj in book_list: print(book_obj.title)

例子:

# 一对多正向查询 book=Book.objects.filter(name='红楼梦').first() print(book.publish)#与这本书关联的出版社对象 print(book.publish.name) # 一对多反向查询 # 人民出版社出版过的书籍名称 pub=Publish.objects.filter(name='人民出版社').first() ret=pub.book_set.all() print(ret)

查询

正向查询按字段

egon=Author.objects.filter(name="egon").first() print(egon.authorDetail.telephone)

反向查询按表名

# 查询所有住址在北京的作者的姓名

authorDetail_list=AuthorDetail.objects.filter(addr="beijing")
for obj in authorDetail_list:
print(obj.author.name)

例子:

# 一对一正向查询 # lqz的手机号 musibii=Author.objects.filter(name='musibii').first() tel=musibii.author_detail.telephone print(tel) # 一对一反向查询 # 地址在北京的作者姓名 author_detail=AuthorDatail.objects.filter(addr='北京').first() name=author_detail.author.name print(name)

查询

正向查询按字段

# 所有作者的名字以及手机号

book_obj=Book.objects.filter(title="眉").first()
authors=book_obj.authors.all()
for author_obj in authors:
print(author_obj.name,author_obj.authorDetail.telephone)

反向查询按表名_set

# 查询musibii出过的所有书籍的名字

author_obj=Author.objects.get(name="musibii")
book_list=author_obj.book_set.all() #与musibii作者相关的所有书籍
for book_obj in book_list:
print(book_obj.title)

例子:

# 正向查询----查询红楼梦所有作者名称 book=Book.objects.filter(name='红楼梦').first() ret=book.authors.all() print(ret) for auth in ret: print(auth.name) # 反向查询 查询lqz这个作者写的所有书 author=Author.objects.filter(name='lqz').first() ret=author.book_set.all() print(ret)

注意:可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改:

publish = ForeignKey(Book,related_name='bookList')

之后使用:

# 查询 人民出版社出版过的所有书籍

publish=Publish.objects.get(name="人民出版社")
book_list=publish.bookList.all() # 与人民出版社关联的所有书籍对象集合


<h2 id="基于双下划线的跨表查询">基于双下划线的跨表查询

Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 sql JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model 为止。

正向查询按字段,反向查询按表名小姐用来告诉 ORM 引擎 join 哪张表

查询

# 查询苹果出版社出版过的所有书籍的名字与价格(一对多)

正向查询 按字段:publish

queryResult=Book.objects
        .filter(publish__name="苹果出版社")
        .values_list("title","price")

反向查询 按表名:book

queryResult=Publish.objects
          .filter(name="苹果出版社")
          .values_list("booktitle","bookprice")
查询的本质一样,就是select from的表不一样


<pre># 正向查询按字段,反向查询按表名小写

查询红楼梦这本书出版社的名字

select * from app01_book inner join app01_publish

on app01_book.publish_id=app01_publish.nid

ret=Book.objects.filter(name='红楼梦').values('publishname')
print(ret)
ret=Publish.objects.filter(book
name='红楼梦').values('name')
print(ret)

<h3 id="多对多查询-1">多对多查询
<pre># 查询musibii出过的所有书籍的名字(多对多)

正向查询 按字段:authors:

queryResult=Book.objects
            .filter(authors__name="yuan")
            .values_list("title")

反向查询 按表名:book

queryResult=Author.objects
              .filter(name="yuan")
              .values_list("booktitle","bookprice")

例子:

# 正向查询按字段,反向查询按表名小写 # 查询红楼梦这本书出版社的名字 # select * from app01_book inner join app01_publish # on app01_book.publish_id=app01_publish.nid ret=Book.objects.filter(name='红楼梦').values('publish__name') print(ret) ret=Publish.objects.filter(book__name='红楼梦').values('name') print(ret) # sql 语句就是from的表不一样 # -------多对多正向查询 # 查询红楼梦所有的作者 ret=Book.objects.filter(name='红楼梦').values('authors__name') print(ret) # ---多对多反向查询 ret=Author.objects.filter(book__name='红楼梦').values('name') ret=Author.objects.filter(book__name='红楼梦').values('name','author_detail__addr') print(ret)

查询

# 正向查询 ret = Author.objects.filter(name="musibii").values("authordetail__telephone")

反向查询

ret = AuthorDetail.objects.filter(author__name="musibii").values("telephone")

例子:

# 查询musibii的手机号 # 正向查 ret=Author.objects.filter(name='musibii').values('author_detail__telephone') print(ret) # 反向查 ret= AuthorDatail.objects.filter(author__name='musibii').values('telephone') print(ret)

例子:

# 查询eureka的手机号 # 查询人民出版社出版过的所有书籍的名字以及作者的姓名

正向查询

queryResult=Book.objects
            .filter(publish__name="人民出版社")
            .values_list("title","authors__name")

反向查询

queryResult=Publish.objects
              .filter(name="人民出版社")
              .values_list("booktitle","bookauthorsage","bookauthors__name")

练习: 手机号以151开头的作者出版过的所有书籍名称以及出版社名称

方式1:

queryResult=Book.objects
            .filter(authorsauthorDetailtelephone__regex="151")
            .values_list("title","publish__name")

方式2:

ret=Author.objects
.filter(authordetailtelephonestartswith="151")
.values("booktitle","bookpublish__name")


<pre># ----进阶练习,连续跨表

查询手机号以33开头的作者出版过的书籍名称以及书籍出版社名称

author_datail author book publish

基于authorDatail表

ret=AuthorDatail.objects.filter(telephonestartswith='33').values('authorbookname','authorbookpublishname')
print(ret)

基于Author表

ret=Author.objects.filter(author_detailtelephonestartswith=33).values('bookname','bookpublish__name')
print(ret)

基于Book表

ret=Book.objects.filter(authors__author_detailtelephonestartswith='33').values('name','publish__name')
print(ret)

基于Publish表

ret=Publish.objects.filter(bookauthorsauthor_detailtelephonestartswith='33').values('book__name','name')
print(ret)

<h3 id="related_name">related_name


<pre>publish = ForeignKey(Blog,related_name='bookList')

例子:

查询人民出版社出版过的所有书籍的名字与价格(一对多)

反向查询 不再按表名:book,而是related_name:bookList

queryResult = Publish.objects
              .filter(name="人民出版社")
              .values_list("bookListtitle","bookListprice")

Anaconda+django写出第一个web app(十一)

Anaconda+django写出第一个web app(十一)

今天我们来学习给页面添加一个Sidebar,根据Sidebar跳转到相应的tutorial。

打开views.py,编辑single_slug函数:

def single_slug(request, single_slug):
    categories = [c.category_slug for c in TutorialCategory.objects.all()]
    if single_slug in categories:
        matching_series = TutorialSeries.objects.filter(tutorial_category__category_slug=single_slug)
        series_urls = {}

        for m in matching_series.all():
            part_one = Tutorial.objects.filter(tutorial_series__tutorial_series=m.tutorial_series).earliest(''tutorial_published'')
            series_urls[m] = part_one.tutorial_slug

        return render(request=request,
            template_name=''main/category.html'',
            context={''tutorial_series'':matching_series, ''part_ones'': series_urls})

    tutorials = [t.tutorial_slug for t in Tutorial.objects.all()]
    if single_slug in tutorials:
        this_tutorial = Tutorial.objects.get(tutorial_slug=single_slug)
        tutorials_from_series = Tutorial.objects.filter(tutorial_series__tutorial_series=this_tutorial.tutorial_series).order_by(''tutorial_published'')
        this_tutorial_idx = list(tutorials_from_series).index(this_tutorial)
        return render(request=request,
            template_name=''main/tutorial.html'',
            context={''tutorial'':this_tutorial, ''sidebar'':tutorials_from_series, ''this_tut_idx'':this_tutorial_idx})

接下来我们需要编写tutorial.html,来显示相应的内容和Sidebar,Sidebar的写法参考materialize:

{% extends ''main/header.html'' %}

{% block content %}
<div class="row">
    <div class="col s12, m8, l8">
    <h3>{{tutorial.tutorial_title}}</h3>
    <p style="font-size: 70%">Published {{tutorial.tutorial_published}}</p>
    {{tutorial.tutorial_content|safe}}
  </div>

  <div class="col s12 m4 l4">
    <ul class="collapsible popout">
        {% for tutorial in sidebar %}
            {% if forloop.counter0 == this_tut_idx %}
                <li class="active">
                    <div class="collapsible-header" style="background-color: #008E72; color: white;">{{tutorial.tutorial_title}}<br>(currently viewing)</div>
                </li>
            {% else %}
                <li>
                    <div class="collapsible-header">{{tutorial.tutorial_title}}</div>
                    <div class="collapsible-body">
                        <p><a href="/{{tutorial.tutorial_slug}}"><button class="btn waves-effect waves-light right-align" style="background-color:green-light; color:white">Go</button></a></p>
                    </div>
                </li>
            {% endif %}
        {% endfor %}
    </ul>
  </div>
</div>

{% endblock %}

我上传了几个Ted视频,效果显示如下,这时点击Sidebar没有反应,我们还需要在header.html中初始化javascript,在最后一行写上:

 <script>M.AutoInit();</script> ,Sidebar即可正常使用:

 点击想要跳转的tutorial,点击go,即可跳转。

到此为止,我们的第一个app已经建成了,这个web app可以显示目录的类别、系列以及查看每个目录的内容。如果我们想要增加更多的功能,也非常的方便,接下来还需要学习html、数据库、python的知识,来一步步提高,多查阅帮助文档和看看喜欢的网站,然后想想如何实现,亲自动手操作一遍。

目前为止使用的都是python默认的数据库sqlite3,下一节继续对这个网站学习如何把sqlite数据库转化为mysql数据库。

参考链接:

[1] https://materializecss.com/collapsible.html

[2] https://pythonprogramming.net/dynamic-sidebar-django-tutorial/

Django REST framework+Vue 打造生鲜超市(十一)

Django REST framework+Vue 打造生鲜超市(十一)

目录

生鲜超市(一)    生鲜超市(二)    生鲜超市(三)   

生鲜超市(四)    生鲜超市(五)    生鲜超市(六)   

生鲜超市(七)    生鲜超市(八)    生鲜超市(九)   

生鲜超市(十)    生鲜超市(十一)    生鲜超市(十二)    生鲜超市(十三)   

代码下载

github

教程

学习自慕课网-前端vue结合后端DjangoFramework的在线生鲜超市 

十二、支付宝沙箱环境配置

12.1.创建应用

进入蚂蚁金服开放平台(https://open.alipay.com/platform/home.htm),登录后进入管理中心-->>应用列表

 创建应用

 

 创建应用后会有一个appid。还需要提交信息进行审核。微信支付和支付宝支付都是要求企业认证才可以完成的,个人开发不可以,所以我们需要用

沙箱环境,它可以让我们不具备这些应用或者说应用审核还没通过的时候先开发调试

 

12.2.沙箱环境

沙箱应用地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info

 

(1)公钥和私钥的生成方法

地址:https://docs.open.alipay.com/291/105971/

选winwods,如果linux就选linux

下载好工具照着生成就可以了

 

 

 (2) 把生成的公钥和私钥拷贝到trade/keys下面--->>>重命名--->>首位各添加下面的内容

-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----

 

 

 

(3)把支付宝公钥也拷贝到这路径下面,同样首尾添加

 

 

 

12.3.文档说明

我们主要用到电脑网站支付,文档地址:https://docs.open.alipay.com/270

用到的API接口:统一收单下单并支付页面接口

里面有文档说明

 

几个比较重要的参数

 

 

 sign签名

 应该看未使用开放平台的SDK里面的说明:https://docs.open.alipay.com/291/106118

 

 请求参数

必填的几个:

 

 

12.4.编写代码

把环境改成本地的

首先的安装一个模块

pip install pycryptodome

utils中新建alipay.py,代码如下:

# apps/utils.py

import json
from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64encode, b64decode
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from urllib.request import urlopen
from base64 import decodebytes, encodebytes


class AliPay(object):
    """
    支付宝支付接口
    """
    def __init__(self, appid, app_notify_url, app_private_key_path,
                 alipay_public_key_path, return_url, debug=False):
        self.appid = appid
        self.app_notify_url = app_notify_url
        #私钥
        self.app_private_key_path = app_private_key_path
        self.app_private_key = None
        self.return_url = return_url
        with open(self.app_private_key_path) as fp:
            self.app_private_key = RSA.importKey(fp.read())
        #公钥
        self.alipay_public_key_path = alipay_public_key_path
        with open(self.alipay_public_key_path) as fp:
            self.alipay_public_key = RSA.import_key(fp.read())


        if debug is True:
            self.__gateway = "https://openapi.alipaydev.com/gateway.do"
        else:
            self.__gateway = "https://openapi.alipay.com/gateway.do"

    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        #请求参数
        biz_content = {
            "subject": subject,
            "out_trade_no": out_trade_no,
            "total_amount": total_amount,
            "product_code": "FAST_INSTANT_TRADE_PAY",
            # "qr_pay_mode":4
        }
        #允许传递更多参数,放到biz_content
        biz_content.update(kwargs)
        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
        return self.sign_data(data)

    def build_body(self, method, biz_content, return_url=None):
        #build_body主要生产消息的格式
        #公共请求参数
        data = {
            "app_id": self.appid,
            "method": method,
            "charset": "utf-8",
            "sign_type": "RSA2",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "biz_content": biz_content
        }

        if return_url is not None:
            data["notify_url"] = self.app_notify_url
            data["return_url"] = self.return_url

        return data

    def sign_data(self, data):
        #签名
        data.pop("sign", None)
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        #排完序后拼接起来
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
        #这里得到签名的字符串
        sign = self.sign(unsigned_string.encode("utf-8"))
        #对url进行处理
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)

        # 获得最终的订单信息字符串
        signed_string = quoted_string + "&sign=" + quote_plus(sign)
        return signed_string

    #参数传进来一定要排序
    def ordered_data(self, data):
        complex_keys = []
        for key, value in data.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来
        for key in complex_keys:
            data[key] = json.dumps(data[key], separators=('','', '':''))

        return sorted([(k, v) for k, v in data.items()])

    def sign(self, unsigned_string):
        # 开始计算签名
        key = self.app_private_key
        #签名的对象
        signer = PKCS1_v1_5.new(key)
        #生成签名
        signature = signer.sign(SHA256.new(unsigned_string))
        # base64 编码,转换为unicode表示并移除回车
        sign = encodebytes(signature).decode("utf8").replace("\n", "")
        return sign

    def _verify(self, raw_content, signature):
        # 开始计算签名
        key = self.alipay_public_key
        signer = PKCS1_v1_5.new(key)
        digest = SHA256.new()
        digest.update(raw_content.encode("utf8"))
        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
            return True
        return False

    def verify(self, data, signature):
        if "sign_type" in data:
            sign_type = data.pop("sign_type")
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
        return self._verify(message, signature)


if __name__ == "__main__":
    return_url = ''http://127.0.0.1:8000/?total_amount=100.00&timestamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2FvhdDYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdpVQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&method=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0''
    o = urlparse(return_url)
    query = parse_qs(o.query)
    processed_query = {}
    ali_sign = query.pop("sign")[0]


# 测试用例
    alipay = AliPay(
        # 沙箱里面的appid值
        appid="2016091500517456",
        #notify_url是异步的url
        app_notify_url="http://127.0.0.1:8000/",
        # 我们自己商户的密钥
        app_private_key_path="../trade/keys/private_2048.txt",
        # 支付宝的公钥
        alipay_public_key_path="../trade/keys/alipay_key_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        # debug为true时使用沙箱的url。如果不是用正式环境的url
        debug=True,  # 默认False,
        return_url="http://127.0.0.1:8000/alipay/return/"
    )

    for key, value in query.items():
        processed_query[key] = value[0]
    # print (alipay.verify(processed_query, ali_sign))

    # 直接支付:生成请求的字符串。
    url = alipay.direct_pay(
        # 订单标题
        subject="测试订单derek",
        # 我们商户自行生成的订单号
        out_trade_no="20180417derek",
        # 订单金额
        total_amount=100,
        #成功付款后跳转到的页面,return_url同步的url
        # return_url="http://127.0.0.1:8000/"
    )
    # 将生成的请求字符串拿到我们的url中进行拼接
    re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)

    print(re_url)

 

运行alipay.py,会生成一个支付链接url,点进去跳到支付页面

 

 点“登录账户付款”,用沙箱账号付款

 

 输入账号密码以及支付密码,成功付款

 

 12.5.django集成支付宝notify_url和return_url

(1)配置url

# 配置支付宝支付相关接口的url
path(''alipay/return/'', AlipayView.as_view())

(2)alipay.py

把return_url和notify_url都改成远程服务器的地址

return_url="http://47.93.198.159:8000/alipay/return/"

app_notify_url="http://47.93.198.159:8000/alipay/return/"
# apps/utils.py

import json
from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64encode, b64decode
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from urllib.request import urlopen
from base64 import decodebytes, encodebytes


class AliPay(object):
    """
    支付宝支付接口
    """
    def __init__(self, appid, app_notify_url, app_private_key_path,
                 alipay_public_key_path, return_url, debug=False):
        self.appid = appid
        self.app_notify_url = app_notify_url
        #私钥
        self.app_private_key_path = app_private_key_path
        self.app_private_key = None
        self.return_url = return_url
        with open(self.app_private_key_path) as fp:
            self.app_private_key = RSA.importKey(fp.read())
        #公钥
        self.alipay_public_key_path = alipay_public_key_path
        with open(self.alipay_public_key_path) as fp:
            self.alipay_public_key = RSA.import_key(fp.read())


        if debug is True:
            self.__gateway = "https://openapi.alipaydev.com/gateway.do"
        else:
            self.__gateway = "https://openapi.alipay.com/gateway.do"

    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        #请求参数
        biz_content = {
            "subject": subject,
            "out_trade_no": out_trade_no,
            "total_amount": total_amount,
            "product_code": "FAST_INSTANT_TRADE_PAY",
            # "qr_pay_mode":4
        }
        #允许传递更多参数,放到biz_content
        biz_content.update(kwargs)
        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
        return self.sign_data(data)

    def build_body(self, method, biz_content, return_url=None):
        #build_body主要生产消息的格式
        #公共请求参数
        data = {
            "app_id": self.appid,
            "method": method,
            "charset": "utf-8",
            "sign_type": "RSA2",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "biz_content": biz_content
        }

        if return_url is not None:
            data["notify_url"] = self.app_notify_url
            data["return_url"] = self.return_url

        return data

    def sign_data(self, data):
        #签名
        data.pop("sign", None)
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        #排完序后拼接起来
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
        #这里得到签名的字符串
        sign = self.sign(unsigned_string.encode("utf-8"))
        #对url进行处理
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)

        # 获得最终的订单信息字符串
        signed_string = quoted_string + "&sign=" + quote_plus(sign)
        return signed_string

    #参数传进来一定要排序
    def ordered_data(self, data):
        complex_keys = []
        for key, value in data.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来
        for key in complex_keys:
            data[key] = json.dumps(data[key], separators=('','', '':''))

        return sorted([(k, v) for k, v in data.items()])

    def sign(self, unsigned_string):
        # 开始计算签名
        key = self.app_private_key
        #签名的对象
        signer = PKCS1_v1_5.new(key)
        #生成签名
        signature = signer.sign(SHA256.new(unsigned_string))
        # base64 编码,转换为unicode表示并移除回车
        sign = encodebytes(signature).decode("utf8").replace("\n", "")
        return sign

    def _verify(self, raw_content, signature):
        # 开始计算签名
        key = self.alipay_public_key
        signer = PKCS1_v1_5.new(key)
        digest = SHA256.new()
        digest.update(raw_content.encode("utf8"))
        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
            return True
        return False

    def verify(self, data, signature):
        if "sign_type" in data:
            sign_type = data.pop("sign_type")
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
        return self._verify(message, signature)


if __name__ == "__main__":
    return_url = ''http://127.0.0.1:8000/?total_amount=100.00&timestamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2FvhdDYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdpVQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&method=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0''
    o = urlparse(return_url)
    query = parse_qs(o.query)
    processed_query = {}
    ali_sign = query.pop("sign")[0]


# 测试用例
    alipay = AliPay(
        # 沙箱里面的appid值
        appid="2016091500517456",
        #notify_url是异步的url
        app_notify_url="http://47.93.198.159:8000/alipay/return/",
        # 我们自己商户的密钥
        app_private_key_path="../trade/keys/private_2048.txt",
        # 支付宝的公钥
        alipay_public_key_path="../trade/keys/alipay_key_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        # debug为true时使用沙箱的url。如果不是用正式环境的url
        debug=True,  # 默认False,
        return_url="http://47.93.198.159:8000/alipay/return/"
    )

    for key, value in query.items():
        processed_query[key] = value[0]
    # print (alipay.verify(processed_query, ali_sign))

    # 直接支付:生成请求的字符串。
    url = alipay.direct_pay(
        # 订单标题
        subject="测试订单derek",
        # 我们商户自行生成的订单号
        out_trade_no="20180417derek2",
        # 订单金额
        total_amount=100,
        #成功付款后跳转到的页面,return_url同步的url
        return_url="http://47.93.198.159:8000/alipay/return/"
    )
    # 将生成的请求字符串拿到我们的url中进行拼接
    re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)

    print(re_url)
alipay.py

(3)settings.py

 settings中配置公钥私钥路径

# 支付宝相关的key
private_key_path = os.path.join(BASE_DIR, ''apps/trade/keys/private_2048.txt'')
ali_pub_key_path = os.path.join(BASE_DIR, ''apps/trade/keys/alipay_key_2048.txt'')

(4)trade/views.py

from datetime import datetime
from utils.alipay import AliPay
from rest_framework.views import APIView
from MxShop.settings import ali_pub_key_path, private_key_path
from rest_framework.response import Response

class AlipayView(APIView):
    def get(self, request):
        """
        处理支付宝的return_url返回
        """
        processed_dict = {}
        # 1. 获取GET中参数
        for key, value in request.GET.items():
            processed_dict[key] = value
        # 2. 取出sign
        sign = processed_dict.pop("sign", None)

        # 3. 生成ALipay对象
        alipay = AliPay(
            appid="2016091500517456",
            app_notify_url="http://47.93.198.159:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://47.93.198.159:8000/alipay/return/"
        )

        verify_re = alipay.verify(processed_dict, sign)

        # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。
        if verify_re is True:
            order_sn = processed_dict.get(''out_trade_no'', None)
            trade_no = processed_dict.get(''trade_no'', None)
            trade_status = processed_dict.get(''trade_status'', None)

            existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
            for existed_order in existed_orders:
                existed_order.pay_status = trade_status
                existed_order.trade_no = trade_no
                existed_order.pay_time = datetime.now()
                existed_order.save()


    def post(self, request):
        """
        处理支付宝的notify_url
        """
        #存放post里面所有的数据
        processed_dict = {}
        #取出post里面的数据
        for key, value in request.POST.items():
            processed_dict[key] = value
        #把signpop掉,文档有说明
        sign = processed_dict.pop("sign", None)

        #生成一个Alipay对象
        alipay = AliPay(
            appid="2016091500517456",
            app_notify_url="http://47.93.198.159:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://47.93.198.159:8000/alipay/return/"
        )

        #进行验证
        verify_re = alipay.verify(processed_dict, sign)

        # 如果验签成功
        if verify_re is True:
            #商户网站唯一订单号
            order_sn = processed_dict.get(''out_trade_no'', None)
            #支付宝系统交易流水号
            trade_no = processed_dict.get(''trade_no'', None)
            #交易状态
            trade_status = processed_dict.get(''trade_status'', None)

            # 查询数据库中订单记录
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
            for existed_order in existed_orders:
                # 订单商品项
                order_goods = existed_order.goods.all()
                # 商品销量增加订单中数值
                for order_good in order_goods:
                    goods = order_good.goods
                    goods.sold_num += order_good.goods_num
                    goods.save()

                # 更新订单状态
                existed_order.pay_status = trade_status
                existed_order.trade_no = trade_no
                existed_order.pay_time = datetime.now()
                existed_order.save()
            #需要返回一个''success''给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息
            return Response("success")

 

(5)trade/serializers.py

创建订单的时候生成一个支付的url,这个逻辑OderSerializer和OrderDetailSerializer中都添加

#支付订单的url
    alipay_url = serializers.SerializerMethodField(read_only=True)

    def get_alipay_url(self, obj):
        alipay = AliPay(
            appid="2016091500517456",
            app_notify_url="http://47.93.198.159:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://47.93.198.159:8000/alipay/return/"
        )

        url = alipay.direct_pay(
            subject=obj.order_sn,
            out_trade_no=obj.order_sn,
            total_amount=obj.order_mount,
        )
        re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)

        return re_url
# trade/serializer.py
__author__ = ''derek''

import time

from .models import ShoppingCart
from rest_framework import serializers
from goods.models import Goods
from goods.serializers import GoodsSerializer
from .models import OrderInfo,OrderGoods
from MxShop.settings import ali_pub_key_path, private_key_path
from utils.alipay import AliPay

class ShopCartDetailSerializer(serializers.ModelSerializer):
    ''''''
    购物车商品详情信息
    ''''''
    # 一个购物车对应一个商品
    goods = GoodsSerializer(many=False, read_only=True)
    class Meta:
        model = ShoppingCart
        fields = ("goods", "nums")


class ShopCartSerializer(serializers.Serializer):
    #获取当前登录的用户
    user = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )
    nums = serializers.IntegerField(required=True, label="数量",min_value=1,
                                    error_messages={
                                        "min_value":"商品数量不能小于一",
                                        "required": "请选择购买数量"
                                    })
    #这里是继承Serializer,必须指定queryset对象,如果继承ModelSerializer则不需要指定
    #goods是一个外键,可以通过这方法获取goods object中所有的值
    goods = serializers.PrimaryKeyRelatedField(required=True, queryset=Goods.objects.all())

    #继承的Serializer没有save功能,必须写一个create方法
    def create(self, validated_data):
        # validated_data是已经处理过的数据
        #获取当前用户
        # view中:self.request.user;serizlizer中:self.context["request"].user
        user = self.context["request"].user
        nums = validated_data["nums"]
        goods = validated_data["goods"]

        existed = ShoppingCart.objects.filter(user=user, goods=goods)
        #如果购物车中有记录,数量+1
        #如果购物车车没有记录,就创建
        if existed:
            existed = existed[0]
            existed.nums += nums
            existed.save()
        else:
            #添加到购物车
            existed = ShoppingCart.objects.create(**validated_data)

        return existed

    def update(self, instance, validated_data):
        # 修改商品数量
        instance.nums = validated_data["nums"]
        instance.save()
        return instance


#订单中的商品
class OrderGoodsSerialzier(serializers.ModelSerializer):
    goods = GoodsSerializer(many=False)
    class Meta:
        model = OrderGoods
        fields = "__all__"

#订单商品信息
# goods字段需要嵌套一个OrderGoodsSerializer
class OrderDetailSerializer(serializers.ModelSerializer):
    goods = OrderGoodsSerialzier(many=True)
    # 支付订单的url
    alipay_url = serializers.SerializerMethodField(read_only=True)

    def get_alipay_url(self, obj):
        alipay = AliPay(
            appid="2016091500517456",
            app_notify_url="http://47.93.198.159:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://47.93.198.159:8000/alipay/return/"
        )

        url = alipay.direct_pay(
            subject=obj.order_sn,
            out_trade_no=obj.order_sn,
            total_amount=obj.order_mount,
        )
        re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)

        return re_url
    class Meta:
        model = OrderInfo
        fields = "__all__"

class OrderSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )
    #生成订单的时候这些不用post
    pay_status = serializers.CharField(read_only=True)
    trade_no = serializers.CharField(read_only=True)
    order_sn = serializers.CharField(read_only=True)
    pay_time = serializers.DateTimeField(read_only=True)
    nonce_str = serializers.CharField(read_only=True)
    pay_type = serializers.CharField(read_only=True)
    #支付订单的url
    alipay_url = serializers.SerializerMethodField(read_only=True)

    def get_alipay_url(self, obj):
        alipay = AliPay(
            appid="2016091500517456",
            app_notify_url="http://47.93.198.159:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://47.93.198.159:8000/alipay/return/"
        )

        url = alipay.direct_pay(
            subject=obj.order_sn,
            out_trade_no=obj.order_sn,
            total_amount=obj.order_mount,
        )
        re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)

        return re_url


    def generate_order_sn(self):
        #生成订单号
        # 当前时间+userid+随机数
        from random import Random
        random_ins = Random()
        order_sn = "{time_str}{userid}{ranstr}".format(time_str=time.strftime("%Y%m%d%H%M%S"),
                                                       userid=self.context["request"].user.id,
                                                       ranstr=random_ins.randint(10, 99))
        return order_sn

    def validate(self, attrs):
        #validate中添加order_sn,然后在view中就可以save
        attrs["order_sn"] = self.generate_order_sn()
        return attrs

    class Meta:
        model = OrderInfo
        fields = "__all__"
trade/serializer.py

 

(6)测试代码

把本地修改的地方一定要上传到服务器,因为我们需要在服务器上调试代码

vue项目中api.js里面local_host改为服务器ip

let local_host = ''http://47.93.198.159:8000'';

在phcharm中开始运行项目

浏览器访问地址:http://47.93.198.159:8000/orders/

创建一个订单

生成订单的详情

 

12.6.vue静态文件放到django中

vue有两种开发模式

  • build    用来生成静态文件 
  • dev

(1)运行

cnpm run build

生成的静态文件在dist目录下面

 

(2)把index.html拷贝到templates目录下

(3)django中创建static目录

  • 把index.entry.js考到django的static目录下面
  • 把dist/static下的两个文件夹拷贝到django static目录下

 

(4)settings设置静态文件路径

STATIC_URL = ''/static/''
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
)

 (5)修改index.html中静态文件路径

<script type="text/javascript" src="/static/index.entry.js"></script></body>

(6)配置index的url

from django.views.generic import TemplateView

urlpatterns = [
    # 首页
    path(''index/'', TemplateView.as_view(template_name=''index.html''),name=''index'')
]

 (7)配置支付成功return的地址

trade/views.py

response = redirect("/index/#/app/home/member/order")
            return response

        else:
            response = redirect("index")
            return response
# trade/views.py

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from utils.permissions import IsOwnerOrReadOnly
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.authentication import SessionAuthentication
from .serializers import ShopCartSerializer,ShopCartDetailSerializer,OrderDetailSerializer,OrderGoodsSerialzier,OrderSerializer
from .models import ShoppingCart,OrderGoods,OrderInfo
from rest_framework import mixins
from django.shortcuts import render, redirect


class ShoppingCartViewset(viewsets.ModelViewSet):
    """
    购物车功能
    list:
        获取购物车详情
    create:
        加入购物车
    delete:
        删除购物记录
    """
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
    serializer_class = ShopCartSerializer
    #商品的id
    lookup_field = "goods_id"

    def get_serializer_class(self):
        if self.action == ''list'':
            return ShopCartDetailSerializer
        else:
            return ShopCartSerializer

    #获取购物车列表
    def get_queryset(self):
        return ShoppingCart.objects.filter(user=self.request.user)


class OrderViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin,
                   viewsets.GenericViewSet):
    """
    订单管理
    list:
        获取个人订单
    delete:
        删除订单
    create:
        新增订单
    """
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
    serializer_class = OrderSerializer
    #动态配置serializer
    def get_serializer_class(self):
        if self.action == "retrieve":
            return OrderDetailSerializer
        return OrderSerializer
    #获取订单列表
    def get_queryset(self):
        return OrderInfo.objects.filter(user=self.request.user)

    #在订单提交保存之前还需要多两步步骤,所以这里自定义perform_create方法
    #1.将购物车中的商品保存到OrderGoods中
    #2.情况购物车
    def perform_create(self, serializer):
        order = serializer.save()
        # 获取购物车所有商品
        shop_carts = ShoppingCart.objects.filter(user=self.request.user)
        for shop_cart in shop_carts:
            order_goods = OrderGoods()
            order_goods.goods = shop_cart.goods
            order_goods.goods_num = shop_cart.nums
            order_goods.order = order
            order_goods.save()
            #清空购物车
            shop_cart.delete()
        return order

from datetime import datetime
from utils.alipay import AliPay
from rest_framework.views import APIView
from MxShop.settings import ali_pub_key_path, private_key_path
from rest_framework.response import Response

class AlipayView(APIView):
    def get(self, request):
        """
        处理支付宝的return_url返回
        """
        processed_dict = {}
        # 1. 获取GET中参数
        for key, value in request.GET.items():
            processed_dict[key] = value
        # 2. 取出sign
        sign = processed_dict.pop("sign", None)

        # 3. 生成ALipay对象
        alipay = AliPay(
            appid="2016091500517456",
            app_notify_url="http://47.93.198.159:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://47.93.198.159:8000/alipay/return/"
        )

        verify_re = alipay.verify(processed_dict, sign)

        # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。
        if verify_re is True:
            order_sn = processed_dict.get(''out_trade_no'', None)
            trade_no = processed_dict.get(''trade_no'', None)
            trade_status = processed_dict.get(''trade_status'', None)

            existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
            for existed_order in existed_orders:
                existed_order.pay_status = trade_status
                existed_order.trade_no = trade_no
                existed_order.pay_time = datetime.now()
                existed_order.save()

            response = redirect("/index/#/app/home/member/order")
            return response

        else:
            response = redirect("index")
            return response

    def post(self, request):
        """
        处理支付宝的notify_url
        """
        #存放post里面所有的数据
        processed_dict = {}
        #取出post里面的数据
        for key, value in request.POST.items():
            processed_dict[key] = value
        #把signpop掉,文档有说明
        sign = processed_dict.pop("sign", None)

        #生成一个Alipay对象
        alipay = AliPay(
            appid="2016091500517456",
            app_notify_url="http://47.93.198.159:8000/alipay/return/",
            app_private_key_path=private_key_path,
            alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
            debug=True,  # 默认False,
            return_url="http://47.93.198.159:8000/alipay/return/"
        )

        #进行验证
        verify_re = alipay.verify(processed_dict, sign)

        # 如果验签成功
        if verify_re is True:
            #商户网站唯一订单号
            order_sn = processed_dict.get(''out_trade_no'', None)
            #支付宝系统交易流水号
            trade_no = processed_dict.get(''trade_no'', None)
            #交易状态
            trade_status = processed_dict.get(''trade_status'', None)

            # 查询数据库中订单记录
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
            for existed_order in existed_orders:
                # 订单商品项
                order_goods = existed_order.goods.all()
                # 商品销量增加订单中数值
                for order_good in order_goods:
                    goods = order_good.goods
                    goods.sold_num += order_good.goods_num
                    goods.save()

                # 更新订单状态
                existed_order.pay_status = trade_status
                existed_order.trade_no = trade_no
                existed_order.pay_time = datetime.now()
                existed_order.save()
            #需要返回一个''success''给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息
            return Response("success")
# trade/views.py

 

现在可以通过index直接访问了:http://47.93.198.159:8000/index/#/app/home/index

添加商品到购物车-->>去结算-->>生成订单-->>跳到支付页面了

 

 
 
 

Django 框架(二十一)—— Django rest_framework - 权限组件

Django 框架(二十一)—— Django rest_framework - 权限组件

Django rest_framework - 权限组件

[TOC]

一、权限组件的使用

# 用户信息表
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    # 写choice
    user_choice=((0,''普通用户''),(1,''会员''),(2,''超级用户''))
    # 指定choice,可以快速的通过数字,取出文字
    user_type=models.IntegerField(choices=user_choice,default=0)
    pwd = models.CharField(max_length=32)

# 用户token
class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to=UserInfo)

1、使用语法

from rest_framework.permissions import BasePermission

# 写一个权限类
class UserPermission(BasePermission):
    # 重写没有权限时的数据
    message = ''您没有权限''
	# 重写has_permission()方法,传入三个参数
    # 第一个是对象自身(自动传);第二个是request对象;第三个是
    def has_permission(self, request, view):
        # 只要认证通过,就会把当前用户对象放到request中
        user_type = request.user.user_type
        # get_字段名_display() 可以获取choice数字对应的具体值
        # user_type_name = request.user.get_user_type_display()
        # print(user_type_name)
        if user_type == 2:
            return True
        return False
class Book(APIView):
    # 用户认证
    authentication_classes = [UserAuth.UserAuth, ]
    # 权限判断
    permission_classes = [MyPerm.UserPermission, ]

    def get(self, request, *args, **kwargs):
        response = {''status'': 100, ''msg'': ''查询成功''}
        ret = models.Book.objects.all()
        ser = MySerializer.BookSerializer(instance=ret, many=True)
        response[''data''] = ser.data
        return JsonResponse(response, safe=False)

2、全局使用、局部使用、局部禁用权限

(1)全局使用

  • 在 settings 文件中配置,配置完以后,就无需在视图类中写,已经是所有视图类都需要权限判断
  • 必须为 REST_FRAMEWORK,key 值必须为 DEFAULT_AUTHENTICATION_CLASSES
REST_FRAMEWORK={
    ''DEFAULT_PERMISSION_CLASSES'':[''app01.MyPerm.UserPermission'',],
}

(2)局部使用

在需要使用的视图类中写,只对当前视图类起认证作用,重新定义 permission_classes

class Book(APIView):
    # # 该方法是局部使用认证
    authentication_classes = [UserAuth.UserAuth, ]
    # 该方法是局部使用权限
    permission_classes = [MyPerm.UserPermission, ]

    def get(self, request, *args, **kwargs):
        response = {''status'': 100, ''msg'': ''查询成功''}
        ret = models.Book.objects.all()
        ser = MySerializer.BookSerializer(instance=ret, many=True)
        response[''data''] = ser.data
        return JsonResponse(response, safe=False)

(3)局部禁用

在配置过全局权限判断以后,有些视图类不需要判断权限,可以局部禁用权限证,只需将 permission_classes 定义为空列表即可。

class Book(APIView):
    # # 该方法是局部使用认证
    authentication_classes = [UserAuth.UserAuth, ]
    # 该方法是局部禁用权限
    permission_classes = []

    def get(self, request, *args, **kwargs):
        response = {''status'': 100, ''msg'': ''查询成功''}
        ret = models.Book.objects.all()
        ser = MySerializer.BookSerializer(instance=ret, many=True)
        response[''data''] = ser.data
        return JsonResponse(response, safe=False)

二、源码分析

as_view ----------> view -------------> dispatch -------> Request 包装新的 request ------> 认证、权限、频率 --------> 根据请求方式分发到不同的方法

url(r''books/'',views.Book.as_view())

1、Book 中没有 as_view

2、APIView 的 as_view

class APIView(View):
    
	@classmethod
    # cls 是 Book类
	def as_view(cls, **initkwargs):
            
		# view = super(APIView, Book).as_view(**initkwargs)
        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)

3、view = super (APIView, cls).as_view (**initkwargs) ---------------------> View 中的 as_view

class View(object):
    
	@classonlymethod
    # cls====> Book
    def as_view(cls, **initkwargs):

        def view(request, *args, **kwargs):
            # 实例化产生一个book对象
            self = cls(**initkwargs)
            if hasattr(self, ''get'') and not hasattr(self, ''head''):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            # 调dispatch方法
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

4、return self.dispatch(request, *args, **kwargs) ----------------> dispatch

self====> Book 对象,一层层找 dispatch

APIView 中找到 dispatch

class APIView(View):
    
	def dispatch(self, request, *args, **kwargs):

        self.args = args
        self.kwargs = kwargs
        
        # (a)初始化request,就是通过Request类来包装原生request,得到包装后的request
        request = self.initialize_request(request, *args, **kwargs)
        # 从现在开始request就是包装后的request
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            # (b) 认证、权限、频率
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            # http_method_names表示列表[''get'', ''post'', ''put'', ''patch'', ''delete'', ''head'', ''options'', ''trace'']
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    
    

(a)request = self.initialize_request (request, *args, **kwargs) 包装 request

self 是 Book 对象

class APIView(View):
    # 默认的认证列表类
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
	def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)
		# (a-b)实例化初始化产生新的request对象
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),  # 认证类实例化产生的对象的列表
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]
(a------1)return Request (・・・) ----------> Request 类初始化
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            ''The `request` argument must be an instance of ''
            ''`django.http.HttpRequest`, not `{}.{}`.''
            .format(request.__class__.__module__, request.__class__.__name__)
        )

        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context[''request''] = self
        self.parser_context[''encoding''] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, ''_force_auth_user'', None)
        force_token = getattr(request, ''_force_auth_token'', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

(b)self.initial (request, *args, **kwargs) -----> 认证、权限、频率

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        # (b------1) 认证
        self.perform_authentication(request)
        # (b------2)权限
        self.check_permissions(request)
        # 频率
        self.check_throttles(request)
(b------1) self.check_permissions (request) -------> 权限判断
    def check_permissions(self, request):
        """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
        # (b------1---------1) get_permissions 权限类对象组成的列表
        for permission in self.get_permissions():
            # 重写的就是这个has_permission()方法,判断当前用户是否有权限
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, ''message'', None)
                )
(b------1---------1) self.get_permissions () -------> 获取权限类对象组成的列表
def get_permissions(self):
    """
    Instantiates and returns the list of permissions that this view requires.
    """
    return [permission() for permission in self.permission_classes]

Django 系列博客(一)

Django 系列博客(一)

Django 系列博客(一)

前言

学习了 python 这么久,终于到了Django 框架。这可以说是 python 名气最大的web 框架了,那么从今天开始会开始从 Django框架的安装到使用一步步的学习,这系列博客不会像前端的那样水了(立个 flag),希望可以成为高质量的博客。那么本篇博客介绍 Django 的安装以及如何在电脑上运行第一个 Django 应用。

Django 的安装

Django 的安装很简单,在 win 和 mac 上面都可以使用 pip 安装命令安装,也可以通过 pycharm 安装,或者下载文件在命令行使用安装工具安装。

接下来我在 ubuntu 上安装做示例。

在这里还安装了一个依赖包 pytz。这是用来做时区转换的一个第三方库。

其他平台的 pip 安装方式一样,不过要选用 python3的对应 pip 来安装,因为现在的 Django 版本已经不支持 python2了。

虚拟环境的安装

什么是虚拟环境

  • 对真实的 python 解释器的一个拷贝版本
  • 事实有效,可以独立存在并运行解释 python 代码
  • 可以在计算机上拷贝多个虚拟环境

为什么要使用虚拟环境

  • 保证真实环境的纯净性
  • 框架的多版本共存
  • 方便做框架的版本迭代
  • 降低多框架共存的维护成本

安装虚拟环境

  1. 通过 pip 安装虚拟环境库

因为我之前已经下载好了,所以这里直接显示请求已经完成,并且后面是安装的绝对路径。

  1. 前往目标文件夹

这个文件夹是你用来保存虚拟环境的文件夹,该文件夹一旦确定就不要轻易更改。

image-20190103154126721

这个 py3-env1是创建的一个纯净虚拟环境。

  1. 创建纯净的虚拟环境
virtualenv 虚拟环境名 (py3-env2)
  1. 终端启动虚拟环境
cd py3-env1\Scripts
activate
  1. 进入虚拟环境下的 python 开发环境
python3
  1. 关闭虚拟环境
deactivate
  1. Pycharm的开发配置
添加:创建项目 -> Project Interpreter -> Existing interpreter -> Virtualenv Environment | System Interpreter -> 目标路径下的 python.exe
删除:Setting -> Project -> Project Interpreter -> Show All

mac 电脑从第三步直接到最后一步就好

了解

# 创建非纯净环境:
#	-- virtualenv-clone 本地环境 虚拟环境名
# Mac配置终端,在终端运行虚拟环境
# 在用户根目录下的.bash_profile(没有需手动创建)文件中设置
# alias 终端指令名(env-py3)=''/Library/Virtualenv/虚拟环境名/bin/python3''
# alias 终端指令名(env-pip3)=''/Library/Virtualenv/虚拟环境名/bin/pip3''

HTTP 协议

因为 Django 框架应用层是采用的 HTTP 协议,所以有必要了解 HTTP 协议。

什么是 HTTP 协议

  • HTTP(HyperText Transport Protocol) 是超文本传输协议,而 HTTPS 也归属于 HTTP 协议,S 代表安全。
  • 基于 TCP/IP协议基础上的应用层协议,底层实现仍为 socket
  • 基于请求-响应模式:通信一定是从客户端开始,服务端接收到客户端一定会做出对应响应
  • 无状态:协议不对任何一次通信状态和任何数据做保存
  • 无连接:一次连接只完成一次请求-响应,请求-响应完毕后会立即断开连接。

HTTP 工作原理

一次 HTTP 连接称之为一个事务,过程可以分为四步

  1. 客户端与服务端建立连接
  2. 客户端发生一个 HTTP 协议指定格式的请求
  3. 服务端接收请求后,回应一个 HTTP 协议指定格式的响应
  4. 客户端将服务端的响应展现给用户

HTTP 状态码

  • 1开头:

  • 2开头:

  • 3开头:

  • 4开头:

  • 5开头:

请求报文

# 请求行  请求头  请求体
''''''
POST / HTTP/1.1\r\n
Host: 127.0.0.1:8001\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
\r\n
usr=abc&pwd=123
''''''

响应报文

# 响应行  响应头  响应体
''''''
HTTP/1.1 200 OK\r\n
Content-type:text/html\r\n
\r\n
Login Success
''''''

使用原生 socket 完成和浏览器的交互

目录结构

01_socket
	-- 01_client.html:前台通过form表单访问后台的页面
	-- 01_login.html:后台测试渲染给前台的登录页面
	-- 01_index.html:后台测试渲染给前台的主页
	-- 01_server.py:后台服务器文件

因为 B/S架构的客户端已经由浏览器写好,所以只需要关注服务器端就ok。

服务器端代码

from socket import socket

# 设置响应头(包含响应行)
RESP_HEADER = b''HTTP/1.1 200 OK\r\nContent-type:text/html;charset=utf-8\r\n\r\n'' # 连续两个\r\n表示响应头结束

# 设置服务器 socket 相关信息
server = socket()
server.bind('''', 8080) # 空字符串表示绑定本机
server.listen(5)
print((''服务:http://localhost:8080''))

while True:
    # 获取以 http 协议发来的请求
    client, addr = server.accept()
    data = client.recv(1024)
    # 数据报文 包含请求行 请求头 请求体
    print(data)
    client.send(RESP_HEADER)
    
    # /index => 响应主页
    # /login => 登录页面
    # 错误 => 404
    # 数据 data, 字节形式 => 字符串形式
    strData = str(data, encodeing)
    
    # 解析请求的数据,分析得到路由
    my_route = strData.split(''\r\n'')[0].split('' '')[1]
    
    # 后台没有设置的路由,统统由404来处理
    dt = b''404''
    
    # 设置的路由返回响应的页面文件
    if my_route == ''/index'':
        with open(''index 页面路径'', ''rb'') as f:
            dt = f.read()
    if my_route == ''/login'':
        with open(''login 页面路径'', ''rb'') as f:
            dt = f.read()
            
    # /favicon.ico该请求是往后台请求标签图标
    if my_route == ''/favicon.ico'':
        with open(''favicon.ico'', ''rb'') as f:
            dt = f.read()
    # 服务器发送响应体
    client.send(dt)
    # 一次循环,代表一次响应,也就是一次事务的完成,要关闭 http 请求连接
    client.close()

修改返回数据,完善响应体

# 字符串
client.send(b''HTTP/1.1 200 OK\r\n'')
client.send(b''\r\n'')
client.send(b''Normal Socket Web'')
# html代码,请求头要设置支持 html 代码
client.send(b''HTTP/1.1 200 OK\r\n'')
client.send(b''Content-type:text/html\r\n'')
client.send(b''\r\n'')
client.send(b''<h1>Normal Socket Web</h1>'')
# html文件(同级目录建立一个index.html页面)
client.send(b''HTTP/1.1 200 OK\r\n'')
client.send(b''Content-type:text/html\r\n'')
client.send(b''\r\n'')
# 利用文件方式读取页面
with open(''01_index.html'', ''rb'') as f:
    dt = f.read()
client.send(dt)

拓展

修改接收数据,模拟后台路由

# 分析接收到的数据
data = client.recv(1024)
# 保证接收到的数据作为字符串进行以下处理
data = str(data, encoding=''utf-8'')
# 拆分出地址位
route = data.split(''\r\n'')[0].split('' '')[1]
# 匹配地址,做出不同的响应
if route == ''/index'':
    with open(''01_index.html'', ''rb'') as f:
    	dt = f.read()
elif route == ''/login'':
    with open(''01_login.html'', ''rb'') as f:
    	dt = f.read()
else:
    dt = b''404''
client.send(dt)

框架演变

目录结构

02_frame
	-- favicon.ico
	-- index.html
	-- manage.py

manage.py

import socket
import pymysql
# 响应头
RESP_HEADER = b''HTTP/1.1 200 OK\r\nContent-type:text/html\r\n\r\n''

# 请求处理
def index():
    # 以字节方式读取文件
    with open(''index.html'', ''rb'') as f:
        dt = f.read()
    return dt
def ico():
    with open(''favicon.ico'', ''rb'') as f:
        dt = f.read()
    return dt
def user():
    # 数据库操作
    conn = pymysql.connect(host=''127.0.0.1'', port=3306, db=''django'', user=''root'', password=''root'')
    cur = conn.cursor(pymysql.cursors.DictCursor)
    cur.execute(''select * from user'')
    users = cur.fetchall()
    print(users)
    users = ''''''%d:%s
    %d:%s'''''' % (users[0][''id''], users[0][''name''], users[1][''id''], users[1][''name''])
    return users.encode(''utf-8'')

# 设置路由
urls = {
    # 请求路径与请求处理函数一一对应
    ''/index'': index,
    ''/favicon.ico'': ico,
    ''/user'': user
}

# 设置socket
def serve(host, port):
    server = socket.socket()
    server.bind((host, port))
    print(''start:http://'' + host + '':'' + str(port))
    server.listen(5)
    while True:
        sock, addr = server.accept()
        data = sock.recv(1024)
        data = str(data, encoding=''utf-8'')
        print(data)
        route = data.split(''\r\n'')[0].split('' '')[1]

        resp = b''404''
        if route in urls:
            resp = urls[route]()

        sock.send(RESP_HEADER)
        sock.send(resp)
        sock.close()

# 启服务
if __name__ == ''__main__'':
    serve(''127.0.0.1'', 8002)

项目演变

目录结构

03_proj
	-- template
		-- index.html
		-- user.html
	favicon.ico
	start.py
	urls.py
	views.py

index.html

<h1>{{ name }}</h1>

user.html

<table border="1">
    <tr>
        <th>id</th>
        <th>name</th>
        <th>password</th>
    </tr>
    {% for user in users%}
    <tr>
        <td>{{user.id}}</td>
        <td>{{user.name}}</td>
        <td>{{user.password}}</td>
    </tr>
    {% endfor %}
</table>

start.py

from wsgiref.simple_server import make_server
from urls import urls


def app(env, response):
    print(env)
    # 设置响应头
    response("200 OK", [(''Content-type'', ''text/html'')])
    route = env[''PATH_INFO'']
    print(route)
    data = urls[''error'']()
    if route in urls:
        data = urls[route]()
    # 返回二进制响应体
    return [data]


if __name__ == ''__main__'':
    server = make_server(''127.0.0.1'', 8003, app)
    print(''start:http://127.0.0.1:8003'')
    server.serve_forever()

urls.py

from views import *
urls = {
    ''/index'': index,
    ''/favicon.ico'': ico,
    ''/user'': user,
    ''error'': error
}

views.py

import pymysql
# 利用jinja2来渲染模板,将后台数据传给前台
from jinja2 import Template

def index():
    with open(''templates/index.html'', ''r'') as f:
        dt = f.read()
    tem = Template(dt)
    resp = tem.render(name=''主页'')
    return resp.encode(''utf-8'')

def ico():
    with open(''favicon.ico'', ''rb'') as f:
        dt = f.read()
    return dt

def user():
    # 数据库操作
    conn = pymysql.connect(host=''127.0.0.1'', port=3306, db=''django'', user=''root'', password=''root'')
    cur = conn.cursor(pymysql.cursors.DictCursor)
    cur.execute(''select * from user'')
    users = cur.fetchall()
    print(users)

    with open(''templates/user.html'', ''r'') as f:
        dt = f.read()
    tem = Template(dt)
    resp = tem.render(users=users)

    return resp.encode(''utf-8'')

def error():
    return b''404''

关于Django 系列博客十一的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于Anaconda+django写出第一个web app(十一)、Django REST framework+Vue 打造生鲜超市(十一)、Django 框架(二十一)—— Django rest_framework - 权限组件、Django 系列博客(一)等相关内容,可以在本站寻找。

本文标签: