Django从零搭建个人网站
- 导览
- 前言
- 一、环境介绍
- 二、安装测试
- 三. 账号功能
- 四、前端调试
- 五、简易API
- 六、常用工具
- 七、服务器搭建
- 八、Django迁移
- 九、对象存储
- 十、域名备案
- 十一、付款接入
- 十二、理解原理
- 十三、Dapps智能合约
- 十四、打包同步PyPi
- 十五、部署Jupyter
- 持续更新...
导览
以下内容您将了解如何使用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
-
新建app/urls
/(project)/(app)/urls
-
创建URLconf
# project/app/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]
-
插入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. 自定端口
-
python文件更改
# manage.py输入 execute_from_command_line(['manage.py', 'runserver', '0.0.0.0:2516'])
-
启用端口
# project/terminal输入 python3.8 manage.py runserver 0.0.0.0:8000
-
清空端口(若需)
# 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. 创建模型
-
编辑模型
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']
-
激活模型
INSTALLED_APPS = [ ... '(app-item).apps.(App-item)Config', ]
-
模型迁移
python3.8 manage.py makemigrations (App-item) python3.8 manage.py migrate
-
模型删除(若需)
python3.8 manage.py migrate (App-item) zero
2. 管理数据
-
创建管理员
python3.8 manage.py createsuperuser
-
通知Admin站点
from django.contrib import admin from .models import * admin.site.register(Music)
-
数据管理页面
python3.8 manage.py runserver 0.0.0.0:8000 http://0.0.0.0:8000/admin/login/?next=/admin/
-
离线数据库编辑
# 离线浏览及编辑数据库 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()
-
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. 邮件分发
- 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'
- 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("发送完成")
- 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. 浏览权限
-
创建装饰器
# 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
-
配置需要权限的视图
# views.py from MusicStore.decorator import already_login @already_login def index (request): return render(request,'index.html')
9. 密码重置
-
向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')
-
修改密码
@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')
-
装饰器
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
-
urls
urlpatterns = [ ... path('email_code/', views.email_code, name='email_code'), path('pswrd_reset/',views.pswrd_reset,name='pswrd_reset'), ]
-
前端
... <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. 请求限制
- 安装插件
pip3.8 insatall django-ratelimit
- 设限条件
@ratelimit(key='ip', rate='5/h',block=True) your_views(requst): ...
- 参考链接
https://django-ratelimit.readthedocs.io/en/stable/usage.html
…
12. 禁止IP
-
安装GeoLite2并下载IP数据库
# 1. 安装geoip2 pip3.8 install geoip2 # 2. 下载City和Country数据库 搜索-> GeoLite2免费下载 ...
-
创建 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>')
-
setting.py
MIDDLEWARE = [ ... '(django项目名).middleware.TestMiddleware', ]
13. 404页面
-
urls.py
... from MusicStore.views import * handler403 = page_403 handler404 = page_404 ...
-
views.py
# setting.py DEBUG = False ...
def page_404 (request, exception, template_name='404.html'): return render(request,template_name)
14. 关闭Debug模式
- setting.py
DEBUG = False
- terminal
<!-- MacOS系统下Debug默认为True,关闭成False后静态素材和样式丢失的解决办法 --> python3.8 manage.py runserver 0.0.0.0:8000 --insecure
15. RestAPI
-
安装组件
pip3.8 install djangorestframework pip3.8 install markdown pip3.8 install django-filter
-
添加项目
INSTALLED_APPS = [ ... 'rest_framework', ]
-
配置模型
# setting.py REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' ] }
-
创建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')) ]
-
运行项目
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调式
-
映射跳转链接
# 替换模板中的页面跳转 | 新建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 !]')
-
映射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()
-
批量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)
-
批量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
-
创建templates
# 该文件夹将用于存放html内容 (project)/(item)/templates
-
导入html
# 将模板索引页拖入templates中 (project)/(item)/templates/index.html (project)/(item)/templates/home.html ...
-
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'), ... ]
-
urls新建
# url.py输入 from django.shortcuts import render def index(request): return render(request, 'index.html') def home(request): return render(request, 'home.html') ...
-
DIRS设置
# setting.py 添加DIRS import os TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], ... } ]
-
INSTALLED_APPS设置
# setting.py 添加所建的App import os INSTALLED_APPS = [ [ ... 'xxx-app', ... ]
…
4. 导入static
- 创建static
# 该文件夹将用于存放物及料配置内容 (位置与templates同级) (project)/(item)/static
- 导入物料
# 文件移动 (该文件夹将用于存放Images、JavaScript、CSS等文件) (project)/(item)/static/images (project)/(item)/static/css (project)/(item)/static/js ...
- 静态收集
-
setting.py 设置
# 为了部署时将静态文件复制到所有服务端都可以访问的文件夹 STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'collectstatic/') STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static'), )
-
manage.py 收集
# MacOS Project/Terminal执行收集程序 python manage.py collectstatic
-
注释
# 关于STATIC的注释 STATIC_URL = static地址 [让网页可以访问到静态文件] STATIC_ROOT = 收集归档所有static文件 [让静态文件夹可被所有客户端访问到] [可以避免多个app需要多个静态目录] [名字自取但需和uwsgi.ini中路径一致] STATICFILES_DIRS = [让Django同时在App的内、外搜索静态文件]
-
…
5. html映射
- 跳转链接映射
# html链接映射(pagename在urlpatterns里设置) # 替换前 <a href="about.html">关于</a> # 替换后 <a href="{% url 'home' %}">关于</a> {% url 'home' %}
- 物料链接映射
# 物料链接映射 ../static/images/xxx.png ../static/css/xxx.css ../static/js/xxx.js
…
6. 测试服务
- 运行网页服务
# project/terminal输入 python3.8 manage.py
- 退出网页服务
# 退出项目 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. 页面显参
-
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)
-
.html
<span> {{ msg }} </span>
-
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
-
过程简述
codepen上有很多有趣的svg和动画效果,很多是scss格式而不是css 所以需要研究如何在HTML中引入,大概路径如下: 1. 安装npm 2. 安装sass 3. scss转换css 4. 引入css
-
安装步骤
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. 前端页面
-
创建基础页面
<!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>
-
用户登录展示
{% if user.is_authenticated %} <span> {{user.username}} </span> {% endif %}
…
4. API交互调试
- 运行网页服务
# project/terminal输入 python3.8 manage.py
- 用户输入内容提交
asj!~fh%$#@#!
- 服务器反馈
[xx/Jun/2022 14:20:26] "GET /xxxxx = HTTP/1.1" 3xx x
- 退出网页服务
# 退出项目 control + c
5. Request
- request.user
返回用户登录名 用户没有登陆的时返回AnonymousUser(匿名用户)
- request.session
作用: session里设置数值,便于日后访问网页时做判断 方法: 1. 通过request.session[name]=value 【设置数值】 2. 通过request.session.get(name) 【读取数值】 3. 通过request.seesion.set_expire(value)【过期】
6. 收藏功能(实例)
-
实现路径
// 想做一个音乐收藏功能,搞了好几天,终于搞定 // Ugly but it works ... 1. Sqlite数据存储 (ADD/DELETE) 2. DjangoAPI (POST/GET) 3. HTML前端设计 (UI/SVG) 4. AJAX交互 (传参/.CSS()/刷新) 5. 用户体验优化 (卡顿/for循环排序...)
-
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']
-
migrate
# 一系列操作... 大致如下: # 通知admin 1. admin.site.register(Favorite) # 数据库建档迁移 2. makemigrations & migrate ...
-
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')
-
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 %}
-
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. 用户行为日志
-
功能描述
通过"中间件"记录用户数据日志 自定义exclude_urls列表访问列表中的url 通过设置的响应时间阈值(可配置化) 将超过阈值的操作日志进行单独保存
-
创建中间件
(app-item)/middlewares/LogMiddleware.py
-
setting.py
// 自定义中间件 MIDDLEWARE += [ 'app01.middlewares.LogMiddleware.OpLogs' ]
-
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
-
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'
-
migrate
makemigrations / migrate
-
时区更改
# setting.py TIME_ZONE = 'Asia/Shanghai' # 'UTC'
8. 搜索匹配
...
9. 购物车
...
10. 用户上传
-
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('')
-
urls.py
path('fileManagerUpload/', fileManagerUpload,name="fileManagerUpload"),
-
setting.py
MEDIA_ROOT = os.path.join(BASE_DIR,'(app-item)/(temp)')
-
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
-
terminal 执行
# 在命令行中,从你的Django项目目录运行以下两个命令 #(每个命令需要在不同的终端中运行) # 启动worker celery -A your_project_name worker --loglevel=info #启动beat celery -A your_project_name beat --loglevel=info
-
启动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. 阿里云服务
- 购买云服务器
# 阿里云购买轻量服务器 地址: https://www.aliyun.com/ 配置: 2核 - 1GB内存 - 系统盘 40GB ESSD - 上海 价格: 80/月 镜像: Linux CentOS 8.2
- 云服务器初始化
# 阿里云购买轻量服务器 1. 进入阿里云控制台 2. 设定管理员密码 3. 查看公网/内网地址 (可外网访问) 4. 防火墙打开:8000(Django)/:8888(宝塔)等端口 5. 开启服务器 # 4. 硬盘挂载
…
2. 腾讯云服务
- 购买云服务器
- 云服务器初始化
…
3. 亚马逊云服务
-
购买云服务器
1. 注册AWS 2. 完成Billing Account 信用卡信息 3. 选择免费服务 4. EC2 创建实例(Linux 免费) 5. 扩充空间 EC2-Elastic Block Store 原始(8G)->(20G) 6. 搜索ssl并添加负载均衡 (参考文章:亚马逊云的服务器(EC2)+阿里云的域名,添加ssl证书) 7. 复制CNAME和VALUE用于域名解析 8. Connect连接实例 9. 安装宝塔 10. Security Group安全组增加访问端口 11. 登录面板 10.安装python管理器2.0 由于宝塔仅支持Centos系统,亚马逊是AmazonLinux 难免不兼容,所以不同版本多装几次(2.1版本经常报错) 我的顺序是3.79->3.71->3.85...
-
宝塔安装
1. 打开所有端口 2.远程登录,输入sudo -i 3.yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
-
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 => 设置密码
-
宝塔Django报错:
问题:Django - deterministic=True requires SQLite 3.8.3 or higher upon running python manage.py runserver1. 安装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. 域名绑定
-
在 SSH 窗口中,使用 Debian 管理器安装 apache2 软件包。
-
安装 Apache 后,操作系统会自动启动 Apache 服务器。
sudo apt-get update && sudo apt-get install apache2 -y
-
使用 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.)。 点击创建。
-
更新域名服务器
如需更新 Cloud Domains 中的域名服务器,请按以下步骤操作: 在 Google Cloud 控制台中,前往 Cloud Domains 页面。 前往 Cloud Domains 点击您要修改的域名。您还可以点击域名旁边的 more_vert更多来查看修改菜单。 如需修改 DNS 详细信息,请点击 Edit DNS details(修改 DNS 详细信息)。 选择使用 Cloud DNS(推荐)。 在 Cloud DNS 可用区列表中,选择 my-new-zone。 点击保存。
-
验证
# 安装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
-
其它查阅
4. Web端运维
-
MacOS安装 Royal-TSX
1. 使用Royal-TSX代替XShell 远程连接服务器SSH/FTPS 2. Plugins添加Terminal/File Transfer两款插件 3. 新建一个Terminal窗口并输入对应的服务器的信息 4. 指定用户名和密码 5. 完成登录
注:Royal-TSX 图片内容均引用自作者zoiiiiii
-
服务器下载宝塔脚本(CentOS)
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec
-
yum报错处理
-
存在问题
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 ...
-
替换数据源
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
-
vim修改文件
vim /etc/yum.repos.d/CentOS-Linux-AppStream.repo
-
替换baseurl数据源地址
baseurl=http://mirrors.cloud.aliyuncs.com/centos-vault/8.5.2111/AppStream/$basearch/os/
-
重新创建元数据
yum makecache
-
安装成功
-
-
宝塔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 项目打包
-
依赖环境打包
# 项目所在文件夹terminal输入 pip freeze > requirements.txt # 也可以只打包需要用到的有效插件 pip install pipreqs pipreqs /path/to/your/project
-
静态文件打包
python manage.py collectstatic
-
项目文件打包
将项目文件夹移动至云服务器内
2. 宝塔部署服务器
-
安装python项目管理器
1. 服务器宝塔面板 2. 软件商店 3. 应用搜索“python” 4. 安装“Python项目管理器”
-
安装python环境
1. 打开Python项目管理器 2. 选自一个与自己项目匹配的python环境进行安装
-
启动Django项目
1. python管理器添加项目 2. 修改配置 # (可以不设置) 3. 开启映射 #(公网ip/失败的话多试几次) 4. 输入ip+端口进行外网连接测试
-
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. 静态素材处理
-
<uwsgi.ini> 中添加static路径
static-map = /static=/www/wwwroot/(项目名|可替换)/collect_static
注:collect_static需与前期setting设置中STATIC_ROOT一致 静态素材缺失卡了整整一天,试了很多方式包括调整反向代理location, 调整setting等均无效果。最终无意在处理no Internet serve问题时获得答案 总结下来就是不要放弃寻找心中的答案! 安全起见在项目打包上传宝塔前于MacOS环境下完成static素材收集
-
测试成功显示静态素材
…
九、对象存储
- 购买腾讯云COS服务
- 创建存储桶
- 上传资料文件
- 获得资料链接
- 引入html测试
…
十、域名备案
- 腾讯云域名注册
- 创建审核个人实名档案
- 72小时后申报备案
- 腾讯客服会对申请内容做出建议
个人申请内容尽量是个人爱好 - 收到工信部的核验消息完成核验
- 等待7~15天完成备案
- 腾讯云域名解析 (www.xxx.com)
- 宝塔接入站点
- 测试域名完成验证
PS:网站名申请若有疑问,腾讯会电话申请人并给到一些建议
记得要精准核对电话中的中文字,以免产生误解
…
十一、付款接入
支付宝支付参考文章
cnblogs.com/xiaolu915/p/10528155.html
十二、理解原理
…
十三、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
-
构建项目
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'], )
-
打包
# 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