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

Django从零搭建个人网站

  • 导览
  • 前言
  • 一、环境介绍
  • 二、安装测试
    • 1. 框架安装
    • 2. 查看版本
    • 3. 新建项目
    • 4. 新建App
    • 5. 运行项目
    • 6. 测试项目
    • 7. 局域配置
    • 8. include
    • 9. 自定端口
    • 10. 局域测试
  • 三. 账号功能
    • 1. 创建模型
    • 2. 管理数据
    • 3. 登录验证
    • 4. 创建账号
    • 5. 退出登录
    • 6. 弹窗提示
    • 7. 邮件分发
    • 8. 浏览权限
    • 9. 密码重置
    • 10. 加密解密
    • 11. 请求限制
    • 12. 禁止IP
    • 13. 404页面
    • 14. 关闭Debug模式
    • 15. RestAPI
  • 四、前端调试
    • 1. 下载模板
    • 2. html调式
    • 3. 导入html
    • 4. 导入static
    • 5. html映射
    • 6. 测试服务
    • 7. 调试结果
    • 8. loading动画
    • 9. 页面显参
    • 10. jQuery
      • 1. 概念简述
      • 2. 后台API
      • 3. HTML标签传参
      • 4. 传参不跳转
      • 5. 传参且跳转页面
      • 6. 跳转页面带参
      • 7. 定时自动跳转
      • 8. 输入时按钮定时隐藏与显示
      • 9. SCSS
      • 10. 双重For循环
      • 11. 分页显示
      • 12. 音频播放器
      • 13. Split分割字符串
      • 14. 鼠标长按触发
      • 15. input监听
      • 16. JS接收API返回值
      • 17. Safari圆角失效
  • 五、简易API
    • 1. 创建接口函数
    • 2. 创建Urls配置
    • 3. 前端页面
    • 4. API交互调试
    • 5. Request
    • 6. 收藏功能(实例)
    • 7. 用户行为日志
    • 8. 搜索匹配
    • 9. 购物车
    • 10. 用户上传
    • 11. 用户下载
    • 12. 用户读取
    • 13. 用户读写权限
    • 14. User模型扩展
    • 15. 笔记若干
      • 1. Serializers
      • 2. json标准格式化
      • 3. urllib中文地址解码
      • 4. DateTimeField 报错
      • 5. 服务器日志打印输出
  • 六、常用工具
    • 1. 自动文档生成
    • 2. 文件压缩
    • 3. 身份证验证
    • 4. 电话号码验证
    • 5. 密码设置规则
  • 七、服务器搭建
    • 1. 阿里云服务
    • 2. Web端运维
    • 3. SSL认证
    • 4. DDNS认证
  • 八、Django迁移
    • 1. MacOS-Django 项目打包
    • 2. 宝塔部署服务器
    • 3. 静态素材处理
  • 九、对象存储
  • 十、域名备案
  • 十一、付款接入
  • 十二、理解原理
  • 持续更新...


导览

以下内容您将了解如何使用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

2. 查看版本

# temrinal输入
python3.8 -m django --version

3. 新建项目

# temrinal输入
django-admin startproject website

4. 新建App

# terminal输入
python3 manage.py startapp webapp

5. 运行项目

# terminal输入
python3.8 manage.py runserver

6. 测试项目

# 浏览器输入
http://127.0.0.1:8000/

7. 局域配置

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

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

8. include

  1. 新建app/urls

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

    # project/app/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
    	 path('', views.index, name='index'),
    ]
    
  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),
    ]
    
    

9. 自定端口

  1. python文件更改

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

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

    # Question: ( 报错 )
    Django-Error: That port is already in use
    
    # Answer: ( 查询正在使用的进程并终止 )
    lsof -i:oooo 
    kill -9 nnnn 
    

10. 局域测试

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

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

python3.8 manage.py runserver 0.0.0.0:8000

三. 账号功能

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']
    
  2. 激活模型

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

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

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

2. 管理数据

  1. 创建管理员

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

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

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

    # 离线浏览及编辑数据库
    DB Browser for SQLite Version 3.12.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()
    
  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()
    

    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()
    

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__())

注:一定要设置USERNAME_FIELD

b. 配置注册

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

c. urlpatterns

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

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')

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

e. 页面引入

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

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')

5. 退出登录

def signout(requst):                      
    logout(requst)        
    return render(requst,'signin.html')  

6. 弹窗提示

a. view.py

from django.contrib import messages

def alert_box(requst,message):
    messages.success(requst,message)

b. html 加载在body内


