以下内容您将了解如何使用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 / 宝塔 / 阿里云 / 腾讯云
…
打开terminal,输入如下代码:
# temrinal输入
python3.8 -m pip install Django
…
# temrinal输入
python3.8 -m django --version
…
# temrinal输入
django-admin startproject website
…
# terminal输入
python3 manage.py startapp webapp
…
# terminal输入
python3.8 manage.py runserver
…
# 浏览器输入
http://127.0.0.1:8000/
…
# setting.py输入
ALLOWED_HOSTS = ['*',]
注:找到并更改ALLOWED_HOSTS就可开启局域网
setting.py可以在项目文件中找到
…
新建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),
]
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
…
# 浏览器输入
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
…
编辑模型
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
创建管理员
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()
…
前端获得用户验证输入(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" ...>
…
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')
…
def signout(requst):
logout(requst)
return render(requst,'signin.html')
…
a. view.py
from django.contrib import messages
def alert_box(requst,message):
messages.success(requst,message)
b. html 加载在body内
{% if messages %}
<script>
{% for msg in messages %}
alert('{{ msg.message }}');
{% endfor %}
script>
{% endif %}
…
# 发送邮件配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# smpt服务器地址 [建议使用163]
EMAIL_HOST = 'smtp.163.com'
# 邮箱端口
EMAIL_PORT = 25
# 发送邮件的邮箱
EMAIL_HOST_USER = '[email protected]'
# ***客户端授权密码!!**
# 需要去163邮箱开启smpt服务并获得唯一授权码
EMAIL_HOST_PASSWORD = '!@#$%^&*$%^@#'
# 收件人看到的发件人
EMAIL_FROM = 'xxx'
# 避免报错加上
DEFAULT_FROM_EMAIL = '[email protected]'
from django.shortcuts import render, HttpResponse
from django.core.mail import send_mail, EmailMultiAlternatives
from django.conf import settings
from email.header import make_header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import os
def send_simple_email(request):
subject = "[邮件主题]"
message = "[邮件内容]"
from_email = settings.EMAIL_FROM
recipient_list = ["[email protected]","[email protected]"...]
ret = send_mail(subject, message, from_email, recipient_list)
return HttpResponse(ret)
def send_complex_email(request):
subject = ''
text_content = ''
html_content = ''
from_email = settings.DEFAULT_FROM_EMAIL
receive_email_addr = ["[email protected]"]
msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr)
msg.attach_alternative(html_content, "text/html")
# 发送图像
html1 = ""
msg_html_img = MIMEText(html1, 'html', 'utf-8')
msg.attach(msg_html_img)
file_path = os.path.join(settings.BASE_DIR, "static/kd.png")
with open(file_path, "rb") as f:
msg_img = MIMEImage(f.read())
msg_img.add_header('Content-ID', 'imgid')
msg.attach(msg_img)
# 发送txt附件
file_path = os.path.join(settings.BASE_DIR, "日志.txt")
text = open(file_path, 'rb').read()
file_name = os.path.basename(file_path)
b = make_header([(file_name, 'utf-8')]).encode('utf-8')
msg.attach(b, text)
# 发送jpg附件
file_path = os.path.join(settings.BASE_DIR, "test.jpg")
text = open(file_path, 'rb').read()
file_name = os.path.basename(file_path)
b = make_header([(file_name, 'utf-8')]).encode('utf-8')
msg.attach(b, text)
# 发送xlsx附件
file_path = os.path.join(settings.BASE_DIR, "test.xlsx")
text = open(file_path, 'rb').read()
file_name = os.path.basename(file_path)
b = make_header([(file_name, 'utf-8')]).encode('utf-8')
msg.attach(b, text)
# msg.attach_file(file_path)
msg.send()
return HttpResponse("发送完成")
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"),
]
…
创建装饰器
# 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')
向email发送验证码
def email_code(requst):
username = requst.GET.get('username')
has_username = User.objects.filter(username=username)
if has_username:
def random_str(randomlength=4):
import random
codekey = ''
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
length = len(chars) - 1
for i in range(randomlength):
codekey += chars[random.randint(0, length)]
return codekey
subject = '验证码'
text_content = '重置密码'
code = random_str()
requst.session["code"]=code
requst.session["username"] = username
email = User.objects.get(username=username).email
requst.session["email"] = email
html_content = f'重置验证码,'
\
f'请谨慎保管'
\
f'{code}'
from_email = settings.DEFAULT_FROM_EMAIL
receive_email_addr = [email]
msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr)
msg.attach_alternative(html_content, 'text/html')
msg.send()
return redirect('password')
else:
alert_box(requst,'Email尚未注册')
return redirect('forgot')
修改密码
@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="确认密码">
…
from django.contrib.auth.hashers import make_password, check_password
sha256_password = make_password("123456", None, 'pbkdf2_sha256') # 加密
checkbool = check_password("123456",'pbkdf2_sha256')# 校验
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
…
安装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('no permission
')
setting.py
MIDDLEWARE = [
...
'(django项目名).middleware.TestMiddleware',
]
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)
…
DEBUG = False
python3.8 manage.py runserver 0.0.0.0:8000 --insecure
安装组件
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/
```
…
# 浏览器输入: 挑选下载一个喜欢的html模板
注: 找一些比较成熟综合素材网
UI/UX设计可以降维用于日常测试
适合平时制作ppt和影视资源使用
…
映射跳转链接
# 替换模板中的页面跳转 | 新建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)
…
创建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',
...
]
…
# 该文件夹将用于存放物及料配置内容 (位置与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的内、外搜索静态文件]
…
# html链接映射(pagename在urlpatterns里设置)
# 替换前 关于
# 替换后 关于
{% url 'home' %}
# 物料链接映射
../static/images/xxx.png
../static/css/xxx.css
../static/js/xxx.js
…
# project/terminal输入
python3.8 manage.py
# 退出项目
control + c
…
动画效果及排版等正常显示
…
{% 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 %}
…
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 %}
…
jQuery - 用于简化选取HTML元素,并对它们执行"操作"
https://blog.csdn.net/u012932876/article/details/117465004?spm=1001.2014.3001.5506
<button id="btn">ClickMebutton>
<script src="../static/js/jquery-3.5.1.min.js">script>
<script>
$(function (){
$("#btn").click(function (){
$.ajax({
type : "post",
url : "{% url 'handle' %}"
data : {"username":"jacky"},
dataType : "json",
});
});
})
script>
def handle():
if request.method == 'POST':
collection = Favorite()
username = request.POST.get('username')
<div id="">div>
<scrip>
$("#id").text(data["msg"])
scrip>
$ajax({
...
});
return false;
$ajax({
...
success: function(data){
window.location.href="{% url 'xxx' %}";
}
});
// 增加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>
<header style="text-indent: 2em; margin-top: 30px;">
<span id="time">4span><a href="index.html" title="点击访问">跳过a>
header>
<script>
function delayURL() {
var delay = document.getElementById("time").innerHTML;
var t = setTimeout("delayURL()", 1000);
if (delay > 0) {
delay--;
document.getElementById("time").innerHTML = delay;
} else {
clearTimeout(t);
window.location.href = "index.html";
}
}
delayURL()
script>
var t
$("#ipt").on('input',function (){
clearTimeout(t)
$("#ipt_btn").hide()
t = setTimeout(function (){
$("#ipt_btn").show()
},1500);
)
过程简述
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报错[email protected]: request has been deprecated
1. npm config set registry https://registry.npm.taobao.org
2. npm config get registry
3. npm install nrm -g
4. cnpm install node-sass --save-dev
5. cnpm install sass-loader --save-dev
# 在需要转换的sass文件夹下terminal
sass (input.scss) (output.css)
# 使用Django模板标签
data1 = xx.objects.all()
data2 = {
"key": ["value1","value2"],
...
}
msg = {
"data1" : data1,
"data2" : data2,
}
{% for k,v in msg %}
<div>{{ k.xx }}div>
{% for item in k %}
<div>{{ item.yy }}div>
{% endfor %}
{% endfor %}
view.py
def page(request):
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
song = Music.objects.all() # 获取所有的book
paginator = Paginator(song, 30) # 每页10条
page = request.GET.get('page', 1) # 获取页面请求的page页码,默认为第1页
currentPage = int(page)
try:
song_page = paginator.page(page) # book_list为page对象
except PageNotAnInteger:
song_page = paginator.page(1)
except EmptyPage:
song_page = paginator.page(paginator.num_pages)
result = {
"book_list" : song_page,
"paginator" : paginator,
"currentPage" : currentPage,
}
return render(request, "page.html",result)
.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
head>
<body>
<div class="container">
<h4>分页器h4>
<ul>
{% for book in book_list %}
<div>{{ book.title }} {{ book.length }}div>
{% endfor %}
ul>
<ul class="pagination" id="pager">
{% if book_list.has_previous %}
<li class="previous">
<a href="/page/?page={{ book_list.previous_page_number }}">上一页a>
li>
{% else %}
<li class="previous disabled"><a href="#">上一页a>li>
{% endif %}
{% for num in paginator.page_range %}
{% if num == currentPage %}
<li class="item active"><a href="/page/?page={{ num }}">{{ num }}a>li>
{% else %}
<li class="item"><a href="/page/?page={{ num }}">{{ num }}a>li>
{% endif %}
{% endfor %}
{% if book_list.has_next %}
<li class="next">
<a href="/page/?page={{ book_list.next_page_number }}">下一页a>
li>
{% else %}
<li class="next disabled"><a href="#">下一页a>li>
{% endif %}
ul>
div>
body>
html>
<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>
let element = location.href.split(',',2).at(1)
<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>
// 失焦
$("#id").blur('input',function (){})
// 聚焦
$("#id").focus('input',function (){})
// 开始输入
$("#id").on('input',function (){})
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>
style = "-webkit-transform:rotate(0deg)"
# 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')
…
# setting.py 定义接口
from django.contrib import admin
from django.urls import path
urlpatterns = [
...
path('playmusic/', views.playmusic),
...
]
…
创建基础页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>logintitle>
head>
<body>
<form action="/playmusic" method="POSTS">
<h1>用户名:<input name="musicNFT">h1>
<input type="submit" value="提交">
form>
body>
html>
用户登录展示
{% if user.is_authenticated %}
<span> {{user.username}} span>
{% endif %}
…
# project/terminal输入
python3.8 manage.py
asj!~fh%$#@#!
[xx/Jun/2022 14:20:26] "GET /xxxxx = HTTP/1.1" 3xx x
# 退出项目
control + c
返回用户登录名
用户没有登陆的时返回AnonymousUser(匿名用户)
作用:
session里设置数值,便于日后访问网页时做判断
方法:
1. 通过request.session[name]=value 【设置数值】
2. 通过request.session.get(name) 【读取数值】
3. 通过request.seesion.set_expire(value)【过期】
实现路径
// 想做一个音乐收藏功能,搞了好几天,终于搞定
// 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 %}
{% 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>
功能描述
通过"中间件"记录用户数据日志
自定义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'
...
...
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>
[方法 A]
views
def file_download(request):
filename = '下载显示的文件名'
try:
from django.utils.encoding import escape_uri_path
signed_filename = os.path.basename(file_path)
response = FileResponse(open(file_path, 'rb'))
response['content_type'] = "application/octet-stream"
response['Content-Disposition'] = "attachment; filename*=utf-8''{}".format(escape_uri_path(filename)) # 可支持中文及大文件下载
return response
except Exception:
raise Http404
html
<button href="{% url 'file_download' %}" id="">下载文件</button>
[方法 B]
js
<a id="" href="" Download=""><button>Downloadbutton>a>
<script src="../static/js/jquery-3.5.1.min.js">script>
<script>
$.ajax({
type : "get",
url : "{% url '' %}",
data : {
"xx":"",
},
success: function(data){
var filelink = data.toString()
$("#midi_return").attr("href", filelink)
},
error: function(){
alert('获取失败')
},
});
script>
# setting.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'xx/media')
# url.py
from django.conf import settings
from django.urls import re_path
from django.views.static import serve
urlpatterns = [ ...
re_path(r'media/(?P.*)' , serve, {'document_root': settings.MEDIA_ROOT}),
]
# 查看文件夹
import os
def data_view(request):
files = [i for i in os.listdir(settings.MEDIA_ROOT) if not i.startswith('.')]
result = {"files": files}
return HttpResponse(json.dumps(result))
# 页面查看或下载
https://xxx.com/media/xxx.pdf
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) 所有者和同组用户有读、写及执行权限,其他用户组没任何权限。
# (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
# 对于objects.all()/objects.filter()
# 必须进行序列化后方可进入json转化
from django.core import serializers
data = Music.objects.all()
json_data = serializers.serialize("json", data)
result = {"key":"vlaue"}
result = json.dumps(result)
return HttpResponse(result, content_type="application/json")
from urllib import parse
username = parse.unquote(request.get_full_path().split('')[1])
# 问题报错:
RuntimeWarning: DateTimeField Draw.drawdate received a naive datetime
# 解决方法:
setting.py
USE_TZ = False
import logging
logger = logging.getLogger('django')
logger.error('Something went wrong!')
# 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,
)
# 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],
)
# -*- 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)
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)
规则条件:密码必须包含字母与数字,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" >
…
# 阿里云购买轻量服务器
地址: https://www.aliyun.com/
配置: 2核 - 1GB内存 - 系统盘 40GB ESSD - 上海
价格: 80/月
镜像: Linux CentOS 8.2
# 阿里云购买轻量服务器
1. 进入阿里云控制台
2. 设定管理员密码
3. 查看公网/内网地址 (可外网访问)
4. 防火墙打开:8000(Django)/:8888(宝塔)等端口
5. 开启服务器
# 4. 硬盘挂载
…
MacOS安装 Royal-TSX
1. 使用Royal-TSX代替XShell 远程连接服务器SSH/FTPS
2. Plugins添加Terminal/File Transfer两款插件
3. 新建一个Terminal窗口并输入对应的服务器的信息
4. 指定用户名和密码
5. 完成登录
服务器下载宝塔脚本(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. 注册宝塔会员并一键安装
1. 腾讯云 -> 申请免费SSL
2. 腾讯云 -> 下载SSL文件
3. 腾讯云 -> 安全组开启443端口
4. 宝塔面板 -> 网站 -> 设置 -> SSL -> 粘贴(key/pem)
-> 证书夹 -> 部署SSL
5. 验证 http -> https
1. 确认本地网络是否为(真)公有ip(查看路由器LanIP与WanIP是否一致)
若不一致则属于NAT模式,需致电运营商客服,改为公有ip。话术:监控需要
2. 致电运营商客服报修,将光猫更改为PPOE拨号模式
3. 购买华硕官方固改路由器 wifi-6
4. 登录后台设置PPOE拨号、DDNS、DMZ、外网
5. 手机断网连接测试是否成功
依赖环境打包
# 项目所在文件夹terminal输入
pip freeze > requirements.txt
静态文件打包
python manage.py collectstatic
项目文件打包
将项目文件夹移动至云服务器内
安装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需要填你的端口)
static-map = /static=/www/wwwroot/(项目名|可替换)/collect_static
注:collect_static需与前期setting设置中STATIC_ROOT一致
静态素材缺失卡了整整一天,试了很多方式包括调整反向代理location,
调整setting等均无效果。最终无意在处理no Internet serve问题时获得答案
总结下来就是不要放弃寻找心中的答案!
安全起见在项目打包上传宝塔前于MacOS环境下完成static素材收集
测试成功显示静态素材
…
PS:网站名申请若有疑问,腾讯会电话申请人并给到一些建议
记得要精准核对电话中的中文字,以免产生误解
…
支付宝支付参考文章
cnblogs.com/xiaolu915/p/10528155.html