当前位置:   article > 正文

【实战】Django从零搭建个人网站_django实战

django实战

Django从零搭建个人网站


导览

以下内容您将了解如何使用Django快速搭建网站
在腾讯云购买域名备案到部署云服务器正式上线
顺便学习总结相关网页前端和数据库的基础操作


前言

本章为零基础学习Django快速获得学习反馈,
供本人学习复盘使用也不具任何学习观摩性。


一、环境介绍

系统版本:MacOS Monterey 12.4
芯片版本:Apple M1 Max
软件版本:Python 3.8
数据编辑:DB Browser for SQLite Version 3.12.2
编辑版本:PyCharm 2022.1.1
其他服务:CentOS 8.2 / 宝塔 / 阿里云 / 腾讯云


二、安装测试

1. 框架安装

打开terminal,输入如下代码:

# temrinal输入
python3.8 -m pip install Django
  • 1
  • 2

2. 查看版本

# temrinal输入
python3.8 -m django --version
  • 1
  • 2

3. 新建项目

# temrinal输入
django-admin startproject website
  • 1
  • 2

4. 新建App

# terminal输入
python3 manage.py startapp webapp
  • 1
  • 2

5. 运行项目

# terminal输入
python3.8 manage.py runserver
  • 1
  • 2

6. 测试项目

# 浏览器输入
http://127.0.0.1:8000/
  • 1
  • 2

7. 局域配置

# setting.py输入
ALLOWED_HOSTS = ['*',]
  • 1
  • 2

注:找到并更改ALLOWED_HOSTS就可开启局域网
setting.py可以在项目文件中找到

8. include

  1. 新建app/urls

    /(project)/(app)/urls
    
    • 1
  2. 创建URLconf

    # project/app/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
    	 path('', views.index, name='index'),
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  3. 插入URLconf

    # project/urls.py
    from django.contrib import admin
    from django.urls import include, path
    
    urlpatterns = [
      	path('(appitem)/', include('(appitem).urls')),
    	path('admin/', admin.site.urls),
    ]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

9. 自定端口

  1. python文件更改

    # manage.py输入
    execute_from_command_line(['manage.py', 'runserver', '0.0.0.0:2516'])
    
    • 1
    • 2
  2. 启用端口

    # project/terminal输入
    python3.8 manage.py runserver 0.0.0.0:8000
    
    • 1
    • 2
  3. 清空端口(若需)

    # Question: ( 报错 )
    Django-Error: That port is already in use
    
    # Answer: ( 查询正在使用的进程并终止 )
    lsof -i:oooo 
    kill -9 nnnn 
    # 或GoogleCloud
    sudo fuser -k 80/tcp
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

10. 局域测试

# 浏览器输入
http://192.168.x.xx:8000/
  • 1
  • 2

注:ip地址替换成局域网设备地址
按住Option点击MacTopBar中的无线图标,查阅并获得ip地址
代码中192.168.x.xx处即为ip地址替换处
可以使用外部手机或电脑进行测试访问
若无法连接,重新输入以下指令并确认端口号一致

python3.8 manage.py runserver 0.0.0.0:8000
  • 1

三. 账号功能

1. 创建模型

  1. 编辑模型

    from django.db import models
    from django.utils.translation import gettext_lazy as _
    from django.utils.encoding import smart_str  
    
    class Music(models.Model):
    title  = models.CharField(_(u'名称'),max_length=250)
    author = models.CharField(_(u'作者'),max_length=250)
    url    = models.CharField(_(u'地址'),max_length=250)
    
    createdate = models.DateTimeField(_
    (u'创建时间'),
    auto_now_add=True,
    blank=True
    )	
    
    def __unicode__(self):
    	return smart_str(self.title)
    	
    class Meta:
    	verbose_name = _(u'音乐库')
    	verbose_name_plural = _(u'音乐库') 
    	ordering=['-createdate']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  2. 激活模型

    INSTALLED_APPS = [
    	...
    	'(app-item).apps.(App-item)Config',
    ]
    
    • 1
    • 2
    • 3
    • 4
  3. 模型迁移

    python3.8 manage.py makemigrations (App-item)
    python3.8 manage.py migrate
    
    • 1
    • 2
  4. 模型删除(若需)

    python3.8 manage.py migrate (App-item) zero
    
    • 1

2. 管理数据

  1. 创建管理员

    python3.8 manage.py createsuperuser
    
    • 1
  2. 通知Admin站点

    from django.contrib import admin
    from .models import *
    
    admin.site.register(Music)
    
    • 1
    • 2
    • 3
    • 4
  3. 数据管理页面

    python3.8 manage.py runserver 0.0.0.0:8000
    http://0.0.0.0:8000/admin/login/?next=/admin/
    
    • 1
    • 2
  4. 离线数据库编辑

    # 离线浏览及编辑数据库
    DB Browser for SQLite Version 3.12.2
    
    • 1
    • 2
    # python 数据处理工具
    # ---------------------------------------------------------
    # 数据插入
    def multi_sql_insert(db_path,entities,point_id,point_init,table):
        '''
        - 数据嵌入
        :param db_path: '~/db.splite3'
        :param entities: [(0,'','',...),(0,'','',...),...]
        :param point_id: 'id,title,author,...'
        :param point_init: '?,?,?,...'
        :param table: 'Database'
        '''
        import sqlite3
        db = sqlite3.connect(db_path)
        cursorObj = db.cursor()
        cursorObj.execute(f'SELECT * FROM {table}')
        rowcount = cursorObj.fetchall()
        for e in range(len(entities)):
            entity  = entities[e]
            add_row =  str(len(rowcount)+e)
            cursorObj.execute(
                f"INSERT INTO {table}({point_id}) "
                f"VALUES({point_init})",
                tuple([add_row] + list(entity[1:]))
            )
            db.commit()
        db.close()
    
    # 数据修改
    def multi_sql_update(db_path,updates,table,condition):
        '''
        - 数据修改更新
        :param db_path: '~/db.splite3'
        :param updates: [['title','John']] -> (point:value)
        :param table: 'Database'
        :param condition: 'id = 1' / 'WHERE price > 100'/ ...
        '''
        import sqlite3
        db = sqlite3.connect(db_path)
        for updt_i in range(len(updates)):
            update_point = updates[updt_i][0]
            updt_value   = updates[updt_i][1]
            def sql_update(db,condition,update_point,updt_value,table):
                cursorObj = db.cursor()
                cursorObj.execute(f'UPDATE {table} SET '
                                  f'{update_point} = "{updt_value}" '
                                  f'where {condition}'
                                 )
                db.commit()
            sql_update(db,condition,update_point,updt_value,table)
        db.close()
    
    # 数据查询
    def sql_fetch(db_path,point):
        '''
        - 数据查询
        :param db_path: '~/db.splite3'
        :param point: 'id,name'
        :return: rows = ('','','',...)
        '''
        import sqlite3
        db = sqlite3.connect(db_path)
        cursorObj = db.cursor()
        cursorObj.execute(f'SELECT {point} FROM {table}')
        rows = cursorObj.fetchall()
        db.close()
        # print(rows)
        return rows
    
    # 数据删除
    def sql_delete(db_path,table,condition):
        '''
        - 数据行删除
        :param db_path: '~/db.splite3'
        :param table: 'Database'
        '''
        import sqlite3
        db = sqlite3.connect(db_path)
        cursorObj = db.cursor()
        cursorObj.execute(f'DELETE FROM {table} WHERE {condition}')
        db.commit()
        db.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
  5. Q & A
    a. 账号记得 | 密码忘了

    终端输入
    python3 manage.py shell
    >>> from django.contrib.auth.models import User
    >>> user = User.objects.get(username='你的管理员账号')
    >>> user.set_password('你想设置的新密码')
    >>> user.save()
    >>> quit()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    b. 账号忘了 | 密码忘了:

    终端输入
    python3 manage.py shell
    >>> from django.contrib.auth.models import User
    >>> user = User.objects.get(pk=1)
    >>> user
    <User: 管理员账号> 
    
    >>> user = User.objects.get(username='你的管理员账号')
    >>> user.set_password('你想设置的新密码')
    >>> user.save()
    >>> quit()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

3. 登录验证

前端获得用户验证输入(Get)| 后端对比用户验证信息(Auth)

a. 输入模型

# models.py
from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
USERNAME_FIELD = 'username'
username = models.CharField('username', max_length=128, blank=True)
password = models.CharField('password', max_length=50, blank=True)
mod_date = models.DateTimeField('Last modified', auto_now=True)
class Meta: verbose_name = 'User Profile'
def __str__(self): 
	return "{}'s profile".format(self.user.__str__())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

注:一定要设置USERNAME_FIELD

b. 配置注册

# setting.py
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
  • 1
  • 2

c. urlpatterns

# urls.py
path('login_check/', views.login_check, name='login_check')
  • 1
  • 2

d. 添加视图

from django.contrib.auth import authenticate,login

def login_check(requst):
    if requst.method == 'GET':
        username = requst.GET.get('username')
        password = requst.GET.get('password')
        user     = authenticate(username=username,password=password)
        if user is not None:
            login(requst, user)
            return redirect('index') 
        else:
            return render(requst,'signin.html')
    else:
        return render(requst,'signin.html')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

注:确认身份后登录要使用跳转redirect(name)
若使用render会在url地址中体现用户名和密码

e. 页面引入

# html
<form action="{% url 'login_check' %}" ... method="GET">
<input name="username" ...>
<input name="password" ...>
  • 1
  • 2
  • 3
  • 4

4. 创建账号

from django.contrib.auth.models import User

def create_user(requst):
    if requst.method == "GET":
        username = requst.GET.get('username')
        password = requst.GET.get('password')
        email    = requst.GET.get('email')
        confirm  = requst.GET.get("confirm_password")
        if password == "" or username == "":
            alert_box(requst, "用户名或密码不能为空")
        elif password != confirm:
            alert_box(requst, "两次密码不一致")
        elif User.objects.filter(username=username):
            alert_box(requst, "该用户名已存在")
        else:
            new_user = User.objects.create_user(username=username, password=password,email=email)
            new_user.save()
            return redirect('index')
    return render(requst, 'signup.html')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

5. 退出登录

def signout(requst):                      
    logout(requst)        
    return render(requst,'signin.html')  
  • 1
  • 2
  • 3

6. 弹窗提示

a. view.py

from django.contrib import messages

def alert_box(requst,message):
    messages.success(requst,message)
  • 1
  • 2
  • 3
  • 4

b. html 加载在body内

<!-- message box -->
{% if messages %}
	<script>
    	{% for msg in messages %}
         	alert('{{ msg.message }}');
        {% endfor %}
    </script>
{% endif %}
<!-- end message box -->   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

7. 邮件分发

  1. setting.py
    # 发送邮件配置
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    # smpt服务器地址 [建议使用163] 
    EMAIL_HOST = 'smtp.163.com'
    # 邮箱端口
    EMAIL_PORT = 25
    # 发送邮件的邮箱
    EMAIL_HOST_USER = 'xxx@163.com'
    # ***客户端授权密码!!**  
    # 需要去163邮箱开启smpt服务并获得唯一授权码
    EMAIL_HOST_PASSWORD = '!@#$%^&*$%^@#'
    # 收件人看到的发件人
    EMAIL_FROM = 'xxx<xxx@163.com>'
    # 避免报错加上
    DEFAULT_FROM_EMAIL = 'xxx@163.com'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  2. views.py
    from django.shortcuts import render, HttpResponse
    from django.core.mail import send_mail, EmailMultiAlternatives
    from django.conf import settings
    from email.header import make_header
    from email.mime.text import MIMEText    
    from email.mime.image import MIMEImage 
    import os
    
    def send_simple_email(request):
        subject = "[邮件主题]"
        message = "[邮件内容]"
        from_email = settings.EMAIL_FROM 
        recipient_list = ["xx@xx.com","xxx@xx.com"...]
        ret = send_mail(subject, message, from_email, recipient_list)
        return HttpResponse(ret)
        
    def send_complex_email(request):
        subject      = ''
        text_content = ''
        html_content = ''
        from_email   = settings.DEFAULT_FROM_EMAIL
        receive_email_addr = ["xx@xx.com"]
        msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr)
        msg.attach_alternative(html_content, "text/html")
     
        # 发送图像
        html1 = "<div><img src='cid:imgid'></div>"
        msg_html_img = MIMEText(html1, 'html', 'utf-8')
        msg.attach(msg_html_img)
        file_path = os.path.join(settings.BASE_DIR, "static/kd.png")
        with open(file_path, "rb") as f:
            msg_img = MIMEImage(f.read())
        msg_img.add_header('Content-ID', 'imgid') 
        msg.attach(msg_img)
     
        # 发送txt附件
        file_path = os.path.join(settings.BASE_DIR, "日志.txt")
        text = open(file_path, 'rb').read()
        file_name = os.path.basename(file_path)
        b = make_header([(file_name, 'utf-8')]).encode('utf-8')
        msg.attach(b, text)
     
    	# 发送jpg附件
        file_path = os.path.join(settings.BASE_DIR, "test.jpg")
        text = open(file_path, 'rb').read()
        file_name = os.path.basename(file_path)
        b = make_header([(file_name, 'utf-8')]).encode('utf-8')
        msg.attach(b, text)
     
    	# 发送xlsx附件
        file_path = os.path.join(settings.BASE_DIR, "test.xlsx")
        text = open(file_path, 'rb').read()
        file_name = os.path.basename(file_path)
        b = make_header([(file_name, 'utf-8')]).encode('utf-8')
        msg.attach(b, text)
        # msg.attach_file(file_path)
        msg.send()
        return HttpResponse("发送完成")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
  3. urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path("send_simple_email/", views.send_simple_email, name="send_simple_email"),  
        path("send_complex_email/", views.send_complex_email, name="send_complex_email"), 
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

