irpas技术客

【实战】Django从零搭建个人网站_代码储蓄罐_django个人网页设计

网络 1762

Django从零搭建个人网站 导览前言一、环境介绍二、安装测试1. 框架安装2. 查看版本3. 新建项目4. 新建App5. 运行项目6. 测试项目7. 局域配置8. include9. 自定端口10. 局域测试 三. 账号功能1. 创建模型2. 管理数据3. 登录验证4. 创建账号5. 退出登录6. 弹窗提示7. 邮件分发8. 浏览权限9. 密码重置10. 加密解密11. 请求限制12. 禁止IP13. 404页面14. 关闭Debug模式15. RestAPI 四、前端调试1. 下载模板2. html调式3. 导入html4. 导入static5. html映射6. 测试服务7. 调试结果8. loading动画9. 页面显参10. jQuery1. 概念简述2. 后台API3. HTML标签传参4. 传参不跳转5. 传参且跳转页面6. 跳转页面带参7. 定时自动跳转8. 输入时按钮定时隐藏与显示9. SCSS10. 双重For循环11. 分页显示12. 音频播放器13. Split分割字符串14. 鼠标长按触发15. input监听16. JS接收API返回值17. Safari圆角失效 五、简易API1. 创建接口函数2. 创建Urls配置3. 前端页面4. API交互调试5. Request6. 收藏功能(实例)7. 用户行为日志8. 搜索匹配9. 购物车10. 用户上传11. 用户下载12. 用户读取13. 用户读写权限14. User模型扩展15. 笔记若干1. Serializers2. 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 views urlpatterns = [ path('', views.index, name='index'), ]

插入URLconf

# project/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path('(appitem)/', include('(appitem).urls')), path('admin/', admin.site.urls), ] 9. 自定端口

python文件更改

# manage.py输入 execute_from_command_line(['manage.py', 'runserver', '0.0.0.0:2516'])

启用端口

# project/terminal输入 python3.8 manage.py runserver 0.0.0.0:8000

清空端口(若需)

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

10. 局域测试 # 浏览器输入 http://192.168.x.xx:8000/

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

python3.8 manage.py runserver 0.0.0.0:8000

三. 账号功能 1. 创建模型

编辑模型

from django.db import models from django.utils.translation import gettext_lazy as _ from django.utils.encoding import smart_str class Music(models.Model): title = models.CharField(_(u'名称'),max_length=250) author = models.CharField(_(u'作者'),max_length=250) url = models.CharField(_(u'地址'),max_length=250) createdate = models.DateTimeField(_ (u'创建时间'), auto_now_add=True, blank=True ) def __unicode__(self): return smart_str(self.title) class Meta: verbose_name = _(u'音乐库') verbose_name_plural = _(u'音乐库') ordering=['-createdate']

激活模型

INSTALLED_APPS = [ ... '(app-item).apps.(App-item)Config', ]

模型迁移

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

模型删除(若需)

python3.8 manage.py migrate (App-item) zero 2. 管理数据

创建管理员

python3.8 manage.py createsuperuser

通知Admin站点

from django.contrib import admin from .models import * admin.site.register(Music)

数据管理页面

python3.8 manage.py runserver 0.0.0.0:8000 http://0.0.0.0:8000/admin/login/?next=/admin/

离线数据库编辑

# 离线浏览及编辑数据库 DB Browser for SQLite Version 3.12.2 # python 数据处理工具 # --------------------------------------------------------- # 数据插入 def multi_sql_insert(db_path,entities,point_id,point_init,table): ''' - 数据嵌入 :param db_path: '~/db.splite3' :param entities: [(0,'','',...),(0,'','',...),...] :param point_id: 'id,title,author,...' :param point_init: '?,?,?,...' :param table: 'Database' ''' import sqlite3 db = sqlite3.connect(db_path) cursorObj = db.cursor() cursorObj.execute(f'SELECT * FROM {table}') rowcount = cursorObj.fetchall() for e in range(len(entities)): entity = entities[e] add_row = str(len(rowcount)+e) cursorObj.execute( f"INSERT INTO {table}({point_id}) " f"VALUES({point_init})", tuple([add_row] + list(entity[1:])) ) db.commit() db.close() # 数据修改 def multi_sql_update(db_path,updates,table,condition): ''' - 数据修改更新 :param db_path: '~/db.splite3' :param updates: [['title','John']] -> (point:value) :param table: 'Database' :param condition: 'id = 1' / 'WHERE price > 100'/ ... ''' import sqlite3 db = sqlite3.connect(db_path) for updt_i in range(len(updates)): update_point = updates[updt_i][0] updt_value = updates[updt_i][1] def sql_update(db,condition,update_point,updt_value,table): cursorObj = db.cursor() cursorObj.execute(f'UPDATE {table} SET ' f'{update_point} = "{updt_value}" ' f'where {condition}' ) db.commit() sql_update(db,condition,update_point,updt_value,table) db.close() # 数据查询 def sql_fetch(db_path,point): ''' - 数据查询 :param db_path: '~/db.splite3' :param point: 'id,name' :return: rows = ('','','',...) ''' import sqlite3 db = sqlite3.connect(db_path) cursorObj = db.cursor() cursorObj.execute(f'SELECT {point} FROM {table}') rows = cursorObj.fetchall() db.close() # print(rows) return rows # 数据删除 def sql_delete(db_path,table,condition): ''' - 数据行删除 :param db_path: '~/db.splite3' :param table: 'Database' ''' import sqlite3 db = sqlite3.connect(db_path) cursorObj = db.cursor() cursorObj.execute(f'DELETE FROM {table} WHERE {condition}') db.commit() db.close()