{% if messages %}
	<script>
    	{% for msg in messages %}
         	alert('{{ msg.message }}');
        {% endfor %}
    script>
{% endif %}
   

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 = '[email protected]'
    # ***客户端授权密码!!**  
    # 需要去163邮箱开启smpt服务并获得唯一授权码
    EMAIL_HOST_PASSWORD = '!@#$%^&*$%^@#'
    # 收件人看到的发件人
    EMAIL_FROM = 'xxx'
    # 避免报错加上
    DEFAULT_FROM_EMAIL = '[email protected]'
    
  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 = ["[email protected]","[email protected]"...]
        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 = ["[email protected]"]
        msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr)
        msg.attach_alternative(html_content, "text/html")
     
        # 发送图像
        html1 = "
    "
    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("发送完成")
  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"), 
    ]
    

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
    
    
  2. 配置需要权限的视图

    # views.py
    from MusicStore.decorator import already_login
    
    @already_login
    def index (request):
    	return render(request,'index.html')
    

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'

    重置验证码,' \ f'请谨慎保管

    ' \ f'{code}

    '
    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')
  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')
    
  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
    
  4. urls

    urlpatterns = [
    	...
    	path('email_code/', views.email_code, name='email_code'),
    	path('pswrd_reset/',views.pswrd_reset,name='pswrd_reset'),
    ]
    
  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="确认密码">
    

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'# 校验

11. 请求限制

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

12. 禁止IP

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

    # 1. 安装geoip2
    pip3.8 install geoip2
    
    # 2. 下载City和Country数据库 
    搜索-> GeoLite2免费下载
    ...
    
  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('

    no permission

    '
    )
  3. setting.py

    MIDDLEWARE = [
    	...
        '(django项目名).middleware.TestMiddleware',
    ]
    

13. 404页面

  1. urls.py

    ...
    from MusicStore.views import *
    handler403 = page_403
    handler404 = page_404
    ...
    
  2. views.py

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

14. 关闭Debug模式

  1. setting.py
    DEBUG = False
    
  2. terminal
    
    python3.8  manage.py runserver 0.0.0.0:8000 --insecure 
    

