【实战】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

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 
    # 或GoogleCloud
    sudo fuser -k 80/tcp
    
    

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内

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

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'
    
  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("发送完成")
    
  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"), 
    ]
    

如果部署在外服务器,如: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

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'

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'<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')                                                                      
    
  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('<h1 style="opacity:0.2">no permission</h1>')
    
    
  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
    <!-- MacOS系统下Debug默认为True,关闭成False后静态素材和样式丢失的解决办法 -->
    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/
    	```
    
    
    

16. 查询数据库键

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

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. 下载模板

# 浏览器输入: 挑选下载一个喜欢的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里设置)
    # 替换前 <a href="about.html">关于</a> 
    # 替换后 <a href="{% url 'home' %}">关于</a> 
    {% 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动画

 <!-- 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 -->

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. CSS & JS 笔记

1. scroll滚动条

隐藏滚动条

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

启动滚动条

.overflow-x-auto {
    overflow-x: auto
}

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>
/* 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);
}

3. 为.js文件import模块

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

var xxx = function(){
    	...
}

4. 为.js文件添加变量

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

11. jQuery

1. 概念简述

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

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>
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文件 <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);
// 前一页传参
<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">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>

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报错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
    
    # 在需要转换的sass文件夹下terminal
    sass (input.scss) (output.css)
    

10. 双重For循环

# 使用Django模板标签
data1 =  xx.objects.all()
data2 = {
           "key": ["value1","value2"],
           ...
		}
msg = {
		"data1" : data1,
    	"data2" : data2,
	  }
<!-- Html Page -->
{% 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>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>

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圆角失效

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

18. 禁止按钮

<!-- 禁止按钮 -->
$("#button").attr('disabled', 'disabled') 
<!-- 开启按钮 -->
$("#button").attr('disabled', false) 
<!-- 延迟开启按钮 -->
t = setTimeout(function () {
	$("#button").attr('disabled', false) 
}, 1200);

五、简易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>login</title>
    </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 %}
    <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 %}
    
  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>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>	

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<path>.*)', 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!')

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

setting.py

INSTALLED_APPS = [ 
   	...
   	'paypalrestsdk',
   	... 
] 
# 基于沙箱环境                                                   
PAYPAL_TEST = True                                         
# 设置收款的 PayPal 电子邮件账户                                      
PAYPAL_REVEIVER_EMAIL = "xxx@xxx.com" 

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)

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>

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 重启

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)

3. 修改Django的__init__.py文件

from .celery import app as celery_app

__all__ = ('celery_app',)

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'

5. 定义Celery任务

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

from celery import shared_task

@shared_task
def print_one():
    print(1)

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),
    },
}

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
    
  2. 启动Django后自动执行

    在project下新建一个start_service.py文件
    随后将以下代码写进去
    
    #!/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()
    
    # Terminal中运行代码
    # 授权权限
    chmod +x start_service.py 
    # 执行后台
    ./start_service.py
    

六、常用工具

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. 腾讯云服务

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

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...
    
  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
    
  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 服务加入到开机启动项中
    
    * 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 => 设置密码
    
  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
    

4. Google服务器

1. 购买云服务器

2. 宝塔安装

3. 防火墙设置

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

4. 其它

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

请添加图片描述

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()
	表示安装成功~
	====== 运行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) 
	   

6. 域名绑定

  1. 在 SSH 窗口中,使用 Debian 管理器安装 apache2 软件包。

  2. 安装 Apache 后,操作系统会自动启动 Apache 服务器。

     sudo apt-get update && sudo apt-get install apache2 -y
    
  3. 使用 Cloud DNS 设置网域

    在 Google Cloud Console 中,转到创建 DNS 可用区页面。
    
    转到“创建 DNS 可用区”
    
    对于可用区类型,请选择公开。
    
    对于可用区名称,输入 my-new-zone。
    
    对于 DNS 名称,使用一个您注册的域名输入该地区的 DNS 名称后缀(例如 example.com)。
    
    在 DNSSEC 部分,确保已选择 Off 设置。
    
    点击创建以创建填充了 NS 和 SOA 记录的可用区。
    
    如需将您注册的域名指向托管服务器的 IP 地址,您必须将 A 记录添加到您的可用区。
    
    在区域详情页面上,点击添加标准。
    从资源记录类型菜单中选择 A。
    在 IPv4 地址部分,输入您的实例的外部 IP 地址。
    点击创建为您的可用区创建 A 记录。
    (可选)添加 CNAME 记录,以将您的前缀添加到域名,例如 www.。
    
    点击添加标准。
    在 DNS 名称字段中,为域名添加前缀 www。
    在资源记录类型部分,选择 CNAME。
    在标准名称部分,依次输入域名和一个英文句点(例如 example.com.)。
    点击创建。
    
  4. 更新域名服务器

    如需更新 Cloud Domains 中的域名服务器,请按以下步骤操作:
    
    在 Google Cloud 控制台中,前往 Cloud Domains 页面。
    
    前往 Cloud Domains
    
    点击您要修改的域名。您还可以点击域名旁边的 more_vert更多来查看修改菜单。
    
    如需修改 DNS 详细信息,请点击 Edit DNS details(修改 DNS 详细信息)。
    
    选择使用 Cloud DNS(推荐)。
    
    在 Cloud DNS 可用区列表中,选择 my-new-zone。
    
    点击保存。
    
  5. 验证

    # 安装dig
    sudo apt-get update
    sudo apt-get install dnsutils
    
    # 验证
    dig +trace example.com
    # 结果类似
    example.com.    300 IN  A   IP_ADDRESS
    ;; Received 62 bytes from 216.239.34.109#53(ns-cloud-d2.googledomains.com) in 62 ms
    
  6. 其它查阅
    在这里插入图片描述

4. Web端运维

  1. MacOS安装 Royal-TSX

    1. 使用Royal-TSX代替XShell 远程连接服务器SSH/FTPS
    2. Plugins添加Terminal/File Transfer两款插件
    3. 新建一个Terminal窗口并输入对应的服务器的信息
    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	
    
  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. 安装成功
      在这里插入图片描述

  4. 宝塔Web端登录

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

5. SSL认证

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

6. DDNS认证

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

7. 服务器缓存清理

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

八、Django迁移

1. MacOS-Django 项目打包

  1. 依赖环境打包

    # 项目所在文件夹terminal输入
    pip freeze > requirements.txt
    
    # 也可以只打包需要用到的有效插件
    pip install pipreqs
    pipreqs /path/to/your/project
    
    
  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+端口进行外网连接测试
    

    在这里插入图片描述

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

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

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

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

    在这里插入图片描述

    C. 单独安装python模块插件

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

3. 静态素材处理

  1. <uwsgi.ini> 中添加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

十二、理解原理

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>

十四、打包同步PyPi

  1. 构建项目

    myproject/
    │
    ├── myproject/
    │   ├── __init__.py
    │   └── main.py
    │
    ├── README.md
    └── setup.py
    
    

    1.1 setup.py

    from .myproject import *
    
    # VERSION = '1.0'
    # print("Initializing mypackage...")
    

    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'],
    )
    
  2. 打包

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

十五、部署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 


持续更新…

  • 27
    点赞
  • 181
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: Django是一个高效的Python Web框架,在实践中开发Django项目非常有意义。首先,我们需要了解Django的架构和基本功能。Django的核心是面向模型的Web应用程序,它使用SQLite、MySQL或PostgreSQL等数据库来存储数据。Django为开发人员提供了许多工具和组件,如表单、URL路由、ORM等,使开发Web应用程序变得更加容易和快捷。 在开始Django项目开发之前,我们应该考虑以下几个问题: 1.需求分析:我们需要明确项目的目标,例如,我们是否要开发一个电子商务平台或社交网络应用程序。 2.设计数据库模型:在设计数据库模型时,我们应该考虑数据实体之间的关系,使用Django的ORM可以轻松地定义这些关系。 3.开发应用程序:我们可以开发多个应用程序,每个应用程序包含相关功能的模块。 在Django项目开发中,我们可以遵循以下步骤: 1.创建Django项目并设置好虚拟环境。 2.使用Django的ORM定义数据库模型,如用户模型、商品模型等。 3.定义URL路由和视图函数,使HTTP请求能够正确地映射到相应的视图函数。 4.编写适当的模板和静态资源,如CSS和JavaScript文件,使用户界面更美观。 5.编写测试用例并执行单元测试,确保应用程序正常工作。 6.调试和优化应用程序,确保应用程序具有良好的性能和可扩展性。 Django项目开发实战需要更好的编程技能和经验,但是一旦我们了解了Django的架构和基本功能,我们可以使用其强大的工具和组件轻松地开发大型Web应用程序。 ### 回答2: Django 是一个高效、灵活的 Python Web 框架,成熟的开发社区和丰富的资源库广受欢迎。本着实战主义的精神,我们写一篇文章介绍 Django 项目开发实战。 首先,我们需要了解 Django 的基础结构和设计模式。Django 采用了 MVC(Model-View-Controller)的设计思想,但 Django 的实现方式是 MVT(Model-View-Template)模式。该模式将视图拆分为视图和模板两个部分,便于开发和维护。其次,Django 遵循 DRY(Don't Repeat Yourself)原则,通过 ORM(Object-Relational Mapping)模式将数据库操作转化为对象操作,使得数据库的操作更加高效、简洁,减少重复代码和代码维护工作。 接下来,我们需要按照开发流程搭建 Django 项目。通过使用 Django 自带的命令行工具,我们可以创建项目、应用、模型和视图等文件。在开发和测试过程中,我们可以使用 Python 自带的虚拟环境管理工具 pipenv,创建独立的虚拟环境,避免依赖冲突和环境污染,同时可以加快项目部署。 在项目完成之后,我们需要进行部署和维护。Django 的部署方式有多种,如使用 Apache、Nginx、Docker 等工具,根据项目需求进行选择。在维护过程中,需要注意数据库备份、定期清理无用数据、优化 SQL、升级 Django 版本等问题,以确保项目的高效性和稳定性。 总之,Django 项目开发实战需要熟练掌握 Django 的基础结构和设计模式,以及开发流程和部署维护过程中的注意事项。Django 作为一种 Python Web 框架,具有高效性、灵活性和可扩展性,适合大中小型 Web 项目的开发。 ### 回答3: Django是一个高效、灵活、丰富的Python Web框架,它具有强大的MTV架构,利用它可以快速构建高性能的Web应用程序。面对实际开发,我们往往需要依据业务构建Django项目,并且进行开发实战。 首先,我们需要在系统中预装PythonDjango,并建立使用的数据库。接着,我们利用Django的核心特性建立项目框架,如利用命令行工具创建项目目录结构、迁移和建立模型等。 其次,我们需要完成视图、模板、URL路由等关键技术的学习与实践。其中,视图是处理请求和响应的核心,模板是实现页面展示的关键,URL路由则是将请求映射到对应视图的桥梁。 随后,我们需要利用Django的Admin管理系统开发后台,这让我们可以在不编写任何代码的情况下,轻松地创建、修改和删除数据模型,以及管理用户和组等重要数据。 最后,我们还需要考虑Django的安全性、性能、扩展性等实际应用问题。例如,如何进行身份认证、如何优化框架和应用的性能、如何扩展Django的功能等。 总之,Django项目开发实践需要学习和实践多种技术,也需要深入体验框架的各个方面。通过深入学习和实践,我们将能够开发出高性能、安全可靠、易于维护的Web应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值