Q & A a. 账号记得 | 密码忘了

终端输入 python3 manage.py shell >>> from django.contrib.auth.models import User >>> user = User.objects.get(username='你的管理员账号') >>> user.set_password('你想设置的新密码') >>> user.save() >>> quit()

b. 账号忘了 | 密码忘了:

终端输入 python3 manage.py shell >>> from django.contrib.auth.models import User >>> user = User.objects.get(pk=1) >>> user <User: 管理员账号> >>> user = User.objects.get(username='你的管理员账号') >>> user.set_password('你想设置的新密码') >>> user.save() >>> quit()

3. 登录验证

前端获得用户验证输入(Get)| 后端对比用户验证信息(Auth)

a. 输入模型

# models.py from django.db import models from django.contrib.auth.models import User class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') USERNAME_FIELD = 'username' username = models.CharField('username', max_length=128, blank=True) password = models.CharField('password', max_length=50, blank=True) mod_date = models.DateTimeField('Last modified', auto_now=True) class Meta: verbose_name = 'User Profile' def __str__(self): return "{}'s profile".format(self.user.__str__())

注:一定要设置USERNAME_FIELD

b. 配置注册

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

c. urlpatterns

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

d. 添加视图

from django.contrib.auth import authenticate,login def login_check(requst): if requst.method == 'GET': username = requst.GET.get('username') password = requst.GET.get('password') user = authenticate(username=username,password=password) if user is not None: login(requst, user) return redirect('index') else: return render(requst,'signin.html') else: return render(requst,'signin.html')

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

e. 页面引入

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

4. 创建账号 from django.contrib.auth.models import User def create_user(requst): if requst.method == "GET": username = requst.GET.get('username') password = requst.GET.get('password') email = requst.GET.get('email') confirm = requst.GET.get("confirm_password") if password == "" or username == "": alert_box(requst, "用户名或密码不能为空") elif password != confirm: alert_box(requst, "两次密码不一致") elif User.objects.filter(username=username): alert_box(requst, "该用户名已存在") else: new_user = User.objects.create_user(username=username, password=password,email=email) new_user.save() return redirect('index') return render(requst, 'signup.html')

5. 退出登录 def signout(requst): logout(requst) return render(requst,'signin.html')

6. 弹窗提示

a. view.py

from django.contrib import messages def alert_box(requst,message): messages.success(requst,message)

b. html 加载在body内

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

7. 邮件分发 setting.py# 发送邮件配置 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # smpt服务器地址 [建议使用163] EMAIL_HOST = 'smtp.163.com' # 邮箱端口 EMAIL_PORT = 25 # 发送邮件的邮箱 EMAIL_HOST_USER = 'xxx@163.com' # ***客户端授权密码!!** # 需要去163邮箱开启smpt服务并获得唯一授权码 EMAIL_HOST_PASSWORD = '!@#$%^&*$%^@#' # 收件人看到的发件人 EMAIL_FROM = 'xxx<xxx@163.com>' # 避免报错加上 DEFAULT_FROM_EMAIL = 'xxx@163.com' views.pyfrom django.shortcuts import render, HttpResponse from django.core.mail import send_mail, EmailMultiAlternatives from django.conf import settings from email.header import make_header from email.mime.text import MIMEText from email.mime.image import MIMEImage import os def send_simple_email(request): subject = "[邮件主题]" message = "[邮件内容]" from_email = settings.EMAIL_FROM recipient_list = ["xx@xx.com","xxx@xx.com"...] ret = send_mail(subject, message, from_email, recipient_list) return HttpResponse(ret) def send_complex_email(request): subject = '' text_content = '' html_content = '' from_email = settings.DEFAULT_FROM_EMAIL receive_email_addr = ["xx@xx.com"] msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr) msg.attach_alternative(html_content, "text/html") # 发送图像 html1 = "<div><img src='cid:imgid'></div>" msg_html_img = MIMEText(html1, 'html', 'utf-8') msg.attach(msg_html_img) file_path = os.path.join(settings.BASE_DIR, "static/kd.png") with open(file_path, "rb") as f: msg_img = MIMEImage(f.read()) msg_img.add_header('Content-ID', 'imgid') msg.attach(msg_img) # 发送txt附件 file_path = os.path.join(settings.BASE_DIR, "日志.txt") text = open(file_path, 'rb').read() file_name = os.path.basename(file_path) b = make_header([(file_name, 'utf-8')]).encode('utf-8') msg.attach(b, text) # 发送jpg附件 file_path = os.path.join(settings.BASE_DIR, "test.jpg") text = open(file_path, 'rb').read() file_name = os.path.basename(file_path) b = make_header([(file_name, 'utf-8')]).encode('utf-8') msg.attach(b, text) # 发送xlsx附件 file_path = os.path.join(settings.BASE_DIR, "test.xlsx") text = open(file_path, 'rb').read() file_name = os.path.basename(file_path) b = make_header([(file_name, 'utf-8')]).encode('utf-8') msg.attach(b, text) # msg.attach_file(file_path) msg.send() return HttpResponse("发送完成") urls.pyfrom django.urls import path from . import views urlpatterns = [ path("send_simple_email/", views.send_simple_email, name="send_simple_email"), path("send_complex_email/", views.send_complex_email, name="send_complex_email"), ]