15. RestAPI

  1. 安装组件

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

    INSTALLED_APPS = [
    	...
    	'rest_framework',
    ]
    
  3. 配置模型

    # setting.py
    REST_FRAMEWORK = {
    	'DEFAULT_PERMISSION_CLASSES': [
       		'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    	]
    }
    
  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'))
    ]
    
  5. 运行项目

    	python3.8 manage.py migrate
    	python3.8 manage.py runserver
    	http://0.0.0.0:8000/music/
    	```
    


四、前端调试

1. 下载模板

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

注: 找一些比较成熟综合素材网
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 !]')
    
  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()
    
  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)
    
  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)
    

3. 导入html

  1. 创建templates

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

    # 将模板索引页拖入templates中
    (project)/(item)/templates/index.html
    (project)/(item)/templates/home.html
    ...
    
  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'),
    	...
    ]
    
  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')
    ...
    
  5. DIRS设置

    # setting.py 添加DIRS
    import os
    
    TEMPLATES = [
    	{ ...
    	  'DIRS': [os.path.join(BASE_DIR, 'templates')],
    	  ... }
     ]
    
  6. INSTALLED_APPS设置

    # setting.py 添加所建的App
    import os
    
    INSTALLED_APPS = [
    [ ...
      'xxx-app',
      ... 
    ]
    

4. 导入static

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

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

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

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

5. html映射

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

6. 测试服务

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

7. 调试结果

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

8. loading动画

 
{% 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 %}

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)
    
  2. .html

    <span> {{ msg }} span>
    
  3. ForEach

    {% for i in msg %}
        <li> {{i.title}} li>
    {% endfor %}
    

10. jQuery

1. 概念简述

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

2. 后台API

<button id="btn">ClickMebutton>
<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>
def handle():
	if request.method == 'POST':
	   collection = Favorite()
	   username = request.POST.get('username')

3. HTML标签传参

<div id="">div>
<scrip>
	$("#id").text(data["msg"])
scrip>

4. 传参不跳转

$ajax({
	...
});
return false;

5. 传参且跳转页面

$ajax({
	...
	success: function(data){
		window.location.href="{% url 'xxx' %}";
	}
});	

6. 跳转页面带参

// 增加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);
// 前一页传参
<script>
	$(function(){
		...
		name = "";
		age  = "";
		url  = "xxx.html?name="+name+"&age="+age;//此处拼接内容
		window.location.href = url;
	})
</script>
// 后一页获参
<script>
	function getData(){
		var name = $.Request("name");
		var age  = $.Request("age");
	}
	getData()
</script>

7. 定时自动跳转

<header style="text-indent: 2em; margin-top: 30px;">
	<span id="time">4span><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>

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

var t
$("#ipt").on('input',function (){
	clearTimeout(t)
    $("#ipt_btn").hide()
    t = setTimeout(function (){
        $("#ipt_btn").show()
		},1500);
)

9. SCSS

  1. 过程简述

    codepen上有很多有趣的svg和动画效果,很多是scss格式而不是css
    所以需要研究如何在HTML中引入,大概路径如下:
    
    1. 安装npm
    2. 安装sass
    3. scss转换css
    4. 引入css
    
  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
    
    # 安装cnpm(淘宝镜像)
    npm install -g cnpm --registry=https://registry.npm.taobao.org
    
    # 若安装nrm报错[email protected]: 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
    
    # 在需要转换的sass文件夹下terminal
    sass (input.scss) (output.css)
    

10. 双重For循环

# 使用Django模板标签
data1 =  xx.objects.all()
data2 = {
           "key": ["value1","value2"],
           ...
		}
msg = {
		"data1" : data1,
    	"data2" : data2,
	  }

{% for k,v in msg %}
	<div>{{ k.xx }}div>
	{% for item in k %}
		<div>{{ item.yy }}div>
	{% endfor %}
{% endfor %}

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)                                 

.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
    <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>

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>

13. Split分割字符串

let element = location.href.split(',',2).at(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>

15. input监听

// 失焦
$("#id").blur('input',function (){})
// 聚焦
$("#id").focus('input',function (){})
// 开始输入
$("#id").on('input',function (){})

16. JS接收API返回值

Views

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

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>

17. Safari圆角失效


style = "-webkit-transform:rotate(0deg)" 

五、简易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')

2. 创建Urls配置

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

urlpatterns = [
    ...
    path('playmusic/', views.playmusic),
    ...
]

3. 前端页面

  1. 创建基础页面

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>logintitle>
    head>
    <body>
    <form action="/playmusic" method="POSTS">
        <h1>用户名:<input name="musicNFT">h1>
        <input type="submit" value="提交">
    form>
    body>
    html>
    
  2. 用户登录展示

    {% if user.is_authenticated %}
    <span> {{user.username}} span>
    {% endif %}
    

4. API交互调试

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

5. Request

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

6. 收藏功能(实例)

  1. 实现路径

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

    # 一系列操作... 大致如下:
    # 通知admin
    1. admin.site.register(Favorite)
    # 数据库建档迁移
    2. makemigrations & migrate
    ...
    
  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')
    
    
  5. SVG绘制与CSS

    {#  ❤️ }
    {% for song in msg %}
    
    
       
           
       
    
    {% endfor %}
    
  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>
    

7. 用户行为日志

  1. 功能描述

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

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

    // 自定义中间件
    MIDDLEWARE += [
    'app01.middlewares.LogMiddleware.OpLogs'
    ]
    
  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
    
  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'                                 
    
  6. migrate

    makemigrations / migrate
    
  7. 时区更改

    # setting.py
    TIME_ZONE = 'Asia/Shanghai' # 'UTC'
    

8. 搜索匹配

...

9. 购物车

...

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('')
    
  2. urls.py

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

    MEDIA_ROOT = os.path.join(BASE_DIR,'(app-item)/(temp)')
    
  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>
    

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

html

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

[方法 B]
js

<a id="" href="" Download=""><button>Downloadbutton>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>	

12. 用户读取

# setting.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'xx/media')
# 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.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]
# 查看文件夹
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))
# 页面查看或下载
https://xxx.com/media/xxx.pdf

13. 用户读写权限

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

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

常用权限代码参考

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

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")
# setting.py
INSTALLED_APPS = [ ...
    '(App-item)',
]

AUTH_USER_MODEL = '(App-item).User'
$ python3.8 manage.py makemigrations
$ python3.8 manage.py migrate
# (app-item)/admin.py
admin.site.register(User)
# (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 

15. 笔记若干

1. Serializers

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

2. json标准格式化

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

3. urllib中文地址解码

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

4. DateTimeField 报错

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

# 解决方法:
setting.py
USE_TZ  = False  

5. 服务器日志打印输出

import logging
logger = logging.getLogger('django')
logger.error('Something went wrong!')

六、常用工具

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
	# view.py
	draw_image(
	    bocfldpath = userlicensefldpath ,
	    ...
	    timenow    = timenow,
	)

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
	
	# 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],
	    )

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)
	

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)

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>

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

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

七、服务器搭建

1. 阿里云服务

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

2. Web端运维

  1. MacOS安装 Royal-TSX

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

    【实战】Django从零搭建个人网站_第1张图片
    【实战】Django从零搭建个人网站_第2张图片
    【实战】Django从零搭建个人网站_第3张图片
    【实战】Django从零搭建个人网站_第4张图片注: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	
    
  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
      ...
      
    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 
      
    3. vim修改文件

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

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

      yum makecache
      
    6. 安装成功
      【实战】Django从零搭建个人网站_第5张图片

  4. 宝塔Web端登录

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

3. SSL认证

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

4. DDNS认证

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

八、Django迁移

1. MacOS-Django 项目打包

  1. 依赖环境打包

    # 项目所在文件夹terminal输入
    pip freeze > requirements.txt
    
  2. 静态文件打包

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

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

2. 宝塔部署服务器

  1. 安装python项目管理器

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

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

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

    【实战】Django从零搭建个人网站_第6张图片

  4. Q & A

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

3. 静态素材处理

  1. 中添加static路径

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


九、对象存储

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

十、域名备案

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

十一、付款接入

支付宝支付参考文章

cnblogs.com/xiaolu915/p/10528155.html

十二、理解原理

【实战】Django从零搭建个人网站_第7张图片
【实战】Django从零搭建个人网站_第8张图片【实战】Django从零搭建个人网站_第9张图片


持续更新…

你可能感兴趣的:(前端,django,python,后端,阿里云,macos,1024程序员节)