如果部署在外服务器,如:AWS,需要对TLS和SSL进行特别设置
163邮箱配置

EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 465
EMAIL_HOST_USER = 'xxx'
EMAIL_HOST_PASSWORD = 'xxx'  # 这里是授权码
# 下面两项只能有一个为True
EMAIL_USE_TLS = False
EMAIL_USE_SSL = True
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

gmail邮箱的配置

# 出现认证问题参考 -> https://segmentfault.com/q/1010000008458788

EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'xxx'
EMAIL_HOST_PASSWORD = 'xxx'
# 下面两项只能有一个为True
EMAIL_USE_TLS = True
EMAIL_USE_SSL = False
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

8. 浏览权限

  1. 创建装饰器

    # project/app/decorator.py
    from django.shortcuts import render
    from django.http import HttpResponse
    
    def already_login(func):
        def alr_login(request, *args, **kwargs):
       		outsec = 60*60*3                                                 
    		request.session.set_expiry(outsec) # 超过秒数后失效                                                           
    		# import datetime                                                
    		# outday = datetime.datetime.now() + datetime.timedelta(days=30) 
    		# request.session.set_expiry(outday) # 超过日期后时效                   
    		# request.session.set_expiry(0)      # 关闭浏览器后失效                  
    		# request.session.set_expiry(None)   # 遵循全局失效策略                                        
            if request.user.is_authenticated:
                return func(request, *args, **kwargs)
            else:
                return render(request,'signin.html')
        return alr_login
    
    # def validate_permission(func):
    #    def valid_per(request, *args, **kwargs):
    #        group_id = request.session.get('group_id')
    #        if group_id == 0:
    #            return func(request, *args, **kwargs)
    #        else:
    #            return HttpResponse("无权访问")
    #    return valid_per
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  2. 配置需要权限的视图

    # views.py
    from MusicStore.decorator import already_login
    
    @already_login
    def index (request):
    	return render(request,'index.html')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