8. 浏览权限

创建装饰器

# project/app/decorator.py from django.shortcuts import render from django.http import HttpResponse def already_login(func): def alr_login(request, *args, **kwargs): outsec = 60*60*3 request.session.set_expiry(outsec) # 超过秒数后失效 # import datetime # outday = datetime.datetime.now() + datetime.timedelta(days=30) # request.session.set_expiry(outday) # 超过日期后时效 # request.session.set_expiry(0) # 关闭浏览器后失效 # request.session.set_expiry(None) # 遵循全局失效策略 if request.user.is_authenticated: return func(request, *args, **kwargs) else: return render(request,'signin.html') return alr_login # def validate_permission(func): # def valid_per(request, *args, **kwargs): # group_id = request.session.get('group_id') # if group_id == 0: # return func(request, *args, **kwargs) # else: # return HttpResponse("无权访问") # return valid_per

配置需要权限的视图

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

向email发送验证码

def email_code(requst): username = requst.GET.get('username') has_username = User.objects.filter(username=username) if has_username: def random_str(randomlength=4): import random codekey = '' chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' length = len(chars) - 1 for i in range(randomlength): codekey += chars[random.randint(0, length)] return codekey subject = '验证码' text_content = '重置密码' code = random_str() requst.session["code"]=code requst.session["username"] = username email = User.objects.get(username=username).email requst.session["email"] = email html_content = f'<h3>重置验证码,' \ f'请谨慎保管</h3><h1>' \ f'<font style="background-color:darkgray;color: #3F3F3F" >{code}</font></h1>' from_email = settings.DEFAULT_FROM_EMAIL receive_email_addr = [email] msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr) msg.attach_alternative(html_content, 'text/html') msg.send() return redirect('password') else: alert_box(requst,'Email尚未注册') return redirect('forgot')

修改密码

@validate_codemail def pswrd_reset(requst): code = requst.GET.get("code") password = requst.GET.get("password") email = requst.session['email'] username = requst.session["username"] user = User.objects.get(username=username) confirm_password = requst.GET.get("confirm_password") if confirm_password == password: has_username = User.objects.filter(username=username,email=email) if has_username: if code == requst.session['code']: user.set_password(password) user.save() requst.session.flush() alert_box(requst,'密码重置成功') return redirect('signin') else: requst.session.flush() alert_box(requst,'验证码不正确') return redirect('forgot') else: requst.session.flush() alert_box(requst, '用户名尚未注册') return redirect('forgot') else: alert_box(requst, '两次密码不一致') return redirect('password')

装饰器

def validate_codemail(func): def valid_codmail(request, *args, **kwargs): session = len(request.session.items()) # 判断是否有验证码请求记录 if session > 0: return func(request, *args, **kwargs) else: return redirect('forgot') return valid_codmail

urls

urlpatterns = [ ... path('email_code/', views.email_code, name='email_code'), path('pswrd_reset/',views.pswrd_reset,name='pswrd_reset'), ]

前端

... <form action="{% url 'email_code' %}"... method="GET"> <input name="username"... placeholder="用户名"> ... <input name="code"... placeholder="验证码"> <input name="password" ... placeholder="新 密 码"> <input name="confirm_password" ... placeholder="确认密码">

10. 加密解密 from django.contrib.auth.hashers import make_password, check_password sha256_password = make_password("123456", None, 'pbkdf2_sha256') # 加密 checkbool = check_password("123456",'pbkdf2_sha256')# 校验 11. 请求限制 安装插件pip3.8 insatall django-ratelimit 设限条件@ratelimit(key='ip', rate='5/h',block=True) your_views(requst): ... 参考链接https://django-ratelimit.readthedocs.io/en/stable/usage.html

