【实战】Django从零搭建个人网站
Django从零搭建个人网站
- 导览
- 前言
- 一、环境介绍
- 二、安装测试
- 1. 框架安装
- 2. 查看版本
- 3. 新建项目
- 4. 新建App
- 5. 运行项目
- 6. 测试项目
- 7. 局域配置
- 8. include
- 9. 自定端口
- 10. 局域测试
- 三. 账号功能
- 1. 创建模型
- 2. 管理数据
- 3. 登录验证
- 4. 创建账号
- 5. 退出登录
- 6. 弹窗提示
- 7. 邮件分发
- 8. 浏览权限
- 9. 密码重置
- 10. 加密解密
- 11. 请求限制
- 12. 禁止IP
- 13. 404页面
- 14. 关闭Debug模式
- 15. RestAPI
- 四、前端调试
- 1. 下载模板
- 2. html调式
- 3. 导入html
- 4. 导入static
- 5. html映射
- 6. 测试服务
- 7. 调试结果
- 8. loading动画
- 9. 页面显参
- 10. jQuery
- 1. 概念简述
- 2. 后台API
- 3. HTML标签传参
- 4. 传参不跳转
- 5. 传参且跳转页面
- 6. 跳转页面带参
- 7. 定时自动跳转
- 8. 输入时按钮定时隐藏与显示
- 9. SCSS
- 10. 双重For循环
- 11. 分页显示
- 12. 音频播放器
- 13. Split分割字符串
- 14. 鼠标长按触发
- 15. input监听
- 16. JS接收API返回值
- 17. Safari圆角失效
- 五、简易API
- 1. 创建接口函数
- 2. 创建Urls配置
- 3. 前端页面
- 4. API交互调试
- 5. Request
- 6. 收藏功能(实例)
- 7. 用户行为日志
- 8. 搜索匹配
- 9. 购物车
- 10. 用户上传
- 11. 用户下载
- 12. 用户读取
- 13. 用户读写权限
- 14. User模型扩展
- 15. 笔记若干
- 1. Serializers
- 2. json标准格式化
- 3. urllib中文地址解码
- 4. DateTimeField 报错
- 5. 服务器日志打印输出
- 六、常用工具
- 1. 自动文档生成
- 2. 文件压缩
- 3. 身份证验证
- 4. 电话号码验证
- 5. 密码设置规则
- 七、服务器搭建
- 1. 阿里云服务
- 2. Web端运维
- 3. SSL认证
- 4. DDNS认证
- 八、Django迁移
- 1. MacOS-Django 项目打包
- 2. 宝塔部署服务器
- 3. 静态素材处理
- 九、对象存储
- 十、域名备案
- 十一、付款接入
- 十二、理解原理
- 持续更新...
导览
以下内容您将了解如何使用Django快速搭建网站
在腾讯云购买域名备案到部署云服务器正式上线
顺便学习总结相关网页前端和数据库的基础操作
前言
本章为零基础学习Django快速获得学习反馈,
供本人学习复盘使用也不具任何学习观摩性。
一、环境介绍
系统版本:MacOS Monterey 12.4
芯片版本:Apple M1 Max
软件版本:Python 3.8
数据编辑:DB Browser for SQLite Version 3.12.2
编辑版本:PyCharm 2022.1.1
其他服务:CentOS 8.2 / 宝塔 / 阿里云 / 腾讯云
…
二、安装测试
1. 框架安装
打开terminal,输入如下代码:
# temrinal输入
python3.8 -m pip install Django
…
2. 查看版本
# temrinal输入
python3.8 -m django --version
…
3. 新建项目
# temrinal输入
django-admin startproject website
…
4. 新建App
# terminal输入
python3 manage.py startapp webapp
…
5. 运行项目
# terminal输入
python3.8 manage.py runserver
…
6. 测试项目
# 浏览器输入
http://127.0.0.1:8000/
…
7. 局域配置
# setting.py输入
ALLOWED_HOSTS = ['*',]
注:找到并更改ALLOWED_HOSTS就可开启局域网
setting.py可以在项目文件中找到
…
8. include
新建app/urls
/(project)/(app)/urls
创建URLconf
# project/app/urls.py from django.urls import path from . import viewsurlpatterns = [path('', views.index, name='index'), ]
插入URLconf
# project/urls.py from django.contrib import admin from django.urls import include, pathurlpatterns = [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
…
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 sqlite3db = 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 sqlite3db = 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 sqlite3db = 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 sqlite3db = 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 Userclass 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,logindef 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 Userdef 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 messagesdef 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 osdef 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_EMAILreceive_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 viewsurlpatterns = [path("send_simple_email/", views.send_simple_email, name="send_simple_email"), path("send_complex_email/", views.send_complex_email, name="send_complex_email"), ]
…
8. 浏览权限
创建装饰器
# project/app/decorator.py from django.shortcuts import render from django.http import HttpResponsedef 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_passwordsha256_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.xclass 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.databasedatapath = os.path.join(IPDIA_ROOT,'GeoLite2-City.mmdb')reader = geoip2.database.Reader(datapath)try:response = reader.city(ip)country = response.country.iso_codecityname = response.city.namedata = {'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 = Musicfields = ['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 migratepython3.8 manage.py runserverhttp://0.0.0.0:8000/music/```
…
四、前端调试
1. 下载模板
# 浏览器输入: 挑选下载一个喜欢的html模板
注: 找一些比较成熟综合素材网
UI/UX设计可以降维用于日常测试
适合平时制作ppt和影视资源使用
…
2. html调式
映射跳转链接
# 替换模板中的页面跳转 | 新建substitution.py def hyperlink_url_modify():from tqdm import tqdmpage_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 = linefor 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_patternprint(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 += urlspatternprint(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 renderdef index(request):return render(request, 'index.html')def home(request):return render(request, 'home.html') ...
DIRS设置
# setting.py 添加DIRS import osTEMPLATES = [{ ...'DIRS': [os.path.join(BASE_DIR, 'templates')],... }]
INSTALLED_APPS设置
# setting.py 添加所建的App import osINSTALLED_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 = xxxreturn render(request,'phones.html',context={'msg':var})ordef 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. 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 = 5if (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)"
五、简易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'] = musicNFTresult = 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 pathurlpatterns = [...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 datetimeuser_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 jsonfrom MusicDatabase.models import Favoritefrom datetime import datetimeuser_id = request.user.id # 调取正在收藏的用户信息if request.method == 'POST':song_id = request.POST.get('song_id') # 刚才收藏的歌曲idrequest.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 # 刚才收藏的用户idcollection.song_id_id = song_id # 刚才收藏的歌曲idcollection.collectdate = datetime.now() # 刚才收藏的时间collection.save() # 记录收藏信息request.session["result"] = {"collect": True,"song_id": song_id}print('[新增收藏]')result = json.dumps({"":""}) # 必须正确返回json后进入GETreturn 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({ // 发送操作POSTtype : "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,OpLogsclass 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())# 请求IPx_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.POSTif 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_timeself.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) { //100Malert("上传文件不能超过100M");}var reader = new FileReader(); // 实例化FileReaderreader.readAsDataURL(file); // 将文件对象转化为路径对象reader.onload = function () { // 打开文件var imgEle = document.getElementById("avatar"); // 图片框idimgEle.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_pathsigned_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 responseexcept 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 serveurlpatterns = [ ...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 AbstractUserclass User(AbstractUser):wechat = models.CharField(max_length=25, null=True,verbose_name="wechat")phone = models.CharField(max_length=25, null=True,verbose_name="phone")
# setting.py
INSTALLED_APPS = [ ...'(App-item)',
]AUTH_USER_MODEL = '(App-item).User'
$ python3.8 manage.py makemigrations
$ python3.8 manage.py migrate
# (app-item)/admin.py
admin.site.register(User)
# (app-item)/view.py
# 方案一
from django.contrib.auth import get_user_model
User = get_user_model()
# 方案二
from django.contrib.auth.models import User
from (app-item).models import User as User
15. 笔记若干
1. Serializers
# 对于objects.all()/objects.filter()
# 必须进行序列化后方可进入json转化from django.core import serializers
data = Music.objects.all()
json_data = serializers.serialize("json", data)
2. json标准格式化
result = {"key":"vlaue"}
result = json.dumps(result)
return HttpResponse(result, content_type="application/json")
3. urllib中文地址解码
from urllib import parse
username = parse.unquote(request.get_full_path().split('')[1])
4. DateTimeField 报错
# 问题报错:
RuntimeWarning: DateTimeField Draw.drawdate received a naive datetime # 解决方法:
setting.py
USE_TZ = False
5. 服务器日志打印输出
import logging
logger = logging.getLogger('django')
logger.error('Something went wrong!')
六、常用工具
1. 自动文档生成
# tools.pydef draw_image():import os.pathfrom PIL import Image, ImageDraw, ImageFontfrom django.conf import settingswidth,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.pydraw_image(bocfldpath = userlicensefldpath ,...timenow = timenow,)
2. 文件压缩
# tool.pydef file2zip(zip_file, source_file):import zipfile,oswith 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.pyzipnames = [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 reErrors = {'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]) * 3Y = S % 11M = "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]breakprint(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. 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. 注册宝塔会员并一键安装
3. SSL认证
1. 腾讯云 -> 申请免费SSL2. 腾讯云 -> 下载SSL文件3. 腾讯云 -> 安全组开启443端口4. 宝塔面板 -> 网站 -> 设置 -> SSL -> 粘贴(key/pem)-> 证书夹 -> 部署SSL5. 验证 http -> https
4. DDNS认证
1. 确认本地网络是否为(真)公有ip(查看路由器LanIP与WanIP是否一致)若不一致则属于NAT模式,需致电运营商客服,改为公有ip。话术:监控需要
2. 致电运营商客服报修,将光猫更改为PPOE拨号模式
3. 购买华硕官方固改路由器 wifi-6
4. 登录后台设置PPOE拨号、DDNS、DMZ、外网
5. 手机断网连接测试是否成功
八、Django迁移
1. MacOS-Django 项目打包
依赖环境打包
# 项目所在文件夹terminal输入 pip freeze > requirements.txt
静态文件打包
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
# 端口被占用报错 bind(): Address already in use [core/socket.c line 769]) sudo fuser -k 8000/tcp # (8000需要填你的端口)
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
十二、理解原理
…
持续更新…
【实战】Django从零搭建个人网站相关推荐
- 吃透这套架构演化图,从零搭建Web网站也不难
转载自 吃透这套架构演化图,从零搭建Web网站也不难 前言 工作也有几多年了,无论是身边遇到的还是耳间闻到的,多多少少也积攒了自己的一些经验和思考,当然,博主并没有太多接触高大上的分布式架构实践,相对 ...
- 网站搭建:从零搭建个人网站教程(4)
系列文章 网站搭建:从零搭建个人网站教程(1) 网站搭建:从零搭建个人网站教程(2) 网站搭建:从零搭建个人网站教程(3) 网站搭建:从零搭建个人网站教程(4) 网站搭建:从零搭建个人网站教程(5) ...
- 网站搭建:从零搭建个人网站教程(2)
系列文章 网站搭建:从零搭建个人网站教程(1) 网站搭建:从零搭建个人网站教程(2) 网站搭建:从零搭建个人网站教程(3) 网站搭建:从零搭建个人网站教程(4) 网站搭建:从零搭建个人网站教程(5) ...
- RHCE实战:给openlab搭建web网站
网站需求:请给openlab搭建web网站 1.基于域名[www.openlab.com](http://www.openlab.com)可以访问网站内容为 welcome to openlab!!! ...
- 吃透这套架构演化图_从零搭建Web网站也不难!
摘要: 前言 工作也有几多年了,无论是身边遇到的还是耳间闻到的,多多少少也积攒了自己的一些经验和思考,当然,博主并没有太多接触高大上的分布式架构实践,相对比较零碎,随时补充.俗话说得好,冰冻三尺非一日 ...
- 从零搭建个人网站服务器
准备前提 服务器(以阿里云的轻量应用服务器为列) 已经开发好的网站 服务器简介 内存:2G CPU:2核 系统盘:60G 系统镜像:CentOs 已经预安装了PHP 与 Mysql 环境安装 1. n ...
- 从零搭建图书网站--v1.0
一直想写一些技术相关的文章,想到一个有趣的方式.从零开始搭建一个项目,并将这个项目不断迭代优化,就像在工作过程中一样,随着时间的推移,所面对的业务问题会更深入,同时所需要解决的技术问题也会变难,当然技 ...
- 【实战】从零搭建SSO单点登录服务器 - CAS认证流程
前言 因系统逐渐增多,各个业务系统间无法共享用户状态,每个系统都需要用户登录.这对于用户来说很不友好,于是需要搭建一个SSO单点登录服务器,来做统一的登录.注销. 写这个系列的文章有两个目的: 记录自 ...
- 零基础小白10分钟用Python搭建小说网站!网友:我可以!
都说Python什么都能做,本来我是不信的!直到我在CSDN站内看到了一件真事儿:一位博主贴出了自己10分钟用Python搭建小说网站的全过程!全程只用了2步操作,简直太秀了!!-- 第一步:爬取小说 ...
- 类似零基础学python的小说_零基础小白十分钟用Python搭建小说网站!Python真的强!...
零基础小白十分钟用Python搭建小说网站!Python真的强!-1.jpg (128.29 KB, 下载次数: 0) 2018-10-8 18:51 上传 Python 和放大镜的二进制代码 人生苦 ...
最新文章
- SpringCloud Alibaba微服务实战(一) - 基础环境搭建
- Linux 下 进程运行时内部函数耗时的统计 工具:pstack,strace,perf trace,systemtap
- 第十二篇:形式语言理论与有限状态自动机
- ubuntu启动时自动挂载windows分区
- HDU 2567 寻梦(字符串,插入)
- ORA-00020:maximum number of processes (150) exceeded 错误解决方法
- hibernate异常:not-null property references a null or transient value
- 代码重构之旅(一) 项目结构
- python女朋友_教你用Python感知女朋友的情绪变化!
- tp5部署到nginx后所有分页404的解决办法
- C1007: 无法识别的标志“-Ot”
- Unity 显示FPS
- python 查找重复文件,以及查找重复视频的一些思路
- 电脑开机密码忘记,如何修改电脑密码?
- 接收Cookie总结
- java 1900年_JDK与1900年01月01日
- 计算机网络笔记手写板,电脑手写板怎么使用?莫慌!手把手教学来了
- 峰面积峰高半峰宽_峰高峰面积的计算方法
- IDEA导入项目无法识别
- axure流程图模式_手把手教你用AXURE绘制流程图的图文教程