9. 密码重置

  1. 向email发送验证码

    def email_code(requst):                                                                                
        username = requst.GET.get('username')                                                              
        has_username = User.objects.filter(username=username)                                              
        if has_username:                                                                                   
            def random_str(randomlength=4):                                                                
                import random                                                                              
                codekey = ''                                                                               
                chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'                                             
                length = len(chars) - 1                                                                    
                for i in range(randomlength):                                                              
                    codekey += chars[random.randint(0, length)]                                            
                return codekey                                                                             
                                                                                                           
            subject = '验证码'                                                                                
            text_content = '重置密码'                                                                          
            code = random_str()                                                                            
            requst.session["code"]=code                                                                    
            requst.session["username"] = username                                                          
            email = User.objects.get(username=username).email                                              
            requst.session["email"] = email                                                                
                                                                                                           
            html_content = f'<h3>重置验证码,' \                                                                 
                           f'请谨慎保管</h3><h1>' \                                                             
                           f'<font style="background-color:darkgray;color: #3F3F3F" >{code}</font></h1>'   
            from_email = settings.DEFAULT_FROM_EMAIL                                                       
            receive_email_addr = [email]                                                                   
            msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr)            
            msg.attach_alternative(html_content, 'text/html')                                              
            msg.send()                                                                                     
            return redirect('password')                                                                    
        else:                                                                                              
            alert_box(requst,'Email尚未注册')                                                                  
            return redirect('forgot')                                                                      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  2. 修改密码

    @validate_codemail
    def pswrd_reset(requst):
        code     = requst.GET.get("code")
        password = requst.GET.get("password")
        email    = requst.session['email']
        username = requst.session["username"]
        user     = User.objects.get(username=username)
        confirm_password = requst.GET.get("confirm_password")
        
        if confirm_password == password:
            has_username = User.objects.filter(username=username,email=email)
            if has_username:
                if code == requst.session['code']:
                    user.set_password(password)
                    user.save()
                    requst.session.flush()    
                    alert_box(requst,'密码重置成功')
                    return redirect('signin')
                else:
                    requst.session.flush()
                    alert_box(requst,'验证码不正确')
                    return redirect('forgot')
            else:
                requst.session.flush()
                alert_box(requst, '用户名尚未注册')
                return redirect('forgot')
        else:
            alert_box(requst, '两次密码不一致')
            return redirect('password')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  3. 装饰器

    def validate_codemail(func):
        def valid_codmail(request, *args, **kwargs):
            session = len(request.session.items()) # 判断是否有验证码请求记录
            if session > 0:
                return func(request, *args, **kwargs)
            else:
                return redirect('forgot')
        return valid_codmail
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  4. urls

    urlpatterns = [
    	...
    	path('email_code/', views.email_code, name='email_code'),
    	path('pswrd_reset/',views.pswrd_reset,name='pswrd_reset'),
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
  5. 前端

    ...
    <form  action="{% url 'email_code' %}"... method="GET">
    <input name="username"... placeholder="用户名">
    ...
    <input name="code"... placeholder="验证码">
    <input name="password" ... placeholder="新 密 码">
    <input name="confirm_password" ... placeholder="确认密码">
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

10. 加密解密

from django.contrib.auth.hashers import make_password, check_password
	
sha256_password = make_password("123456", None, 'pbkdf2_sha256') # 加密
checkbool = check_password("123456",'pbkdf2_sha256'# 校验
  • 1
  • 2
  • 3
  • 4

11. 请求限制

  1. 安装插件
    pip3.8 insatall django-ratelimit
    
    • 1
  2. 设限条件
    @ratelimit(key='ip', rate='5/h',block=True)
    your_views(requst):
    ...
    
    • 1
    • 2
    • 3
  3. 参考链接
    https://django-ratelimit.readthedocs.io/en/stable/usage.html
    
    • 1

12. 禁止IP

  1. 安装GeoLite2并下载IP数据库

    # 1. 安装geoip2
    pip3.8 install geoip2
    
    # 2. 下载City和Country数据库 
    搜索-> GeoLite2免费下载
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  2. 创建 middleware.py ,与 settings.py 同目录下

    from django.http import HttpResponse
    from django.utils.deprecation import MiddlewareMixin    # 1.10.x
    
    class TestMiddleware(MiddlewareMixin):
        def process_view(self,request,view_func,*view_args,**view_kwargs):
        def get_ip_location():
            '''
            -获得ip位置信息
            :param request:
            :param datapath:
            :return: country(string)
            '''
            import geoip2.database
            datapath = os.path.join(IPDIA_ROOT,'GeoLite2-City.mmdb')
            reader = geoip2.database.Reader(datapath)
            try:
                response = reader.city(ip)
                country = response.country.iso_code
                cityname = response.city.name
                data = {'country': country, 'city': cityname}
                return data['country']
            except:
                local_ips = ['127.0.0.1']
                if ip in local_ips:
                    return 'LOCAL'
                else:
                    return 'Unkown IP'
    
        if 'HTTP_X_FORWARDED_FOR' in  request.META:
            ip = request.META['HTTP_X_FORWARDED_FOR']
        else:
            ip = request.META['REMOTE_ADDR']
    
    	# 使用GeoLite2数据库判别
        id_country = get_ip_location()
        print(f'[{id_country}] -> {ip} ')
    
        countries = ['CN','TW','HK','LOCAL']
        if id_country not in countries:
            return HttpResponse('<h1 style="opacity:0.2">no permission</h1>')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
  3. setting.py

    MIDDLEWARE = [
    	...
        '(django项目名).middleware.TestMiddleware',
    ]
    
    • 1
    • 2
    • 3
    • 4

13. 404页面

  1. urls.py

    ...
    from MusicStore.views import *
    handler403 = page_403
    handler404 = page_404
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
  2. views.py

    # setting.py
    DEBUG = False
    ...
    
    • 1
    • 2
    • 3
    def page_404 (request, exception, template_name='404.html'):
        return render(request,template_name)
    
    • 1
    • 2

14. 关闭Debug模式

  1. setting.py
    DEBUG = False
    
    • 1
  2. terminal
    <!-- MacOS系统下Debug默认为True,关闭成False后静态素材和样式丢失的解决办法 -->
    python3.8  manage.py runserver 0.0.0.0:8000 --insecure 
    
    • 1
    • 2

15. RestAPI

  1. 安装组件

    pip3.8 install djangorestframework
    pip3.8 install markdown 
    pip3.8 install django-filter 
    
    • 1
    • 2
    • 3
  2. 添加项目

    INSTALLED_APPS = [
    	...
    	'rest_framework',
    ]
    
    • 1
    • 2
    • 3
    • 4
  3. 配置模型

    # setting.py
    REST_FRAMEWORK = {
    	'DEFAULT_PERMISSION_CLASSES': [
       		'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    	]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  4. 创建API

    # (project)/urls.py
    from django.contrib import admin
    from django.urls import include, path
    from (appitem).models import *
    from rest_framework import routers, serializers, viewsets
    
    # Serializers define the API representation.
    class MusicSerializer(serializers.HyperlinkedModelSerializer):
    	class Meta:
        	model = Music
        	fields = ['title', 'author', 'url', 'createdate']
    
    # ViewSets define the view behavior.
    class MusicViewSet(viewsets.ModelViewSet):
    	queryset = Music.objects.all()
    	serializer_class = MusicSerializer
    
    # Routers provide an easy way of automatically determining the 	URL conf.
    router = routers.DefaultRouter()
    router.register(r'music', MusicViewSet)
    
    urlpatterns = [
    	path('', include(router.urls)),
    	path('(appitem)/', include('(appitem).urls')),
    	path('admin/', admin.site.urls),
    	path(r'^api-auth/', include('rest_framework.urls'))
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
  5. 运行项目

    	python3.8 manage.py migrate
    	python3.8 manage.py runserver
    	http://0.0.0.0:8000/music/
    	```
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

16. 查询数据库键

# 通过转化Query第一列数据为dict字典类型,获取keys
# Query = [{},{},...]
obj = Obj()
Query_Keys = dict(obj.objects.values()[0]).keys()
  • 1
  • 2
  • 3
  • 4

17. 数据库添加记录

# 获取Query所有键
query_keys = list(dict(Music.objects.values()[0]).keys())

# 需要手工添加的字典键内容
handle_keys = ['aaa', 'bbb']
new_dict = {}
new_dict.update({
    'aaa': '',
    'bbb': '',
})

# 默认值添加
for key in query_keys:
    if key not in handle_keys:
        new_dict.update({key: '-'}) # 非指定值都用-填写

# 创建数据
Music.objects.create(**new_dict)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18


四、前端调试

1. 下载模板

# 浏览器输入: 挑选下载一个喜欢的html模板
  • 1

注: 找一些比较成熟综合素材网
UI/UX设计可以降维用于日常测试
适合平时制作ppt和影视资源使用

2. html调式

  1. 映射跳转链接

    # 替换模板中的页面跳转 | 新建substitution.py
    def hyperlink_url_modify():
    	from tqdm import tqdm
        page_path = '~'
        pages = [i for i in os.listdir(page_path) if i.endswith('.html')]
        
        # 遍历所有同类html模板
        for page_i in tqdm(range(len(pages))):
            index_file = os.path.join(page_path,pages[page_i])
            f = open(index_file,'r')
            
            # 链接映射
            lines = []
            for line in f.readlines():
                new_line = line
                for page_ii in range(len(pages)):
                    html_nm   = os.path.basename(pages[page_ii])
                    page_nm   = html_nm.split('.html')[0]
                    source_nm = '"'+ html_nm + '"'
                    subs_nm   = '"{% url ' + '\'' +  page_nm  + '\'' + ' %}"'
                    if '404' in page_nm:
                        subs_nm   = '"{% url ' +  '\'' + 'page_404' +  '\'' + ' %}"'
                    new_line = new_line.replace(source_nm, subs_nm)
                lines.append(new_line)
            new_lines = ''.join(lines)
            
            # 覆盖原文件
            f = open(index_file,'w')
            f.write(new_lines)
            f.close()
        print('[ Static Folder Modify Finished !]')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
  2. 映射static物料

    # 替换模板中的物料静态地址 | 新建substitution.py
    def static_url_modify():
        page_path = '~'
        pages = [i for i in os.listdir(page_path) if i.endswith('.html')]
        for page_i in tqdm(range(len(pages))):
            index_file = os.path.join(page_path,pages[page_i])
            f = open(index_file,'r')
    
            lines = []
            for line in f.readlines():
                new_line = line \
                    # .replace('"image', '"../static/image')
                    # .replace('"icon', '"../static/icon')
                    # .replace('"css', '"../static/css')\
                    # .replace('"js', '"../static/js')\
                    # .replace('"img', '"../static/img')
                    # .replace('assets','../static')
                    # .replace('(assets', '(../static/assets') \
                    # .replace('"assets/', '"../static/assets/') \
                    # .replace('./','../static/')\
                    # .replace('"images/','"../static/images/')\
                    # .replace('"css/', '"../static/css/') \
                    # .replace('"libs/', '"../static/libs/') \
                    # .replace('"scripts/', '"../static/scripts/')\
                    # .replace('\'images', '\'../static/images')
                lines.append(new_line)
    
            new_lines = ''.join(lines)
            f = open(index_file,'w')
            f.write(new_lines)
            f.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
  3. 批量views

    # views.py
    def multi_def_generate():
        page_path = '~'
        pages = [i for i in os.listdir(page_path) if i.endswith('.html')]
        lines = ''
        for page_i in tqdm(range(len(pages))):
            page_nmfull = pages[page_i]
            page_nm = pages[page_i].replace('.html','')
            if page_nm.startswith('404'):
                def_pattern = f'def page_40(request):\n    return render(request,\'{str(page_nmfull)}\')\n\n'
            else:
                def_pattern = f'def {page_nm}(request):\n    return render(request,\'{str(page_nmfull)}\')\n\n'
            lines += def_pattern
        print(lines)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  4. 批量urlspattern

    # (project)/urls.py
    def urlspatterns_generate():
        page_path = '~'
        pages = [i for i in os.listdir(page_path) if i.endswith('.html')]
        lines = ''
        for page_i in tqdm(range(len(pages))):
            page_nm    = pages[page_i].replace('.html','')
            if page_nm.startswith('404'):
                urlspattern = f'path(\'{page_nm}/\', views.page_404, name=page_404),\n'
            else:
                urlspattern = f'path(\'{page_nm}/\', views.{page_nm}, name=\'{page_nm}\'),\n'
            lines += urlspattern
        print(lines)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

3. 导入html

  1. 创建templates

    # 该文件夹将用于存放html内容
    (project)/(item)/templates
    
    • 1
    • 2
  2. 导入html

    # 将模板索引页拖入templates中
    (project)/(item)/templates/index.html
    (project)/(item)/templates/home.html
    ...
    
    • 1
    • 2
    • 3
    • 4
  3. views新建

    # views.py输入 ( path一定要设置 name= ' ' 方便后面调用网页 )
    from django.urls import path
    from . import views 
    
    urlpatterns = [
    	path('index/', views.index,name='index'),
    	path('home/', views.home,name='home'),
    	...
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  4. urls新建

    # url.py输入
    from django.shortcuts import render
    
    def index(request):
    	 return render(request, 'index.html')
    	 
    def home(request):
    	return render(request, 'home.html')
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  5. DIRS设置

    # setting.py 添加DIRS
    import os
    
    TEMPLATES = [
    	{ ...
    	  'DIRS': [os.path.join(BASE_DIR, 'templates')],
    	  ... }
     ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  6. INSTALLED_APPS设置

    # setting.py 添加所建的App
    import os
    
    INSTALLED_APPS = [
    [ ...
      'xxx-app',
      ... 
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

4. 导入static

  1. 创建static
    # 该文件夹将用于存放物及料配置内容 (位置与templates同级)
    (project)/(item)/static
    
    • 1
    • 2
  2. 导入物料
    # 文件移动 (该文件夹将用于存放Images、JavaScript、CSS等文件)
    (project)/(item)/static/images
    (project)/(item)/static/css
    (project)/(item)/static/js
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
  3. 静态收集
    1. setting.py 设置

      # 为了部署时将静态文件复制到所有服务端都可以访问的文件夹
      STATIC_URL = '/static/'
      STATIC_ROOT = os.path.join(BASE_DIR, 'collectstatic/')
      STATICFILES_DIRS = (
      	os.path.join(BASE_DIR, 'static'),
      )
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. manage.py 收集

      # MacOS Project/Terminal执行收集程序
      python manage.py collectstatic
      
      • 1
      • 2
    3. 注释

      # 关于STATIC的注释
      STATIC_URL  = static地址 [让网页可以访问到静态文件]
      STATIC_ROOT = 收集归档所有static文件
      		  	  [让静态文件夹可被所有客户端访问到]
      		  	  [可以避免多个app需要多个静态目录]
      		 	  [名字自取但需和uwsgi.ini中路径一致]
      STATICFILES_DIRS = [让Django同时在App的内、外搜索静态文件]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

5. html映射

  1. 跳转链接映射
    # html链接映射(pagename在urlpatterns里设置)
    # 替换前 <a href="about.html">关于</a> 
    # 替换后 <a href="{% url 'home' %}">关于</a> 
    {% url 'home' %}
    
    • 1
    • 2
    • 3
    • 4
  2. 物料链接映射
    # 物料链接映射
    ../static/images/xxx.png
    ../static/css/xxx.css
    ../static/js/xxx.js
    
    • 1
    • 2
    • 3
    • 4

6. 测试服务

  1. 运行网页服务
    # project/terminal输入
    python3.8 manage.py
    
    • 1
    • 2
  2. 退出网页服务
    # 退出项目 
    control + c
    
    • 1
    • 2

7. 调试结果

动画效果及排版等正常显示

8. loading动画

 <!-- loaing effects -->
{% if # %}
<div class="loading">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
                
<style type="text/css">
 * {
     padding: 0;
     margin: 0;
   }
                
.loading{
	height: 25px;
	margin: 100px auto;
	display: flex;
	justify-content: center;
}

.loading span{
 	width: 6px;
 	height: 100%;
 	border-radius: 4px;
 	background-color: lightgreen;
 	animation: load 1s ease infinite;
 	margin: 0 2px;
}

@keyframes load{
  	0%,
 	100% {
  		transform: scaleY(1.2);
 		background-color: lightgreen;
  	}
  	50% {
  		transform: scaleY(0.3);
  		background-color: lightblue;
   	}
}
                
.loading span:nth-child(2){
	animation-delay: 0.2s;
}
.loading span:nth-child(3){
	animation-delay: 0.4s;
}
.loading span:nth-child(4){
    animation-delay: 0.6s;
}
.loading span:nth-child(5){
    animation-delay: 0.8s;
}
</style>
{% endif %}
<!-- end loaing effects -->
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

9. 页面显参

  1. view.py

    # 需以字典方式传参
    def yourView(request):
        var = xxx
        return render(request,'phones.html',context={'msg':var})
        
    or
    
    def yourView(request):
        var = {
        	'':''
        }
        return render(request,'phones.html',context=var)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  2. .html

    <span> {{ msg }} </span>
    
    • 1
  3. ForEach

    {% for i in msg %}
        <li> {{i.title}} </li>
    {% endfor %}
    
    • 1
    • 2
    • 3

10. CSS & JS 笔记

1. scroll滚动条

隐藏滚动条

::-webkit-scrollbar{width:0;}
  • 1

启动滚动条

.overflow-x-auto {
    overflow-x: auto
}
  • 1
  • 2
  • 3

2. css类变量应用到for循环

// html | 增加变量id,为id添加类并启动

{% for i in is %}
	<div id="id{{ forloop.counter }}" class="" ></div>
{% end for %}

<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
	$("#id{{ forloop.counter }}").addClass('xxx').toggleClass('nnn')
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
/* css */
.xxx {
    height: 115px;
    width: 100%;
    position: absolute;
    bottom: 0;
    background: rgba(255, 255, 255, 0.85);
    transform: translateY(35px);
    transition: all 0.7s ease-in-out;
}

.nnn {
    transform: translateY(0px);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3. 为.js文件import模块

<!-- 需要为script添加module类型 -->
<script src="../static/js/xxx.js" type="module"></script>
  • 1
  • 2
import xxx from '../.../../'

var xxx = function(){
    	...
}
  • 1
  • 2
  • 3
  • 4
  • 5

4. 为.js文件添加变量

<!-- html 需要为script添加id并新增data属性 -->
<script id="testScript" src="../static/js/albumplay.js" data="testscript"></script>
  • 1
  • 2
// .js
var data = document.getElementById('testScript').getAttribute('data').toString()
  • 1
  • 2

11. jQuery

1. 概念简述

jQuery - 用于简化选取HTML元素,并对它们执行"操作"
https://blog.csdn.net/u012932876/article/details/117465004?spm=1001.2014.3001.5506
  • 1
  • 2

2. 后台API

<button id="btn">ClickMe</button>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
	$(function (){
	   	$("#btn").click(function (){
	         $.ajax({
	             type 	 : "post",
	             url  	 : "{% url 'handle' %}"
	             data 	 : {"username":"jacky"},
	             dataType : "json",
	         });
	    });
	})
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
def handle():
	if request.method == 'POST':
	   collection = Favorite()
	   username = request.POST.get('username')
  • 1
  • 2
  • 3
  • 4

3. HTML标签传参

<div id=""></div>
<scrip>
	$("#id").text(data["msg"])
</scrip>
  • 1
  • 2
  • 3
  • 4

4. 传参不跳转

$ajax({
	...
});
return false;
  • 1
  • 2
  • 3
  • 4

5. 传参且跳转页面

$ajax({
	...
	success: function(data){
		window.location.href="{% url 'xxx' %}";
	}
});	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6. 跳转页面带参

// 增加js文件 <getparam.js>
(function ($) {
    $.extend({
        //1、取值 $.Request("name")
        Request: function (name) {
            var sValue = location.search.match(new RegExp("[\?\&]" + name + "=([^\&]*)(\&?)", "i"));
            //decodeURIComponent解码
            return sValue ? decodeURIComponent(sValue[1]) : decodeURIComponent(sValue);
        },
	});
})(jQuery);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
// 前一页传参
<script>
	$(function(){
		...
		name = "";
		age  = "";
		url  = "xxx.html?name="+name+"&age="+age;//此处拼接内容
		window.location.href = url;
	})
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
// 后一页获参
<script>
	function getData(){
		var name = $.Request("name");
		var age  = $.Request("age");
	}
	getData()
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

7. 定时自动跳转

<header style="text-indent: 2em; margin-top: 30px;">
	<span id="time">4</span><a href="index.html" title="点击访问">跳过</a>
</header>

<script>
function delayURL() {
	var delay = document.getElementById("time").innerHTML;
	var t = setTimeout("delayURL()", 1000);
	if (delay > 0) {
        delay--;
        document.getElementById("time").innerHTML = delay;
    } else {
		clearTimeout(t);
        window.location.href = "index.html";
    }
}
delayURL()
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

8. 输入时按钮定时隐藏与显示

var t
$("#ipt").on('input',function (){
	clearTimeout(t)
    $("#ipt_btn").hide()
    t = setTimeout(function (){
        $("#ipt_btn").show()
		},1500);
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

9. SCSS

  1. 过程简述

    codepen上有很多有趣的svg和动画效果,很多是scss格式而不是css
    所以需要研究如何在HTML中引入,大概路径如下:
    
    1. 安装npm
    2. 安装sass
    3. scss转换css
    4. 引入css
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  2. 安装步骤

    1. brew install node
    # 报错
    (2.0.Error: Command failed with exit 128: git)
    # 解决(查看-复制-运行)
    2.1 brew -v
    # 重装
    3. brew install node
    # 版本
    4. npm -v
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    # 安装cnpm(淘宝镜像)
    npm install -g cnpm --registry=https://registry.npm.taobao.org
    
    • 1
    • 2
    # 若安装nrm报错request@2.88.2: request has been deprecated
    1. npm config set registry https://registry.npm.taobao.org
    2. npm config get registry
    3. npm install nrm -g
    4. cnpm install node-sass --save-dev
    5. cnpm install sass-loader --save-dev
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    # 在需要转换的sass文件夹下terminal
    sass (input.scss) (output.css)
    
    • 1
    • 2

10. 双重For循环

# 使用Django模板标签
data1 =  xx.objects.all()
data2 = {
           "key": ["value1","value2"],
           ...
		}
msg = {
		"data1" : data1,
    	"data2" : data2,
	  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
<!-- Html Page -->
{% for k,v in msg %}
	<div>{{ k.xx }}</div>
	{% for item in k %}
		<div>{{ item.yy }}</div>
	{% endfor %}
{% endfor %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

11. 分页显示

view.py

def page(request):                                                             
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger   
    song = Music.objects.all()            # 获取所有的book                          
    paginator = Paginator(song, 30)       # 每页10条                              
    page = request.GET.get('page', 1)     # 获取页面请求的page页码,默认为第1页               
    currentPage = int(page)                                                    
    try:                                                                       
        song_page = paginator.page(page)  # book_list为page对象                   
    except PageNotAnInteger:                                                   
        song_page = paginator.page(1)                                          
    except EmptyPage:                                                          
        song_page = paginator.page(paginator.num_pages)                        
    result =  {                                                                
                 "book_list"   : song_page,                                    
                 "paginator"   : paginator,                                    
                 "currentPage" : currentPage,                                  
              }                                                                
    return render(request, "page.html",result)                                 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>

	<div class="container">
	    <h4>分页器</h4>
	    <ul>
	    <!-- 上一页 -->
	        {% for book in book_list %}
	            <div>{{ book.title }} {{ book.length }}</div>
	        {% endfor %}
	    </ul>
	    <ul class="pagination" id="pager">
	        {% if book_list.has_previous %}
	            <li class="previous">
	                <a href="/page/?page={{ book_list.previous_page_number }}">上一页</a>
	            </li>
	        {% else %}
	            <li class="previous disabled"><a href="#">上一页</a></li>
	        {% endif %}
	        
		<!-- 页数 -->
	        {% for num in paginator.page_range %}
	            {% if num == currentPage %}
	                <li class="item active"><a href="/page/?page={{ num }}">{{ num }}</a></li>
	            {% else %}
	                <li class="item"><a href="/page/?page={{ num }}">{{ num }}</a></li>
	            {% endif %}
	        {% endfor %}
	
		<!-- 下一页 -->
	        {% if book_list.has_next %}
	            <li class="next">
	                <a href="/page/?page={{ book_list.next_page_number }}">下一页</a>
	            </li>
	        {% else %}
	            <li class="next disabled"><a href="#">下一页</a></li>
	        {% endif %}
	    </ul>
	</div>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

12. 音频播放器

<!-- 适用于分页及不同歌单播放的综合情况 -->
<audio id="audioplayer" controls="controls" hidden><source src=""/></audio>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
    if (document.readyState){
        var tapid_cache,playlist_cache
        $("#pausebtn{{ var_1 }}_{{ var_2 }}").hide()
        document.getElementById("audioplayer").src = "{{ song.url }}"
        let btn = document.getElementById("playmusic{{ var_1 }}_{{ var_2 }}")
        btn.onclick = function (){
            var audioplayer = document.getElementById("audioplayer")
            // -> 暂停状态(点击前)
            if (audioplayer.paused){
                // 同一首歌继续播放
                if (tapid_cache == "{{ var_2 }}"){
                    audioplayer.play()
                }
                // 不同歌曲切换播放
                else {
                    // 通过加载src来重头播放达到停止播放效果
                    document.getElementById("audioplayer").src = "{{ song.url }}"
                    audioplayer.play()
                }
                $("#playbtn{{ var_1 }}_{{ var_2 }}").hide()
                $("#pausebtn{{ var_1 }}_{{ var_2 }}").show()
                tapid_cache = "{{ var_2 }}"
                playlist_cache = "{{ var_1 }}"
            }
            // -> 播放状态(点击前)
            else {
                // 点击歌曲在歌单内序号相同
                if (tapid_cache == "{{ var_2 }}"){
                    // 同一张歌单
                    if (playlist_cache == "{{ var_1 }}"){
                        audioplayer.pause()
                        $("#playbtn{{ var_1 }}_{{ var_2 }}").show()
                        $("#pausebtn{{ var_1 }}_{{ var_2 }}").hide()
                    }
                    // 不同歌单
                    else {
                        // 停止正在播放内容
                        audioplayer.pause()
                        $("#pausebtn"+ playlist_cache + "_" + tapid_cache).hide()
                        $("#playbtn" + playlist_cache + "_" + tapid_cache).show()
                        // 更新新内容地址
                        document.getElementById("audioplayer").src = "{{ song.url }}"
                        // 播放新内容
                        audioplayer.play()
                        $("#playbtn{{ var_1 }}_{{ var_2 }}").hide()
                        $("#pausebtn{{ var_1 }}_{{ var_2 }}").show()
                    }
                }
                // 点击歌曲在歌单内序号不同
                else {
                    // 正在播放图标初始化
                    $("#pausebtn"+playlist_cache + "_" + tapid_cache).hide()
                    $("#playbtn" +playlist_cache + "_" + tapid_cache).show()
                    // 更新新内容地址
                    document.getElementById("audioplayer").src = "{{ song.url }}"
                    audioplayer.play()
                    $("#playbtn{{ var_1 }}_{{ var_2 }}").hide()
                    $("#pausebtn{{ var_1 }}_{{ var_2 }}").show()
                }
                // 记录当前播放标志缓存
                tapid_cache   = "{{ var_2 }}"
                playlist_cache = "{{ var_1 }}"
            }
            // 音乐播放完初始化播放按钮(实时监视)
            document.getElementById("audioplayer").ontimeupdate = function (){
                if (document.getElementById("audioplayer").ended){
                    $("#pausebtn"+playlist_cache + "_" + tapid_cache).hide()
                    $("#playbtn" +playlist_cache + "_" + tapid_cache).show()
                }
                // 非鼠标点击情况下启动或停止播放键监视按钮样式
                if (document.getElementById("audioplayer").paused){
                    $("#pausebtn"+playlist_cache + "_" + tapid_cache).hide()
                    $("#playbtn" +playlist_cache + "_" + tapid_cache).show()
                } else {
                    $("#pausebtn"+playlist_cache + "_" + tapid_cache).show()
                    $("#playbtn" +playlist_cache + "_" + tapid_cache).hide()
                }
            }
        }
        $("#close{{ var_1 }}").click(function (){
            document.getElementById("audioplayer").pause()
            {% for song in songs %}
                $("#playbtn{{ var_1 }}_{{ var_2 }}").show()
                $("#pausebtn{{ var_1 }}_{{ var_2 }}").hide()
                tapid_cache = ''
                playlist_cache = ''
            {% endfor %}
        })
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

13. Split分割字符串

let element = location.href.split(',',2).at(1)
  • 1

14. 鼠标长按触发

<script>
    let num = 0, tid;
    const btn = window.document.getElementById("")
    // 触发事件
    btn.onclick = function(e){
        triggerEvent()
    }
    // 鼠标抬起时
    btn.onmousedown = function(e){
        let hold_time = 500 // 设置定时,触发事件
        tid = setInterval(function(){
            triggerEvent()
        }, hold_time)
    }
    // 鼠标移开时,清除计时器
    btn.onmouseup = function(e){
        clearInterval(tid)
    }
    btn.onmouseout = function(e){
        clearInterval(tid); // 清除计时器
    }
    // 触发事件
    function triggerEvent() {
        num ++;
        // 当点击若干秒后执行操作
        let action_duration = 5
        if (num > action_duration) {
            btn.innerHTML = num
            $.ajax({
                type: '',
                url: '',
                data: {},
                success: function(){
                    window.location.href = ''
                },
            })
        }
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

15. input监听

// 失焦
$("#id").blur('input',function (){})
// 聚焦
$("#id").focus('input',function (){})
// 开始输入
$("#id").on('input',function (){})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

16. JS接收API返回值

Views

# 通过HttpResponse返回值
if request.method == 'POST':                   
    msg = {
    	pass_value = 'hello'
    }   
    return HttpResponse(json.dumps(msg), content_type="application/json")          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

JQuery

<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
    $.ajax({
        type:"post",
        url :"",
        data:{"":""},
        success: function (msg){
            alert(msg['pass_value')
        }
    })
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

17. Safari圆角失效

<!-- 在外层添加样式,解决Safari圆角加载动画后失效问题 -->
style = "-webkit-transform:rotate(0deg)" 
  • 1
  • 2

18. 禁止按钮

<!-- 禁止按钮 -->
$("#button").attr('disabled', 'disabled') 
<!-- 开启按钮 -->
$("#button").attr('disabled', false) 
<!-- 延迟开启按钮 -->
t = setTimeout(function () {
	$("#button").attr('disabled', false) 
}, 1200);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

五、简易API

1. 创建接口函数

# views.py 定义接口
from django.shortcuts import render
from django.http.response import HttpResponse
import json

# def playmusic(requst):
#     if requst.method == 'GET':
#         result = {}
#         musicNFT = requst.GET.get('musicNFT')
#         result['musicNFT'] = musicNFT
#         result = json.dumps(result)
#         return HttpResponse(result)
#     else:
#         return render(requst,'playmusic.html')

def playmusic(requst):
    if requst.method == 'POST':
        result = {}
        musicNFT = requst.GET.get('musicNFT'))
        result['musicNFT'] = musicNFT
        result = json.dumps(result)
        return HttpResponse(result)
    else: 
    	# 此处可对返回值做自定义函数处理
        return render(requst,'playmusic.html')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2. 创建Urls配置

# setting.py 定义接口
from django.contrib import admin
from django.urls import path

urlpatterns = [
    ...
    path('playmusic/', views.playmusic),
    ...
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3. 前端页面

  1. 创建基础页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>login</title>
    </head>
    <body>
    <form action="/playmusic" method="POSTS">
        <h1>用户名:<input name="musicNFT"></h1>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  2. 用户登录展示

    {% if user.is_authenticated %}
    <span> {{user.username}} </span>
    {% endif %}
    
    • 1
    • 2
    • 3

4. API交互调试

  1. 运行网页服务
    # project/terminal输入
    python3.8 manage.py
    
    • 1
    • 2
  2. 用户输入内容提交
    asj!~fh%$#@#!
    
    • 1
  3. 服务器反馈
    [xx/Jun/2022 14:20:26] "GET /xxxxx = HTTP/1.1" 3xx x
    
    • 1
  4. 退出网页服务
    # 退出项目 
    control + c
    
    • 1
    • 2

5. Request

  1. request.user
    返回用户登录名
    用户没有登陆的时返回AnonymousUser(匿名用户)
    
    • 1
    • 2
  2. request.session
    作用:
    session里设置数值,便于日后访问网页时做判断
    
    方法: 
    1. 通过request.session[name]=value 【设置数值】
    2. 通过request.session.get(name)   【读取数值】
    3. 通过request.seesion.set_expire(value)【过期】
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

6. 收藏功能(实例)

  1. 实现路径

    // 想做一个音乐收藏功能,搞了好几天,终于搞定
    // Ugly but it works ... 
    
    1. Sqlite数据存储 (ADD/DELETE)
    2. DjangoAPI (POST/GET)
    3. HTML前端设计 (UI/SVG)
    4. AJAX交互 (传参/.CSS()/刷新)
    5. 用户体验优化 (卡顿/for循环排序...)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  2. model.py

    # 建立收藏数据模型
    class Favorite(models.Model):
        from datetime import datetime
        user_id = models.ForeignKey(to=User, on_delete=models.CASCADE)   # 谁收藏
        song_id = models.ForeignKey(to=Music, on_delete=models.CASCADE)  # 收藏了哪首
        collectdate = models.DateTimeField(default=datetime.now)         # 收藏时间
        class Meta:
            verbose_name = _(u'Favorite')
            verbose_name_plural = _(u'Favorite')
            ordering=['-collectdate']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  3. migrate

    # 一系列操作... 大致如下:
    # 通知admin
    1. admin.site.register(Favorite)
    # 数据库建档迁移
    2. makemigrations & migrate
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  4. view.py

    @already_login 												  # 要求用户登录
    def handle(request):
        import json
        from MusicDatabase.models import Favorite
        from datetime import datetime
        user_id = request.user.id     							  # 调取正在收藏的用户信息
        if request.method == 'POST':
            song_id = request.POST.get('song_id')				  # 刚才收藏的歌曲id
            request.session["song_id"] = song_id				  # 记录刚才收藏的歌曲id备用
            favorites = Favorite.objects.filter(user_id=user_id)  # 找出所有该用户的收藏
            if favorites.filter(song_id=song_id):                 # 检查是否已收藏
                favorites.filter(song_id=song_id).delete()        # 若已收藏夹则取消收藏
                request.session["result"] = {					  # 返回参数
                    "collect":False,							  # 歌曲最终未被收藏
                    "song_id":song_id							  # 被收藏的歌曲
                }
                print('[取消收藏]')
            else:
                collection = Favorite()                           
                collection.user_id_id = user_id					  # 刚才收藏的用户id
                collection.song_id_id = song_id					  # 刚才收藏的歌曲id
                collection.collectdate = datetime.now()			  # 刚才收藏的时间
                collection.save()  								  # 记录收藏信息
                request.session["result"] = {
                    "collect": True,
                    "song_id": song_id
                }
                print('[新增收藏]')
            result = json.dumps({"":""}) 						  # 必须正确返回json后进入GET
            return HttpResponse(result, content_type="application/json")
    
        elif request.method == 'GET':         					  # 返回GET结果
            result = json.dumps(request.session["result"])		
            return HttpResponse(result,content_type="application/json")
        else:													  # 请求类型检查(大小写/拼写)
        	print('request method error')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  5. SVG绘制与CSS

    {#  ❤️ }
    {% for song in msg %}
    <style> 
        svg {
            cursor: pointer;
            overflow: visible;
            width: 36px;
            fill:#AAB8C2;
            fill-rule: evenodd;
        }
    </style>
    <svg id="{{ song.id }}" viewBox="467 392 58 57" xmlns="http://www.w3.org/2000/svg" >
       <g transform="translate(467 392)">
           <path d="M29.144 20.773c-.063-.13-4.227-8.67-11.44-2.59C7.63 28.795 28.94 43.256 29.143 43.394c.204-.138 21.513-14.6 11.44-25.213-7.214-6.08-11.377 2.46-11.44 2.59z" id="{{ song.id }}" />
       </g>
    </svg>
    {% endfor %}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  6. jQuery - Ajax 交互

    // 为避免混乱,POST收藏操作成功获得“返回参数”后再继续GET
    <script src="../static/js/jquery-3.5.1.min.js"></script>
    <script>
        $(function (){ 
            $("#Tag").click(function (){            // 收藏按钮点击后操作
                $.ajax({							// 发送操作POST
                    type     : "post",				// 小写
                    url      : post_url,
                    data     : {"key": "value"},
                    dataType : "json",
                    success:function () {			// 成功返参后获取页面收藏信息GET
                        $.ajax({
                            type : "get",			// 小写
                            url  : get_url,
                            data: "",
                            success:function (data) {
                                if (data["collect"] == false){               // 若取消收藏
                                    if (data["song_id"]=={{ song.id }}) {	 // 通过id锁定  
                                        $('#{{ song.id }}')
                                        	.css({fill: "#AAB8C2", })		 // 灰色爱心			
                                    }
                                }
                                else {										
                                    if (data["collect"] == true) {			 // 若新增收藏
                                        if (data["song_id"]=={{ song.id }}){ // 通过id锁定  
                                            $('#{{ song.id }}')
                                                .css({fill:"#E2264D",})      // 红色爱心
                                        }
                                    }
                                    else { alert('ERROR') }
                                }
                            }
                        })
                    }
                });
            })
    
            //<进入页面时刷新收藏状态>
            $(function (){
                var li = $({{ fav }}) 							// 列表一定要$()进行obejct化
                for (var i=0;i<li.length;i++)
                	{
                    	if ({{song.id}} == li[i]) {
                    	$("#{{ song.id }}")
                        .css({ fill:"#E2264D",})
                	}
                }
            })
        })
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

7. 用户行为日志

  1. 功能描述

    通过"中间件"记录用户数据日志
    自定义exclude_urls列表访问列表中的url
    通过设置的响应时间阈值(可配置化)
    将超过阈值的操作日志进行单独保存
    
    • 1
    • 2
    • 3
    • 4
  2. 创建中间件

    (app-item)/middlewares/LogMiddleware.py
    
    • 1
  3. setting.py

    // 自定义中间件
    MIDDLEWARE += [
    'app01.middlewares.LogMiddleware.OpLogs'
    ]
    
    • 1
    • 2
    • 3
    • 4
  4. LogMiddleware.py

    import time
    import json
    from django.utils.deprecation import MiddlewareMixin
    from MusicStore.models import  AccessTimeOutLogs,OpLogs
    
    class OpLog(MiddlewareMixin):
        __exclude_urls = ['signin/','signup/','signout/']  # 无需记录日志的url名单,如:('index/')
    
        def __init__(self, *args):
            super(OpLog, self).__init__(*args)
            self.start_time = None  
            self.end_time = None    
            self.data = {}
            
        def process_request(self, request):
            self.start_time = time.time()
            re_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
    
            # 请求IP
            x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
            if x_forwarded_for:
                re_ip = x_forwarded_for.split(",")[0]  # 如果有代理,获取真实IP 
            else:
                re_ip = request.META.get('REMOTE_ADDR')
    
            # 请求方法
            re_method = request.method
    
            # 请求参数
            re_content = request.GET if re_method == 'GET' else request.POST
            if re_content:
                re_content = json.dumps(re_content)               # 筛选空参数
            else:
                re_content = None
            # 请求记录
    
            self.data.update(
                {
                    're_time'   : re_time,                        # 请求时间
                    're_url'    : request.path,                   # 请求url
                    're_method' : re_method,                      # 请求方法
                    're_ip'     : re_ip,                          # 请求IP
                    're_content': re_content,                     # 请求参数
                    're_user'   : request.user.username,          # 操作人(需修改),网站登录用户
                    # 're_user' : 'AnonymousUser'                 # 匿名用户测试
                }
            )
    
        def process_response(self, request, response):
            for url in self.__exclude_urls:                       # 无需记录页面不记录
                if url in self.data.get('re_url'):
                    return response
            # 响应内容
            rp_content = response.content.decode()                # 获取响应数据字符串(JSON字符串)
            self.data['rp_content'] = rp_content
    
            # 响应耗时
            self.end_time = time.time()  
            access_time = self.end_time - self.start_time
            self.data['access_time'] = round(access_time * 1000)  # 耗时毫秒/ms
    
            # 单独记录>3s的请求(可在settings中设置"时间阈值")
            if self.data.get('access_time') > 3 * 1000:
                AccessTimeOutLogs.objects.create(**self.data)     # 超时操作日志入库
            OpLogs.objects.create(**self.data)                    # 操作日志入库
    
            return response
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
  5. model.py

    class OpLogs(models.Model):                                              
        id          = models.AutoField(primary_key=True)                     
        re_time     = models.CharField(max_length=32, verbose_name='请求时间')   
        re_user     = models.CharField(max_length=32, verbose_name='操作人')    
        re_ip       = models.CharField(max_length=32, verbose_name='请求IP')   
        re_url      = models.CharField(max_length=255, verbose_name='请求url') 
        re_method   = models.CharField(max_length=11, verbose_name='请求方法')   
        re_content  = models.TextField(null=True, verbose_name='请求参数')       
        rp_content  = models.TextField(null=True, verbose_name='响应参数')       
        access_time = models.IntegerField(verbose_name='响应耗时/ms')            
        class Meta:                                                          
            db_table = 'op_logs'                                             
                                                                             
    class AccessTimeOutLogs(models.Model):                                   
        id          = models.AutoField(primary_key=True)                     
        re_time     = models.CharField(max_length=32, verbose_name='请求时间')   
        re_user     = models.CharField(max_length=32, verbose_name='操作人')    
        re_ip       = models.CharField(max_length=32, verbose_name='请求IP')   
        re_url      = models.CharField(max_length=255, verbose_name='请求url') 
        re_method   = models.CharField(max_length=11, verbose_name='请求方法')   
        re_content  = models.TextField(null=True, verbose_name='请求参数')       
        rp_content  = models.TextField(null=True, verbose_name='响应参数')       
        access_time = models.IntegerField(verbose_name='响应耗时/ms')            
        class Meta:                                                          
            db_table = 'access_timeout_logs'                                 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  6. migrate

    makemigrations / migrate
    
    • 1
  7. 时区更改

    # setting.py
    TIME_ZONE = 'Asia/Shanghai' # 'UTC'
    
    • 1
    • 2

8. 搜索匹配

...
  • 1

9. 购物车

...
  • 1

10. 用户上传

  1. view.py

    def fileManagerUpload(request):
        if request.method == 'POST':
            username = request.user.username      # 上传用户名
            timetag = time.strftime('%Y%m%d%m%s') # 时间标签
            try:
                myFile = request.FILES.get("myfile", None)
                # 文件内容/格式检查
                if not myFile:
                    return HttpResponse('没有要上传的文件')
                if not os.path.splitext(myFile.name)[1] in [".jpg", ".jpeg"]:  # 指定格式
                    return HttpResponse("请上传指定格式文件")
                # 建立用户专属文件夹
                paths = [os.path.join(settings.MEDIA_ROOT, f'user/'),
                         os.path.join(settings.MEDIA_ROOT, f'user/{username}/'),
                         os.path.join(settings.MEDIA_ROOT, f'user/{username}/playlist/')]
                for i in paths:
                    if not os.path.exists(i):
                        os.mkdir(i)
                # 文件保存以时间戳命名
                final_path = os.path.join(settings.MEDIA_ROOT, f'user/{username}/playlist/')
                destination = open(os.path.join(final_path, f'{timetag}.jpg'), 'wb+')
                imgurl = f'../static/media/user/{username}/playlist/{timetag}.jpg'
                # 图片地址加入服务器缓存
                session_tagadd(request, 'imgpath', imgurl, imgurl)
                # 保存图片
                for chunk in myFile.chunks():
                    destination.write(chunk)
                destination.close()
                # 进度提示
                hint = '图片上传成功!'
                session_tagadd(request, 'imghint', hint, '')
                return HttpResponse(hint)
            except:
                hint = '上传失败,请重新上传'
                session_tagadd(request, 'imghint', hint, '')
                return HttpResponse(hint)
        else:
            return HttpResponse('')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  2. urls.py

    path('fileManagerUpload/', fileManagerUpload,name="fileManagerUpload"),
    
    • 1
  3. setting.py

    MEDIA_ROOT = os.path.join(BASE_DIR,'(app-item)/(temp)')
    
    • 1
  4. html

    <p id="progressId">上传进度</p>
    
    <script src="../static/js/jquery-3.5.1.min.js"></script>
    <script>
    	var fileChoose = document.getElementById("file-upload");
      	fileChoose.onchange = function() {
            var file = this.files[0]; // files[0]DOM对象
            
            // 文件限制大小检查
    	    if (file.size > 1024 * 1024 * 100) { //100M
    	        alert("上传文件不能超过100M");
    	    }
    
            var reader = new FileReader(); // 实例化FileReader
            reader.readAsDataURL(file);    // 将文件对象转化为路径对象
            reader.onload = function () {  // 打开文件
                var imgEle = document.getElementById("avatar"); // 图片框id
                imgEle.src = this.result // 这里的this指reader对象
            }
            var fileObject = document.getElementById("file-upload").files[0]; // 拿到图片文件
            //实例化FormData对象,添加数据 data.append(key, value)
            var data = new FormData();
            data.append('myfile', fileObject);
            data.append('filename', $("#titleinfo").val());
            $.ajax({
                url: '{% url 'fileManagerUpload' %}',
                type: 'post',
                data: data,
                processData: false, //不进行转码或预处理
                contentType: false, //不进行"application/x-www-form-urlencoded"的默认编码处理
                success: function () {
                    $("#imghint").text("* 上传成功!")
                },
                error: function (){
                    $("#imghint").text("* 上传失败,请重新上传")
                },
                
                // 获得用户上传进度
                xhr: function(){ // 获取ajaxSettings中的xhr对象,为它的upload属性绑定progress事件的处理函数
    				var myXhr = $.ajaxSettings.xhr();
    				if(myXhr.upload){ // 检查upload属性是否存在
    	        		// 绑定progress事件的回调函数
    	        		$('#waterprogressId').text(); //清空
    	        		myXhr.upload.addEventListener('progress', function(e){
    	                	if (e.lengthComputable){
    	                    	var percent = "上传进度:" + e.loaded/e.total*100 + "%";
    	                    	$('#waterprogressId').text(percent);
    	                	}
    	            	},
            				false);
    				}	
    		    	return myXhr; //xhr对象返回给jQuery使用
    			}      
    		}),
    		// 清除上传信息缓存,保证同名文件也可以上传
    		document.getElementById('file-upload').value = ''
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

11. 用户下载

[方法 A]
views

def file_download(request):
	filename = '下载显示的文件名'
	try:
     	from django.utils.encoding import escape_uri_path
        signed_filename = os.path.basename(file_path)
        response = FileResponse(open(file_path, 'rb'))
        response['content_type'] = "application/octet-stream"
        response['Content-Disposition'] = "attachment; filename*=utf-8''{}".format(escape_uri_path(filename))  # 可支持中文及大文件下载
        return response
	except Exception:
        raise Http404
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

html

 <button href="{% url 'file_download' %}" id="">下载文件</button>
  • 1

[方法 B]
js

<a id="" href="" Download=""><button>Download</button></a>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
    $.ajax({
        type : "get",
        url  : "{% url '' %}",
        data : {
            "xx":"",
        },
        success: function(data){
            var filelink = data.toString()
            $("#midi_return").attr("href", filelink)
        },
        error: function(){
            alert('获取失败')
        },
    });
</script>	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

12. 用户读取

# setting.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'xx/media')
  • 1
  • 2
  • 3
# url.py
from django.conf import settings
from django.urls import re_path
from django.views.static import serve

urlpatterns = [ ...
    re_path(r'media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
# 查看文件夹
import os
def data_view(request):
    files = [i for i in os.listdir(settings.MEDIA_ROOT)  if not i.startswith('.')]
    result = {"files": files}
    return HttpResponse(json.dumps(result))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
# 页面查看或下载
https://xxx.com/media/xxx.pdf
  • 1
  • 2

13. 用户读写权限

Linux服务器用户文件创建或上传需要打开权限

1. 报错:[Errno 13] Permission denied
2. 原因:上层文件夹缺少用户写入权限
3. 解决:至少建立一个公开读写文件夹并更改文件夹权限
		(app-item)/media ->(777)
  • 1
  • 2
  • 3
  • 4

常用权限代码参考

-rw------- (600) 只有所有者才有读和写的权限
-rwx------ (700) 只有所有者才有读,写,执行的权限
-rwxr-xr-x (755) 只有所有者才有读,写,执行的权限,同组用户和其他用户只有读和执行的权限
-rwx--x--x (711) 只有所有者才有读,写,执行的权限,同组用户和其他用户只有执行的权限
-rw-rw-rw- (666) 每个人都有读写的权限
-rw-rw-r-- (664) 所有者的权限为可读可写不可执行、所属群组可读可写不可执行、其他人可读不可写不可执行
-rwxrwxrwx (777) 每个人都有读写和执行的权限
-rwxrwx--- (770) 所有者和同组用户有读、写及执行权限,其他用户组没任何权限。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

14. User模型扩展

# (app-item)/model.py
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    wechat = models.CharField(max_length=25, null=True,verbose_name="wechat")
    phone  = models.CharField(max_length=25, null=True,verbose_name="phone")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
# setting.py
INSTALLED_APPS = [ ...
    '(App-item)',
]

AUTH_USER_MODEL = '(App-item).User'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
$ python3.8 manage.py makemigrations
$ python3.8 manage.py migrate
  • 1
  • 2
# (app-item)/admin.py
admin.site.register(User)
  • 1
  • 2
# (app-item)/view.py
# 方案一
from django.contrib.auth import get_user_model
User = get_user_model()
# 方案二
from django.contrib.auth.models import User 
from (app-item).models import User as User 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

15. 笔记若干

1. Serializers

# 对于objects.all()/objects.filter()
# 必须进行序列化后方可进入json转化
		
from django.core import serializers
data = Music.objects.all()
json_data = serializers.serialize("json", data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2. json标准格式化

result = {"key":"vlaue"}
result = json.dumps(result)
return HttpResponse(result, content_type="application/json")
  • 1
  • 2
  • 3

3. urllib中文地址解码

from urllib import parse 
username = parse.unquote(request.get_full_path().split('')[1])
  • 1
  • 2

4. DateTimeField 报错

# 问题报错:
RuntimeWarning: DateTimeField Draw.drawdate received a naive datetime 

# 解决方法:
setting.py
USE_TZ  = False  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5. 服务器日志打印输出

import logging
logger = logging.getLogger('django')
logger.error('Something went wrong!')
  • 1
  • 2
  • 3

16. 支付API

1. Paypal

1. 注册账号(需要注册bussiness账号)
2. 登录开发者页面 developer.paypal.com
3. 选择沙盒账号 sandbox account
4. 选择 default application 
5. 查看并复制client_id与client_secret
6. 选择个人账号进行测试,复制沙盒个人账号email
7. pip3 install paypalrestsdk
8. setting      
9. View 
10. Html
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

setting.py

INSTALLED_APPS = [ 
   	...
   	'paypalrestsdk',
   	... 
] 
# 基于沙箱环境                                                   
PAYPAL_TEST = True                                         
# 设置收款的 PayPal 电子邮件账户                                      
PAYPAL_REVEIVER_EMAIL = "xxx@xxx.com" 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

view.py

paypal_client_id = ''
paypal_client_secret = ''

def paypal_refund(requset):
    # 退款
    from paypalrestsdk import Sale

    sale = Sale.find("流水号")

    # Make Refund API call
    # Set amount only if the refund is partial
    refund = sale.refund({
        "amount": {
            "total": "0.01",
            "currency": "USD"}})

    # Check refund status
    if refund.success():
        print("Refund[%s] Success" % (refund.id))
    else:
        print("Unable to Refund")
        print(refund.error)


def paypal_execute(request):
    if request.method == 'GET':
        import paypalrestsdk
        paymentid = request.GET.get("paymentId")  # 订单id
        payerid = request.GET.get("payerId")  # 支付者id
        payment = paypalrestsdk.Payment.find(paymentid)

        if payment.execute({"payer_id": payerid}):
            print("Payment execute successfully")
            return HttpResponse("支付成功")
        else:
            print(payment.error)  # Error Hash
            return HttpResponse("支付失败")


def paypal_payment(request):
    import paypalrestsdk
    paypalrestsdk.configure({
        "mode": "sandbox",  # sandbox代表沙盒/live代表真实付款
        "client_id": paypal_client_id,
        "client_secret": paypal_client_secret,
    })

    payment = paypalrestsdk.Payment({
        "intent": "sale",
        "payer": {
            "payment_method": "paypal"},
        "redirect_urls": {
            "return_url": "http://localhost:8000/paypal/pay",  # 支付成功跳转页面
            "cancel_url": "http://localhost:8000/paypal/cancel/"},  # 取消支付页面
        "transactions": [{
            "amount": {
                "total": "0.01",
                "currency": "USD"},
            "description": "test_real"}]})

    if payment.create():
        print("Payment created successfully")
        for link in payment.links:
            if link.rel == "approval_url":
                approval_url = str(link.href)
                print("Redirect for approval: %s" % (approval_url))
                return redirect(approval_url)
    else:
        print(payment.error)
        return HttpResponse("支付失败")


def paypal_pay(request):
	# 用户支付完返paypal返回内容
    back_url = request.get_full_path()
    if 'paymentId=' and 'token=' and 'PayerID=' in back_url:
        paymentid = back_url.split('paymentId=')[1].split('&token=')[0]
        token = back_url.split('&token=')[1].split('&PayerID=')[0]
        payerid = back_url.split('PayerID=')[1]
        pay = {
            'paymentid': paymentid,
            'token': token,
            'payerid': payerid,
        }
    else:
        pay = {}
    return render(request, 'paypal_pay.html', pay)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

html

<button id="paypal_pay">Pay</button>
<button id="paypal_back">Finish</button>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
    // 1. 用户点击支付
    $("#paypal_pay").click(function () {
        $.ajax({
            type: 'post',
            url: '{% url 'paypal_payment' %}',
            data: {},
            success: function (data) {
                $("#paypal_back").show()
            }
        })
    })

    // 2. 用户前往paypal支付完后,回调函数确认
    $("#paypal_back").click(function () {
        $.ajax({
            type: 'get',
            url: '{% url 'paypal_execute' %}',
            data: {
                'paymentId': '{{ paymentid }}',
                'payerId': '{{ payerid }}',
            },
            success: function (data) {
				window.location.href = '{% url '' %}'
            }
        })
    })
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

17. 后台每隔一段时间执行任务

1.安装Celery

#1. 安装celery
pip3.8 install celery

#2. 安装后端redis
brew install redis

#3. 设置开机自动启动
brew services start redis
# brew services stop redis 关闭
# brew services restart redis 重启

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2. 创建Celery实例

# 在你的Django项目目录下,通常在同settings.py文件的同一级目录中,
# 创建一个新的celery.py文件。
# 在celery.py中,定义你的Celery实例:

import os
from celery import Celery
from django.conf import settings

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project_name.settings')

app = Celery('your_project_name')

app.autodiscover_tasks(['your_app_name'])

app.config_from_object('django.conf:settings', namespace='CELERY')

app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3. 修改Django的__init__.py文件

from .celery import app as celery_app

__all__ = ('celery_app',)

  • 1
  • 2
  • 3
  • 4

4. 配置Celery

# 在Django的settings.py中,添加Celery的配置。
# 以下是一个使用Redis作为消息代理的基本示例:

CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'UTC'

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5. 定义Celery任务

# 在你的Django应用目录下(例如myapp)
# 创建或编辑tasks.py文件,并定义你的任务。

from celery import shared_task

@shared_task
def print_one():
    print(1)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

6. 配置定时任务

# 要定期执行任务,你需要为Celery Beat配置一个调度器
# 在settings.py中添加以下代码:

from datetime import timedelta
CELERY_BEAT_SCHEDULE = {
    'print-one-every-hour': {
        'task': 'your_app_name.tasks.print_one',
        'schedule': timedelta(hours=1),
    },
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

7. 运行Celery Worker和Celery Beat

  1. terminal 执行

    # 在命令行中,从你的Django项目目录运行以下两个命令
    #(每个命令需要在不同的终端中运行)
    
    # 启动worker
    celery -A your_project_name worker --loglevel=info
    #启动beat
    celery -A your_project_name beat --loglevel=info
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  2. 启动Django后自动执行

    在project下新建一个start_service.py文件
    随后将以下代码写进去
    
    • 1
    • 2
    #!/usr/bin/env python3
    import signal
    import sys, os
    
    def stop_redis(signal_received, frame):
        """终止Redis的处理器函数"""
        print('正在停止Redis...')
        os.system('pkill redis-server')
    
        print('正在停止Celery Worker...')
        os.system('pkill -f "celery -A _keenthemes worker"')
    
        print('正在停止Celery Beat...')
        os.system('pkill -f "celery -A _keenthemes beat"')
    
        sys.exit(0)
    
    os.system('pkill redis-server')
    os.system('pkill -f "celery -A _keenthemes worker"')
    os.system('pkill -f "celery -A _keenthemes beat"')
    
    # 设置捕获SIGINT和SIGTERM的处理器
    signal.signal(signal.SIGINT, stop_redis)
    signal.signal(signal.SIGTERM, stop_redis)
    
    # 启动Redis
    os.system("redis-server &")
    os.system("celery -A _keenthemes worker --loglevel=info &")
    os.system("celery -A _keenthemes beat --loglevel=info &")
    
    while True:
        signal.pause()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    # Terminal中运行代码
    # 授权权限
    chmod +x start_service.py 
    # 执行后台
    ./start_service.py
    
    • 1
    • 2
    • 3
    • 4
    • 5

六、常用工具

1. 自动文档生成

	# tools.py
	def draw_image():
	    import os.path
	    from PIL import Image, ImageDraw, ImageFont
	    from django.conf import settings
	    width,height = 794,1123  # A4大小
	    album_title  = "授权书"
	    album_title  = enlarge_fontdistance(album_title)
	    
	    # 样式设置 (⚠️ 字体在服务器上需要预先安装)
	    bg_color    = '#F5F5F5' # 背景色                                                         
	    fontsize    = [30]# 字体大小
	    fontcolor   = ['#?????']# 字体颜色
	    fontname    = ['?.ttf']# 字体样式        
	    words       = ['..']# 文字内容
	    bocname     = '../png'
	    bocpath     = os.path.join(bocfldpath, bocname)                                                   			
	    bgimg_path  = '../.png' # 背景图片
	    img_path    = os.path.join(settings.BASE_DIR,bgimg_path)
	    img 	    = Image.open(img_path)
	
	    for t in range(len(words)):
	        # 文字内容
	        word = words[t]
	
	        # 样式应用
	        font = ImageFont.truetype(fontname[t], fontsize[t])
	        text_coordinate = (250, int(width / 2 - width / 2.5) + t)
	            
	        # 合成图片
	        img_draw = ImageDraw.Draw(img)
	        img.save(bocpath, quality=100)
	
	        # 合成文字 (文字一层层覆盖图片)
	        img_draw.text(text_coordinate, word, font=font, fill=fontcolor[t])
	        img.save(bocpath, quality=100)
	    return bocpath
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
	# view.py
	draw_image(
	    bocfldpath = userlicensefldpath ,
	    ...
	    timenow    = timenow,
	)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2. 文件压缩

	# tool.py
	def file2zip(zip_file, source_file):
	    import zipfile,os
	    with zipfile.ZipFile(zip_file, mode='w', compression=zipfile.ZIP_DEFLATED) as zip:
	        files = [os.path.join(source_file, i) for i in os.listdir(source_file) if not i.startswith('.')]
	        for file in files:
	            parent_path,name = os.path.split(file)
	            zip.write(file, name)
	        zip.close()
	    return zip_file
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
	# view.py
	zipnames = [f'?.zip']
	source_files = [xpath]
	for z in range(len(zipnames)):
	    zipath = os.path.join(settings.BASE_DIR,xpath)
	    path = file2zip(
	        zip_file = os.path.join(zipath,zipnames[z]),
	        source_file = source_files[z],
	    )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3. 身份证验证

	# -*- coding: utf-8 -*-
	
	def checkIdcard(idcard):
	    import re
	    Errors = {'error_msg': '* 身份证号码输入不正确!'}
	    area = {"11": "北京", "12": "天津", "13": "河北", "14": "山西", "15": "内蒙古", "21": "辽宁", "22": "吉林", "23": "黑龙江",
	            "31": "上海", "32": "江苏", "33": "浙江", "34": "安徽", "35": "福建", "36": "江西", "37": "山东", "41": "河南", "42": "湖北",
	            "43": "湖南", "44": "广东", "45": "广西", "46": "海南", "50": "重庆", "51": "四川", "52": "贵州", "53": "云南", "54": "西藏",
	            "61": "陕西", "62": "甘肃", "63": "青海", "64": "宁夏", "65": "新疆", "71": "台湾", "81": "香港", "82": "澳门", "91": "国外"}
	    idcard = str(idcard)
	    idcard = idcard.strip()
	    idcard_list = list(idcard)
	
	    # 15位身份号码检测
	    if (len(idcard) == 15):
	        if ((int(idcard[6:8]) + 1900) % 4 == 0 or (
	                (int(idcard[6:8]) + 1900) % 100 == 0 and (int(idcard[6:8]) + 1900) % 4 == 0)):
	            ereg = re.compile(
	                '[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$')  # //测试出生日期的合法性
	        else:
	            ereg = re.compile(
	                '[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$')  # //测试出生日期的合法性
	        if (re.match(ereg, idcard)):
	            return(Errors)
	        else:
	            return(Errors)
	
	    # 18位身份号码检测
	    elif (len(idcard) == 18):
	        # 地区校验
	        try:
	            area[(idcard)[0:2]]
	        except:
	            return (Errors)
	
	        # 出生日校验
	        if (int(idcard[6:10]) % 4 == 0 or (int(idcard[6:10]) % 100 == 0 and int(idcard[6:10]) % 4 == 0)):
	            ereg = re.compile(
	                '[1-9][0-9]{5}(19[0-9]{2}|20[0-9]{2})((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}[0-9Xx]$')  # //闰年出生日期的合法性正则表达式
	        else:
	            ereg = re.compile(
	                '[1-9][0-9]{5}(19[0-9]{2}|20[0-9]{2})((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}[0-9Xx]$')  # //平年出生日期的合法性正则表达式
	
	        # 出生日期的合法性
	        if (re.match(ereg, idcard)):
	            # 计算校验位
	            S = (int(idcard_list[0]) + int(idcard_list[10])) * 7 + (int(idcard_list[1]) + int(idcard_list[11])) * 9 + (
	                    int(idcard_list[2]) + int(idcard_list[12])) * 10 + (
	                        int(idcard_list[3]) + int(idcard_list[13])) * 5 + (
	                        int(idcard_list[4]) + int(idcard_list[14])) * 8 + (
	                        int(idcard_list[5]) + int(idcard_list[15])) * 4 + (
	                        int(idcard_list[6]) + int(idcard_list[16])) * 2 + int(idcard_list[7]) * 1 + int(
	                idcard_list[8]) * 6 + int(idcard_list[9]) * 3
	            Y = S % 11
	            M = "F"
	            JYM = "10X98765432"
	            M = JYM[Y]  # 判断校验位
	            if (M == idcard_list[17]):  # 检测ID的校验位
	                region = area[(idcard)[0:2]]
	                year = idcard[6:10]
	                month = idcard[10:12]
	                day = idcard[12:14]
	                if int(idcard[16]) % 2 == 0:
	                    sex = '女'
	                else:
	                    sex = '男'
	                print('[* 验证通过 *]')
	                print(f'性别:{sex}')
	                print(f'地区:{region}')
	                print(f'出生日期:{year}{month}{day}日')
	                return(
	                        True,
	                        {
	                          'region' : region ,
	                          'year'   : year   ,
	                          'month'  : month  ,
	                          'day'    : day    ,
	                          'sex'    : sex    ,
	                        }
	                      )
	            else:
	                return(False,Errors)
	        else:
	            return(False,Errors)
	    else:
	        return(False,Errors)
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

4. 电话号码验证

	class phoneVertificate():
	    def __init__(self):
	        # 移动:
	        self.hd_yd = [139, 138, 137, 136, 135, 134, 147, 150, 151, 152, 157, 158, 159, 178, 182, 183, 184, 187, 188]
	        # 联通:
	        self.hd_lt = [130, 131, 132, 155, 156, 185, 186, 145, 176]
	        # 电信:
	        self.hd_dx = [133, 153, 177, 173, 180, 181, 189]
	        # 虚拟运营商:
	        self.hd_xn = [170, 171]
	
	        self.hd_name = [self.hd_yd, self.hd_lt, self.hd_dx, self.hd_xn]
	        self.hd_strnm = ['中国移动', '中国联通', '中国电信', '虚拟运营商']
	        self.hd_all = []
	        self.mobile3All()
	
	    def mobile3All(self):
	        for yys in self.hd_name:
	            self.hd_all.extend(yys)
	
	    def verificate(self, mobile: str):
	        if len(mobile) != 11:
	            msg = {
	                'error_msg': '手机号码输入不正确',
	                'history': mobile
	            }
	            return (False,msg)
	        try:
	            hd, wh = int(mobile[:3]), int(mobile[3:])
	        except Exception as err:
	            print(mobile, str(err))
	        else:
	            if hd not in self.hd_all:
	                msg = {
	                   'error_msg': '手机号码输入不正确',
	                    'history' : mobile
	                }
	                return (False,msg)
	            else:
	                operation = ''
	                for i in range(len(self.hd_name)):
	                    if hd in self.hd_name[i]:
	                        operation = self.hd_strnm[i]
	                        break
	                print(f'验证成功:[{operation}] {mobile}')
	                msg = {
	                    'operation': operation,
	                    'history'   : mobile
	                }
	            return (True,msg)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

5. 密码设置规则

规则条件:密码必须包含字母与数字,8<密码长度<20位

<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
	$("#password").on('input',function(){
		var reg = new RegExp(/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,20}$/);
		var idValue = $("#password").val()
		if(reg.test(idValue)){
			...
		}
	})
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

也可在html的input里面对最大长度限制

<input type="tel" maxlength="20" >
  • 1

七、服务器搭建

1. 阿里云服务

  1. 购买云服务器
    	# 阿里云购买轻量服务器
    	地址: https://www.aliyun.com/
    	配置: 2- 1GB内存 - 系统盘 40GB ESSD - 上海
    	价格: 80/月
    	镜像:  Linux CentOS 8.2
    
    • 1
    • 2
    • 3
    • 4
    • 5
  2. 云服务器初始化
    	# 阿里云购买轻量服务器
    	1. 进入阿里云控制台
    	2. 设定管理员密码
    	3. 查看公网/内网地址 (可外网访问)
    	4. 防火墙打开:8000(Django)/8888(宝塔)等端口
    	5. 开启服务器
    	# 4. 硬盘挂载
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

2. 腾讯云服务

  1. 购买云服务器
    
    
    • 1
  2. 云服务器初始化
    	
    
    • 1

3. 亚马逊云服务

  1. 购买云服务器

    1. 注册AWS
    2. 完成Billing Account 信用卡信息
    3. 选择免费服务
    4. EC2 创建实例(Linux 免费)
    5. 扩充空间 EC2-Elastic Block Store 原始(8G)->(20G)
    6. 搜索ssl并添加负载均衡 (参考文章:亚马逊云的服务器(EC2)+阿里云的域名,添加ssl证书)
    7. 复制CNAME和VALUE用于域名解析
    8. Connect连接实例
    9. 安装宝塔
    10. Security Group安全组增加访问端口
    11. 登录面板
    10.安装python管理器2.0 
       由于宝塔仅支持Centos系统,亚马逊是AmazonLinux
       难免不兼容,所以不同版本多装几次(2.1版本经常报错)
       我的顺序是3.79->3.71->3.85...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  2. 宝塔安装

    1. 打开所有端口
    2.远程登录,输入sudo -i
    3.yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
    
    • 1
    • 2
    • 3
  3. SSH 登录

    1、连接EC2服务器
    2、sudo passwd root # 创建root的密码,输入
    3、******  # 提示输入new password。输入一个你要设置的root的密码再验证
    4、su root # 接下来,切换到root身份,输入
    5、vim /etc/ssh/sshd_config 
       < PasswordAuthentication yes | PermitRootLogin yes > 
       # shell 输入按“i”键
       # shell 保存退出按“esc" + ":" + "wq" + "enter"
       # 使用root身份编辑亚马逊云主机的ssh登录方式找到PasswordAuthentication no,把			no改成yes。PermitRootLogin这行改为PermitRootLogin yes//设定是否允许root管理员直接登录输入:`vim /etc/ssh/sshd_config`
    6、systemctl restart sshd  # 重启 sshd 服务
    7、systemctl enable sshd   # 将 sshd 服务加入到开机启动项中
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    * SHA256 pub/private key ==========
    要将SSH密钥添加到Google云平台(GCP)上的实例,你可以按照以下步骤进行操作:
    在本地计算机上生成SSH密钥对。如果你已经有SSH密钥对,可以跳过此步骤。否则,可以使用以下命令生成密钥对:
    	
    1. ssh-keygen -t rsa -b 4096 -C "your_email@example.com" # 这将生成一个RSA类型的4096位密钥对,并将其关联到你的电子邮件地址。
    2. Compute Engine => VM instances Edit => SSH => Additem => copy PublicKey(cat ~/.ssh/id_rsa.pub)=>Save
    3. 打开本地Privatekey复制
    4. 输入host,加载privatekey文件,端口22 => 设置密码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  4. 宝塔Django报错:
    问题:Django - deterministic=True requires SQLite 3.8.3 or higher upon running python manage.py runserver

    1. 安装pysplite3
    	# 非虚拟环境
    	pip3 install pysqlite3
    	pip3 install pysqlite3-binary
    	# 虚拟环境
    	请把pip3替换成: 项目路径/md5命名的文件夹/bin/pip
    	如:/data/python/d9036cc6563924cf9e1da4e1cd64f9a4_venv/bin/pip install pysqlite3
    	
    2. 找到报错提示base路径
    	如:python3.x/site-packages/django/db/backends/sqlite3/base.py
    	
    3. 修改替换pysqlite3
    	# from sqlite3 import dbapi2 as Database # annotation
    	from pysqlite3 import dbapi2 as Database # import pysqlite3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

4. Google服务器

1. 购买云服务器

2. 宝塔安装

3. 防火墙设置

请添加图片描述
请添加图片描述

4. 其它

	* GoogleCloud删除Project时一定先关闭instance和billing
	* 若误删Project,7日内可去IAM&Admin中恢复
  • 1
  • 2

请添加图片描述

5. 宝塔不使用"Python管理器"直接部署方案

	====== Linux编译安装python ======
	
	1.在liunx上安装python运行环境
	yum -y install gcc
	yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel
	
	2.进入到安装目录
	cd /usr/local/
	# /www/server/panel/pyenv/lib/
	
	3.下载python3.8的安装包 (可能会有点慢)
	wget https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tgz
	
	4.解压安装包
	tar -zxvf Python-3.8.1.tgz
	
	5.配置python环境以及安装编译python
	1) mkdir /usr/local/python3 
	   # /www/server/panel/pyenv/lib/python3.8
	2) cd Python-3.8.1
	3) ./configure --prefix=/usr/local/python3
		# /www/server/panel/pyenv/lib/python3.8
	4) make && make install
	
	6.确认安装成功
	/usr/local/python3/bin/python3.8
	
	会出现:
				Python 3.8.1 (default, Jun  6 2022, 11:01:13) 
				[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
				Type "help", "copyright", "credits" or "license" for more information.
				
	>>> print("Hello")
	Hello
	>>> exit()
	表示安装成功~
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
	====== 运行Django ======
	1. 安装Django
	   /usr/local/python3/bin/python3 install django
	2. 安装其他需要组件module
	3. 前往project所在目标地址
	   cd /www/wwwroot/hellojune
	4. /usr/local/python3/bin/python3 manage.py runserver 0.0.0.0:80 --insecure
	5. 若希望一直挂载开启服务器运行Django
	   nohup /usr/local/python3/bin/python3 manage.py runserver 0.0.0.0:80 --insecure  > django.log 2>&1 &  
	   * Terminal返回一串终端号,如:[5149] 记住然后停止时kill
	   * > django.log 2>&1 &  用于替换之前nohup.out log位置
	   * 结束Django nohup使用 kill 5149(之前创建nohup时提示的PID) 
	   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4. Web端运维

  1. MacOS安装 Royal-TSX

    1. 使用Royal-TSX代替XShell 远程连接服务器SSH/FTPS
    2. Plugins添加Terminal/File Transfer两款插件
    3. 新建一个Terminal窗口并输入对应的服务器的信息
    4. 指定用户名和密码
    5. 完成登录
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述注:Royal-TSX 图片内容均引用自作者zoiiiiii

  2. 服务器下载宝塔脚本(CentOS)

    yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec	
    
    • 1
  3. yum报错处理

    1. 存在问题

      Question: [服务器yum下载报错]
      - Errors during downloading metadata for repository ‘appstream’:
      - Status code: 404 for ... Error: Failed to download metadata 
      - for repo ‘appstream’: Cannot download repomd.xml: Cannot 
      - download repodata/repomd.xml: All mirrors were tried
      ...
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. 替换数据源

      cd /etc/yum.repos.d
      mv CentOS-Linux-BaseOS.repo CentOS-Linux-BaseOS.repo.backup 
      wget -O CentOS-LinuxBaseOS.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo 
      
      • 1
      • 2
      • 3
    3. vim修改文件

      vim /etc/yum.repos.d/CentOS-Linux-AppStream.repo
      
      • 1
    4. 替换baseurl数据源地址

      baseurl=http://mirrors.cloud.aliyuncs.com/centos-vault/8.5.2111/AppStream/$basearch/os/
      
      • 1
    5. 重新创建元数据

      yum makecache
      
      • 1
    6. 安装成功
      在这里插入图片描述

  4. 宝塔Web端登录

    1. MacOS浏览器中输入服务器中提示的登录信息
    2. 注册宝塔会员并一键安装
    
    • 1
    • 2

5. SSL认证

	1. 腾讯云 -> 申请免费SSL
	2. 腾讯云 -> 下载SSL文件
	3. 腾讯云 -> 安全组开启443端口
	4. 宝塔面板 -> 网站 -> 设置 -> SSL -> 粘贴(key/pem)
	   -> 证书夹 -> 部署SSL
	5. 验证 http -> https
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6. DDNS认证

1. 确认本地网络是否为()公有ip(查看路由器LanIP与WanIP是否一致)
   若不一致则属于NAT模式,需致电运营商客服,改为公有ip。话术:监控需要
2. 致电运营商客服报修,将光猫更改为PPOE拨号模式
3. 购买华硕官方固改路由器 wifi-6
4. 登录后台设置PPOE拨号、DDNS、DMZ、外网
5. 手机断网连接测试是否成功
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

7. 服务器缓存清理

# 进入目录
cd /abc/ 
# 查看文件夹里的文件 | df  -h
du -sh *
# 查看是/home/文件下那个文件比较大
du --max-depth=1 -h /abc/
# 删除文件夹
rm -rf abc
# 清空日志
>access_log 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

八、Django迁移

1. MacOS-Django 项目打包

  1. 依赖环境打包

    # 项目所在文件夹terminal输入
    pip freeze > requirements.txt
    
    # 也可以只打包需要用到的有效插件
    pip install pipreqs
    pipreqs /path/to/your/project
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  2. 静态文件打包

    python manage.py collectstatic
    
    • 1
  3. 项目文件打包

    将项目文件夹移动至云服务器内
    
    • 1

2. 宝塔部署服务器

  1. 安装python项目管理器

    1. 服务器宝塔面板
    2. 软件商店
    3. 应用搜索“python”
    4. 安装“Python项目管理器”
    
    • 1
    • 2
    • 3
    • 4
  2. 安装python环境

    1. 打开Python项目管理器
    2. 选自一个与自己项目匹配的python环境进行安装
    
    • 1
    • 2
  3. 启动Django项目

    1. python管理器添加项目
    2. 修改配置 # (可以不设置)
    3. 开启映射    #(公网ip/失败的话多试几次)
    4. 输入ip+端口进行外网连接测试
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

  4. Q & A
    A. 端口被占用

    # 端口被占用报错 bind(): Address already in use [core/socket.c line 769])
    sudo fuser -k 8000/tcp  # (8000需要填你的端口)
    
    • 1
    • 2

    B. SQLite3.9.0 or later is required(found%s)…报错

    # 原因: SQLite版本太低,更新或关闭报错
    1. 降低Django版本 django==3.0.4
    2. 更改设置
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    C. 单独安装python模块插件

    # 因为pip和python所在位置为虚拟环境
    在命令行输入 : /项目路径/md5命名的文件夹/bin/pip install xxx
    示例:/data/python/d9036cc6563924cf9e1da4e1cd64f9a4_venv/bin/pip install xxx
    
    • 1
    • 2
    • 3

3. 静态素材处理

  1. <uwsgi.ini> 中添加static路径

    static-map = /static=/www/wwwroot/(项目名|可替换)/collect_static
    
    • 1
    注:collect_static需与前期setting设置中STATIC_ROOT一致
    
    静态素材缺失卡了整整一天,试了很多方式包括调整反向代理location,
    调整setting等均无效果。最终无意在处理no Internet serve问题时获得答案
    总结下来就是不要放弃寻找心中的答案!
    
    安全起见在项目打包上传宝塔前于MacOS环境下完成static素材收集
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  2. 测试成功显示静态素材


九、对象存储

  1. 购买腾讯云COS服务
  2. 创建存储桶
  3. 上传资料文件
  4. 获得资料链接
  5. 引入html测试

十、域名备案

  1. 腾讯云域名注册
  2. 创建审核个人实名档案
  3. 72小时后申报备案
  4. 腾讯客服会对申请内容做出建议
    个人申请内容尽量是个人爱好
  5. 收到工信部的核验消息完成核验
  6. 等待7~15天完成备案
  7. 腾讯云域名解析 (www.xxx.com)
  8. 宝塔接入站点
  9. 测试域名完成验证
PS:网站名申请若有疑问,腾讯会电话申请人并给到一些建议
记得要精准核对电话中的中文字,以免产生误解
  • 1
  • 2

十一、付款接入

支付宝支付参考文章

cnblogs.com/xiaolu915/p/10528155.html
  • 1

十二、理解原理

MTV概念图
请添加图片描述请添加图片描述

十三、Dapps智能合约

使用 Ether.js

<script>
	async function write_to_Dapp() {
    const contractABI = '[{...}]'
    const contractAddress = "0x7f33...";
    const contract_privatekey = "77599fb..."
    const info_returns = 'http://...'

    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const signer = new ethers.Wallet(contract_privatekey, provider);
    const contract = new ethers.Contract(contractAddress, contractABI, signer);
    const accounts = await provider.send("eth_requestAccounts", []);

    // mint contract
    const instance = await contract.mint(accounts[0], info_returns)
    console.log('song mint success', instance)

    // send transaction
    // const tx = {
    //     to: accounts[0],
    //     value: ethers.utils.parseEther("0.002"),
    //     type: 1,
    //     gasPrice: await provider.getGasPrice(),
    //     gasLimit: ethers.utils.hexlify(21000),
    // };
    // signer.sendTransaction(tx)

    // // let varGasPrice = ethers.getGasPrice();
    // console.log(varGasPrice, '...')

    // console.log(parseInt(await provider.getGasPrice()))

    // const sendTransactionPromise = await signer.sendTransaction(tx);
    // console.log(sendTransactionPromise)
}
</script>
<script src="../static/js/ethers-5.2.umd.min.js" type="application/javascript"></script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

十四、打包同步PyPi

  1. 构建项目

    myproject/
    │
    ├── myproject/
    │   ├── __init__.py
    │   └── main.py
    │
    ├── README.md
    └── setup.py
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.1 setup.py

    from .myproject import *
    
    # VERSION = '1.0'
    # print("Initializing mypackage...")
    
    • 1
    • 2
    • 3
    • 4

    1.2 setup.py

    # setup.py
    
    from setuptools import setup, find_packages
    
    setup(
        name='myproject',
        version='0.1.0',
        packages=find_packages(),
        install_requires=[
            # 添加你的依赖项
        ],
        entry_points={
            'console_scripts': [
                'myproject = myproject.__main__:main'
            ]
        },
        author='Your Name',
        author_email='your.email@example.com',
        description='Description of your project',
        url='https://github.com/yourusername/myproject',
        keywords=['python', 'example'],
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  2. 打包

    # 1. 打包程序
    python setup.py sdist bdist_wheel
    
    # 2. 上传pypi
    pip install twine
    twine upload dist/*
    
    # 3. 输入token
    PyPi官网获取Token,并输入
    
    ## 备注
    pypi不允许同样的名字和版本号发布
    记得产看并清楚不必要的dist里的文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

十五、部署Jupyter

#安装
pip install jupyterlab

#特别版本
pip install "jupyterlab~=3.6.0"

# 创建用户(避免root用户直接使用)
adduser yourname

# 切换用户
su - yourname

# 卸载
pip uninstall jupyterlab

# 启动//记得打开防火墙8888
jupyter lab --ip='*' --port=8888 

# 后台不间断启动
nohup jupyter lab --ip='*' --port=8888 --no-browser > jupyterlab.log 2>&1 & 

# 调整notebook所在目标文件夹
--notebook-dir=目标路径

#综合命令
nohup jupyter lab --ip='*' --port=8888 --notebook-dir=/home --allow-root

# 查看token
jupyter server list 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

持续更新…

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/521432
推荐阅读
相关标签
  

闽ICP备14008679号