12. 禁止IP

安装GeoLite2并下载IP数据库

# 1. 安装geoip2 pip3.8 install geoip2 # 2. 下载City和Country数据库 搜索-> GeoLite2免费下载 ...

创建 middleware.py ,与 settings.py 同目录下

from django.http import HttpResponse from django.utils.deprecation import MiddlewareMixin # 1.10.x class TestMiddleware(MiddlewareMixin): def process_view(self,request,view_func,*view_args,**view_kwargs): def get_ip_location(): ''' -获得ip位置信息 :param request: :param datapath: :return: country(string) ''' import geoip2.database datapath = os.path.join(IPDIA_ROOT,'GeoLite2-City.mmdb') reader = geoip2.database.Reader(datapath) try: response = reader.city(ip) country = response.country.iso_code cityname = response.city.name data = {'country': country, 'city': cityname} return data['country'] except: local_ips = ['127.0.0.1'] if ip in local_ips: return 'LOCAL' else: return 'Unkown IP' if 'HTTP_X_FORWARDED_FOR' in request.META: ip = request.META['HTTP_X_FORWARDED_FOR'] else: ip = request.META['REMOTE_ADDR'] # 使用GeoLite2数据库判别 id_country = get_ip_location() print(f'[{id_country}] -> {ip} ') countries = ['CN','TW','HK','LOCAL'] if id_country not in countries: return HttpResponse('<h1 style="opacity:0.2">no permission</h1>')

setting.py

MIDDLEWARE = [ ... '(django项目名).middleware.TestMiddleware', ] 13. 404页面

urls.py

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

views.py

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

14. 关闭Debug模式 setting.pyDEBUG = False terminal<!-- MacOS系统下Debug默认为True,关闭成False后静态素材和样式丢失的解决办法 --> python3.8 manage.py runserver 0.0.0.0:8000 --insecure 15. RestAPI

安装组件

pip3.8 install djangorestframework pip3.8 install markdown pip3.8 install django-filter

添加项目

INSTALLED_APPS = [ ... 'rest_framework', ]

配置模型

# setting.py REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' ] }

创建API

# (project)/urls.py from django.contrib import admin from django.urls import include, path from (appitem).models import * from rest_framework import routers, serializers, viewsets # Serializers define the API representation. class MusicSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Music fields = ['title', 'author', 'url', 'createdate'] # ViewSets define the view behavior. class MusicViewSet(viewsets.ModelViewSet): queryset = Music.objects.all() serializer_class = MusicSerializer # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() router.register(r'music', MusicViewSet) urlpatterns = [ path('', include(router.urls)), path('(appitem)/', include('(appitem).urls')), path('admin/', admin.site.urls), path(r'^api-auth/', include('rest_framework.urls')) ]

运行项目

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


四、前端调试 1. 下载模板 # 浏览器输入: 挑选下载一个喜欢的html模板

注: 找一些比较成熟综合素材网 UI/UX设计可以降维用于日常测试 适合平时制作ppt和影视资源使用

2. html调式

映射跳转链接

# 替换模板中的页面跳转 | 新建substitution.py def hyperlink_url_modify(): from tqdm import tqdm page_path = '~' pages = [i for i in os.listdir(page_path) if i.endswith('.html')] # 遍历所有同类html模板 for page_i in tqdm(range(len(pages))): index_file = os.path.join(page_path,pages[page_i]) f = open(index_file,'r') # 链接映射 lines = [] for line in f.readlines(): new_line = line for page_ii in range(len(pages)): html_nm = os.path.basename(pages[page_ii]) page_nm = html_nm.split('.html')[0] source_nm = '"'+ html_nm + '"' subs_nm = '"{% url ' + '\'' + page_nm + '\'' + ' %}"' if '404' in page_nm: subs_nm = '"{% url ' + '\'' + 'page_404' + '\'' + ' %}"' new_line = new_line.replace(source_nm, subs_nm) lines.append(new_line) new_lines = ''.join(lines) # 覆盖原文件 f = open(index_file,'w') f.write(new_lines) f.close() print('[ Static Folder Modify Finished !]')

映射static物料

# 替换模板中的物料静态地址 | 新建substitution.py def static_url_modify(): page_path = '~' pages = [i for i in os.listdir(page_path) if i.endswith('.html')] for page_i in tqdm(range(len(pages))): index_file = os.path.join(page_path,pages[page_i]) f = open(index_file,'r') lines = [] for line in f.readlines(): new_line = line \ # .replace('"image', '"../static/image') # .replace('"icon', '"../static/icon') # .replace('"css', '"../static/css')\ # .replace('"js', '"../static/js')\ # .replace('"img', '"../static/img') # .replace('assets','../static') # .replace('(assets', '(../static/assets') \ # .replace('"assets/', '"../static/assets/') \ # .replace('./','../static/')\ # .replace('"images/','"../static/images/')\ # .replace('"css/', '"../static/css/') \ # .replace('"libs/', '"../static/libs/') \ # .replace('"scripts/', '"../static/scripts/')\ # .replace('\'images', '\'../static/images') lines.append(new_line) new_lines = ''.join(lines) f = open(index_file,'w') f.write(new_lines) f.close()

批量views

# views.py def multi_def_generate(): page_path = '~' pages = [i for i in os.listdir(page_path) if i.endswith('.html')] lines = '' for page_i in tqdm(range(len(pages))): page_nmfull = pages[page_i] page_nm = pages[page_i].replace('.html','') if page_nm.startswith('404'): def_pattern = f'def page_40(request):\n return render(request,\'{str(page_nmfull)}\')\n\n' else: def_pattern = f'def {page_nm}(request):\n return render(request,\'{str(page_nmfull)}\')\n\n' lines += def_pattern print(lines)

批量urlspattern

# (project)/urls.py def urlspatterns_generate(): page_path = '~' pages = [i for i in os.listdir(page_path) if i.endswith('.html')] lines = '' for page_i in tqdm(range(len(pages))): page_nm = pages[page_i].replace('.html','') if page_nm.startswith('404'): urlspattern = f'path(\'{page_nm}/\', views.page_404, name=page_404),\n' else: urlspattern = f'path(\'{page_nm}/\', views.{page_nm}, name=\'{page_nm}\'),\n' lines += urlspattern print(lines)

3. 导入html

创建templates

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

导入html

# 将模板索引页拖入templates中 (project)/(item)/templates/index.html (project)/(item)/templates/home.html ...

views新建

# views.py输入 ( path一定要设置 name= ' ' 方便后面调用网页 ) from django.urls import path from . import views urlpatterns = [ path('index/', views.index,name='index'), path('home/', views.home,name='home'), ... ]

urls新建

# url.py输入 from django.shortcuts import render def index(request): return render(request, 'index.html') def home(request): return render(request, 'home.html') ...

DIRS设置

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

INSTALLED_APPS设置

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

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

setting.py 设置

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

manage.py 收集

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

注释

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

5. html映射 跳转链接映射# html链接映射(pagename在urlpatterns里设置) # 替换前 <a href="about.html">关于</a> # 替换后 <a href="{% url 'home' %}">关于</a> {% url 'home' %} 物料链接映射# 物料链接映射 ../static/images/xxx.png ../static/css/xxx.css ../static/js/xxx.js

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

7. 调试结果

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

8. loading动画 <!-- loaing effects --> {% if # %} <div class="loading"> <span></span> <span></span> <span></span> <span></span> <span></span> </div> <style type="text/css"> * { padding: 0; margin: 0; } .loading{ height: 25px; margin: 100px auto; display: flex; justify-content: center; } .loading span{ width: 6px; height: 100%; border-radius: 4px; background-color: lightgreen; animation: load 1s ease infinite; margin: 0 2px; } @keyframes load{ 0%, 100% { transform: scaleY(1.2); background-color: lightgreen; } 50% { transform: scaleY(0.3); background-color: lightblue; } } .loading span:nth-child(2){ animation-delay: 0.2s; } .loading span:nth-child(3){ animation-delay: 0.4s; } .loading span:nth-child(4){ animation-delay: 0.6s; } .loading span:nth-child(5){ animation-delay: 0.8s; } </style> {% endif %} <!-- end loaing effects -->

9. 页面显参

view.py

# 需以字典方式传参 def yourView(request): var = xxx return render(request,'phones.html',context={'msg':var}) or def yourView(request): var = { '':'' } return render(request,'phones.html',context=var)

.html

<span> {{ msg }} </span>

ForEach

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

10. jQuery 1. 概念简述 jQuery - 用于简化选取HTML元素,并对它们执行"操作" https://blog.csdn.net/u012932876/article/details/117465004?spm=1001.2014.3001.5506 2. 后台API <button id="btn">ClickMe</button> <script src="../static/js/jquery-3.5.1.min.js"></script> <script> $(function (){ $("#btn").click(function (){ $.ajax({ type : "post", url : "{% url 'handle' %}" data : {"username":"jacky"}, dataType : "json", }); }); }) </script> def handle(): if request.method == 'POST': collection = Favorite() username = request.POST.get('username') 3. HTML标签传参 <div id=""></div> <scrip> $("#id").text(data["msg"]) </scrip> 4. 传参不跳转 $ajax({ ... }); return false; 5. 传参且跳转页面 $ajax({ ... success: function(data){ window.location.href="{% url 'xxx' %}"; } }); 6. 跳转页面带参 // 增加js文件 <getparam.js> (function ($) { $.extend({ //1、取值 $.Request("name") Request: function (name) { var sValue = location.search.match(new RegExp("[\?\&]" + name + "=([^\&]*)(\&?)", "i")); //decodeURIComponent解码 return sValue ? decodeURIComponent(sValue[1]) : decodeURIComponent(sValue); }, }); })(jQuery); // 前一页传参 <script> $(function(){ ... name = ""; age = ""; url = "xxx.html?name="+name+"&age="+age;//此处拼接内容 window.location.href = url; }) </script> // 后一页获参 <script> function getData(){ var name = $.Request("name"); var age = $.Request("age"); } getData() </script> 7. 定时自动跳转 <header style="text-indent: 2em; margin-top: 30px;"> <span id="time">4</span><a href="index.html" title="点击访问">跳过</a> </header> <script> function delayURL() { var delay = document.getElementById("time").innerHTML; var t = setTimeout("delayURL()", 1000); if (delay > 0) { delay--; document.getElementById("time").innerHTML = delay; } else { clearTimeout(t); window.location.href = "index.html"; } } delayURL() </script> 8. 输入时按钮定时隐藏与显示 var t $("#ipt").on('input',function (){ clearTimeout(t) $("#ipt_btn").hide() t = setTimeout(function (){ $("#ipt_btn").show() },1500); ) 9. SCSS

过程简述

codepen上有很多有趣的svg和动画效果,很多是scss格式而不是css 所以需要研究如何在HTML中引入,大概路径如下: 1. 安装npm 2. 安装sass 3. scss转换css 4. 引入css

安装步骤

1. brew install node # 报错 (2.0.Error: Command failed with exit 128: git) # 解决(查看-复制-运行) 2.1 brew -v # 重装 3. brew install node # 版本 4. npm -v # 安装cnpm(淘宝镜像) npm install -g cnpm --registry=https://registry.npm.taobao.org # 若安装nrm报错request@2.88.2: request has been deprecated 1. npm config set registry https://registry.npm.taobao.org 2. npm config get registry 3. npm install nrm -g 4. cnpm install node-sass --save-dev 5. cnpm install sass-loader --save-dev # 在需要转换的sass文件夹下terminal sass (input.scss) (output.css) 10. 双重For循环 # 使用Django模板标签 data1 = xx.objects.all() data2 = { "key": ["value1","value2"], ... } msg = { "data1" : data1, "data2" : data2, } <!-- Html Page --> {% for k,v in msg %} <div>{{ k.xx }}</div> {% for item in k %} <div>{{ item.yy }}</div> {% endfor %} {% endfor %} 11. 分页显示

view.py

def page(request): from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger song = Music.objects.all() # 获取所有的book paginator = Paginator(song, 30) # 每页10条 page = request.GET.get('page', 1) # 获取页面请求的page页码,默认为第1页 currentPage = int(page) try: song_page = paginator.page(page) # book_list为page对象 except PageNotAnInteger: song_page = paginator.page(1) except EmptyPage: song_page = paginator.page(paginator.num_pages) result = { "book_list" : song_page, "paginator" : paginator, "currentPage" : currentPage, } return render(request, "page.html",result)

.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <div class="container"> <h4>分页器</h4> <ul> <!-- 上一页 --> {% for book in book_list %} <div>{{ book.title }} {{ book.length }}</div> {% endfor %} </ul> <ul class="pagination" id="pager"> {% if book_list.has_previous %} <li class="previous"> <a href="/page/?page={{ book_list.previous_page_number }}">上一页</a> </li> {% else %} <li class="previous disabled"><a href="#">上一页</a></li> {% endif %} <!-- 页数 --> {% for num in paginator.page_range %} {% if num == currentPage %} <li class="item active"><a href="/page/?page={{ num }}">{{ num }}</a></li> {% else %} <li class="item"><a href="/page/?page={{ num }}">{{ num }}</a></li> {% endif %} {% endfor %} <!-- 下一页 --> {% if book_list.has_next %} <li class="next"> <a href="/page/?page={{ book_list.next_page_number }}">下一页</a> </li> {% else %} <li class="next disabled"><a href="#">下一页</a></li> {% endif %} </ul> </div> </body> </html> 12. 音频播放器 <!-- 适用于分页及不同歌单播放的综合情况 --> <audio id="audioplayer" controls="controls" hidden><source src=""/></audio> <script src="../static/js/jquery-3.5.1.min.js"></script> <script> if (document.readyState){ var tapid_cache,playlist_cache $("#pausebtn{{ var_1 }}_{{ var_2 }}").hide() document.getElementById("audioplayer").src = "{{ song.url }}" let btn = document.getElementById("playmusic{{ var_1 }}_{{ var_2 }}") btn.onclick = function (){ var audioplayer = document.getElementById("audioplayer") // -> 暂停状态(点击前) if (audioplayer.paused){ // 同一首歌继续播放 if (tapid_cache == "{{ var_2 }}"){ audioplayer.play() } // 不同歌曲切换播放 else { // 通过加载src来重头播放达到停止播放效果 document.getElementById("audioplayer").src = "{{ song.url }}" audioplayer.play() } $("#playbtn{{ var_1 }}_{{ var_2 }}").hide() $("#pausebtn{{ var_1 }}_{{ var_2 }}").show() tapid_cache = "{{ var_2 }}" playlist_cache = "{{ var_1 }}" } // -> 播放状态(点击前) else { // 点击歌曲在歌单内序号相同 if (tapid_cache == "{{ var_2 }}"){ // 同一张歌单 if (playlist_cache == "{{ var_1 }}"){ audioplayer.pause() $("#playbtn{{ var_1 }}_{{ var_2 }}").show() $("#pausebtn{{ var_1 }}_{{ var_2 }}").hide() } // 不同歌单 else { // 停止正在播放内容 audioplayer.pause() $("#pausebtn"+ playlist_cache + "_" + tapid_cache).hide() $("#playbtn" + playlist_cache + "_" + tapid_cache).show() // 更新新内容地址 document.getElementById("audioplayer").src = "{{ song.url }}" // 播放新内容 audioplayer.play() $("#playbtn{{ var_1 }}_{{ var_2 }}").hide() $("#pausebtn{{ var_1 }}_{{ var_2 }}").show() } } // 点击歌曲在歌单内序号不同 else { // 正在播放图标初始化 $("#pausebtn"+playlist_cache + "_" + tapid_cache).hide() $("#playbtn" +playlist_cache + "_" + tapid_cache).show() // 更新新内容地址 document.getElementById("audioplayer").src = "{{ song.url }}" audioplayer.play() $("#playbtn{{ var_1 }}_{{ var_2 }}").hide() $("#pausebtn{{ var_1 }}_{{ var_2 }}").show() } // 记录当前播放标志缓存 tapid_cache = "{{ var_2 }}" playlist_cache = "{{ var_1 }}" } // 音乐播放完初始化播放按钮(实时监视) document.getElementById("audioplayer").ontimeupdate = function (){ if (document.getElementById("audioplayer").ended){ $("#pausebtn"+playlist_cache + "_" + tapid_cache).hide() $("#playbtn" +playlist_cache + "_" + tapid_cache).show() } // 非鼠标点击情况下启动或停止播放键监视按钮样式 if (document.getElementById("audioplayer").paused){ $("#pausebtn"+playlist_cache + "_" + tapid_cache).hide() $("#playbtn" +playlist_cache + "_" + tapid_cache).show() } else { $("#pausebtn"+playlist_cache + "_" + tapid_cache).show() $("#playbtn" +playlist_cache + "_" + tapid_cache).hide() } } } $("#close{{ var_1 }}").click(function (){ document.getElementById("audioplayer").pause() {% for song in songs %} $("#playbtn{{ var_1 }}_{{ var_2 }}").show() $("#pausebtn{{ var_1 }}_{{ var_2 }}").hide() tapid_cache = '' playlist_cache = '' {% endfor %} }) } </script> 13. Split分割字符串 let element = location.href.split(',',2).at(1) 14. 鼠标长按触发 <script> let num = 0, tid; const btn = window.document.getElementById("") // 触发事件 btn.onclick = function(e){ triggerEvent() } // 鼠标抬起时 btn.onmousedown = function(e){ let hold_time = 500 // 设置定时,触发事件 tid = setInterval(function(){ triggerEvent() }, hold_time) } // 鼠标移开时,清除计时器 btn.onmouseup = function(e){ clearInterval(tid) } btn.onmouseout = function(e){ clearInterval(tid); // 清除计时器 } // 触发事件 function triggerEvent() { num ++; // 当点击若干秒后执行操作 let action_duration = 5 if (num > action_duration) { btn.innerHTML = num $.ajax({ type: '', url: '', data: {}, success: function(){ window.location.href = '' }, }) } } </script> 15. input监听 // 失焦 $("#id").blur('input',function (){}) // 聚焦 $("#id").focus('input',function (){}) // 开始输入 $("#id").on('input',function (){}) 16. JS接收API返回值

Views

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

JQuery

<script src="../static/js/jquery-3.5.1.min.js"></script> <script> $.ajax({ type:"post", url :"", data:{"":""}, success: function (msg){ alert(msg['pass_value') } }) </script> 17. Safari圆角失效 <!-- 在外层添加样式,解决Safari圆角加载动画后失效问题 --> style = "-webkit-transform:rotate(0deg)"
五、简易API 1. 创建接口函数 # views.py 定义接口 from django.shortcuts import render from django.http.response import HttpResponse import json # def playmusic(requst): # if requst.method == 'GET': # result = {} # musicNFT = requst.GET.get('musicNFT') # result['musicNFT'] = musicNFT # result = json.dumps(result) # return HttpResponse(result) # else: # return render(requst,'playmusic.html') def playmusic(requst): if requst.method == 'POST': result = {} musicNFT = requst.GET.get('musicNFT')) result['musicNFT'] = musicNFT result = json.dumps(result) return HttpResponse(result) else: # 此处可对返回值做自定义函数处理 return render(requst,'playmusic.html')

2. 创建Urls配置 # setting.py 定义接口 from django.contrib import admin from django.urls import path urlpatterns = [ ... path('playmusic/', views.playmusic), ... ]

3. 前端页面

创建基础页面

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form action="/playmusic" method="POSTS"> <h1>用户名:<input name="musicNFT"></h1> <input type="submit" value="提交"> </form> </body> </html>

用户登录展示

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

4. API交互调试 运行网页服务# project/terminal输入 python3.8 manage.py 用户输入内容提交asj!~fh%$#@#! 服务器反馈[xx/Jun/2022 14:20:26] "GET /xxxxx = HTTP/1.1" 3xx x 退出网页服务# 退出项目 control + c 5. Request request.user返回用户登录名 用户没有登陆的时返回AnonymousUser(匿名用户) request.session作用: session里设置数值,便于日后访问网页时做判断 方法: 1. 通过request.session[name]=value 【设置数值】 2. 通过request.session.get(name) 【读取数值】 3. 通过request.seesion.set_expire(value)【过期】 6. 收藏功能(实例)

实现路径

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

model.py

# 建立收藏数据模型 class Favorite(models.Model): from datetime import datetime user_id = models.ForeignKey(to=User, on_delete=models.CASCADE) # 谁收藏 song_id = models.ForeignKey(to=Music, on_delete=models.CASCADE) # 收藏了哪首 collectdate = models.DateTimeField(default=datetime.now) # 收藏时间 class Meta: verbose_name = _(u'Favorite') verbose_name_plural = _(u'Favorite') ordering=['-collectdate']

migrate

# 一系列操作... 大致如下: # 通知admin 1. admin.site.register(Favorite) # 数据库建档迁移 2. makemigrations & migrate ...

view.py

@already_login # 要求用户登录 def handle(request): import json from MusicDatabase.models import Favorite from datetime import datetime user_id = request.user.id # 调取正在收藏的用户信息 if request.method == 'POST': song_id = request.POST.get('song_id') # 刚才收藏的歌曲id request.session["song_id"] = song_id # 记录刚才收藏的歌曲id备用 favorites = Favorite.objects.filter(user_id=user_id) # 找出所有该用户的收藏 if favorites.filter(song_id=song_id): # 检查是否已收藏 favorites.filter(song_id=song_id).delete() # 若已收藏夹则取消收藏 request.session["result"] = { # 返回参数 "collect":False, # 歌曲最终未被收藏 "song_id":song_id # 被收藏的歌曲 } print('[取消收藏]') else: collection = Favorite() collection.user_id_id = user_id # 刚才收藏的用户id collection.song_id_id = song_id # 刚才收藏的歌曲id collection.collectdate = datetime.now() # 刚才收藏的时间 collection.save() # 记录收藏信息 request.session["result"] = { "collect": True, "song_id": song_id } print('[新增收藏]') result = json.dumps({"":""}) # 必须正确返回json后进入GET return HttpResponse(result, content_type="application/json") elif request.method == 'GET': # 返回GET结果 result = json.dumps(request.session["result"]) return HttpResponse(result,content_type="application/json") else: # 请求类型检查(大小写/拼写) print('request method error')

SVG绘制与CSS

{# ?? } {% for song in msg %} <style> svg { cursor: pointer; overflow: visible; width: 36px; fill:#AAB8C2; fill-rule: evenodd; } </style> <svg id="{{ song.id }}" viewBox="467 392 58 57" xmlns="http://·/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. 腾讯云 -> 申请免费SSL 2. 腾讯云 -> 下载SSL文件 3. 腾讯云 -> 安全组开启443端口 4. 宝塔面板 -> 网站 -> 设置 -> SSL -> 粘贴(key/pem) -> 证书夹 -> 部署SSL 5. 验证 http -> https 4. DDNS认证 1. 确认本地网络是否为(真)公有ip(查看路由器LanIP与WanIP是否一致) 若不一致则属于NAT模式,需致电运营商客服,改为公有ip。话术:监控需要 2. 致电运营商客服报修,将光猫更改为PPOE拨号模式 3. 购买华硕官方固改路由器 wifi-6 4. 登录后台设置PPOE拨号、DDNS、DMZ、外网 5. 手机断网连接测试是否成功
八、Django迁移 1. MacOS-Django 项目打包

依赖环境打包

# 项目所在文件夹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天完成备案腾讯云域名解析 (·)宝塔接入站点测试域名完成验证 PS:网站名申请若有疑问,腾讯会电话申请人并给到一些建议 记得要精准核对电话中的中文字,以免产生误解

十一、付款接入

支付宝支付参考文章

cnblogs.com/xiaolu915/p/10528155.html 十二、理解原理


持续更新…


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #django个人网页设计 #django #零基础 #网站 #音乐