首页 文章 精选 留言 我的

精选列表

搜索[step],共2634篇文章
优秀的个人博客,低调大师

基于django的视频点播网站开发-step2-搭建环境

本讲中,带领大家搭建开发环境。我们会依次安装python、pip、django、mysql和其他的一些必要类库。 安装python 安装 Python 非常简单,去 Python 官方网站 找到 Python3 的下载地址,根据你的系统选择32位或者64位的安装包,下载好后双击安装即可。 安装完毕后,在命令行输入 python -v ,如果输出了 Python 的版本号,说明 Python 已安装成功。 $ python3 -V Python 3.7.0 安装pip 如果已经安装了python3, 那么pip3会自动的被安装。 安装django 安装django非常简单,一条命令搞定。 pip3 install django 安装mysql 由于该项目使用的是mysql数据库,所以需要安装mysql。 如果你使用的是Windows或macOS系统,那么可以去 MySQL官网 直接下载安装包,一步步安装即可(笔者安装的是MySQL 5.7)。安装过程中会提示创建输账号和密码,一定要记得创建哦~。 如果你使用的是Ubuntu系统,可以使用apt-get命令安装 sudo apt-get install mysql-server 安装完毕后,可使用mysql -V查看mysql版本号。 然后创建数据库,命名为video CREATE DATABASE video CHARACTER SET utf8; 安装PyCharm PyCharm 是一款功能强大的 Python 编辑器,具有跨平台性。 我们项目所有功能的开发都是在pycharm上面完成的。 到PyCharm官网下载PyCharm安装包。选择对应系统(Windows/Mac)的版本下载。一般学习用直接安装社区版本即可足够用。 下载之后,双击点下一步安装即可。 其他安装 另外,下面这些是项目开发过程中会用到的类库,放到了requirements.txt里面 django_ratelimit==1.1.0 (限制流量的类库) PyMySQL==0.9.2 (连接mysql数据库的驱动) django_chunked_upload==1.1.3 (文件上传库) sorl-thumbnail==12.5.0 (图片显示工具) pillow==5.3.0 (图片显示) 可以使用pip3直接安装 如pip3 install -r requiredments.txt 创建Django工程 一切就绪,我们创建django工程,仅需要一行命令 django-admin startproject videoproject 创建之后,可使用pycharm打开videoproject文件夹,查看文件结构 pycharm是很强大的,有自带的命令行工具(Terminal),版本控制工具(Version Control)。 打开Terminal,输入 python3 manage.py runserver 来启动项目,在之后的开发中,我们会经常用到该命令行来调试程序。 命令行输出 Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. 然后在浏览器地址栏输入http://127.0.0.1:8000/ 即可看到django默认首页了。 项目配置 项目的配置文件位于videoproject/videoproject/settings.py 配置编码 首先需要配置的是文字编码格式,django默认的编码是英语格式,我们把它改成中文格式,需要修改下面几个变量的值。 LANGUAGE_CODE = 'zh-hans' # zh-hans为中文编码 TIME_ZONE = 'Asia/Shanghai' # 国际时区改成中国时区 USE_I18N = True USE_L10N = True USE_TZ = False 配置static 然后还需要配置资源文件目录,用于存储CSS、Javascript、Images等文件。这里我们设置目录为/static/ STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"), ) 配置数据库 然后还需要配置数据库信息,django默认使用的是sqlite数据库,我们修改为mysql数据库。找到DATABASES节点,修改为如下代码。其中,NAME为数据库名,USER为mysql的用户名,PASSWORD为密码,HOSY为127.0.0.1,PORT为3306 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'video', 'USER': 'root', 'PASSWORD': '123456', 'HOST':'127.0.0.1', 'PORT':'3306', } } 配置好数据库之后,还需要在videoproject/videoproject/__init__.py安装mysql驱动,只需要写入代码: import pymysql pymysql.install_as_MySQLdb() 上面代码运行的前提是你电脑上已经安装了PyMySQL类库。 最后可再次运行工程,检查配置是否正确。

优秀的个人博客,低调大师

基于django的视频点播网站开发-step1-项目介绍

本文将会对该项目进行一个简单的介绍,包括项目名称、项目背景、项目功能、技术栈等等。 项目名称 基于django的视频点播网站开发 项目背景 学习完毕python和django之后,想找个项目练练手,本来想写个博客项目练手,无奈别人已经写过了,所以笔者就打算写一个视频点播网站,因为笔者非常喜欢观看视频,尤其是YouTube、bilibili都是笔者非常喜欢的视频网站,所以想自己实现一个简单的视频点播网站,学以致用。 项目功能 本项目分为前台和后台 前台功能 视频列表展示 视频播放详情 详情评论 个人中心 后台功能 视频管理 评论管理 用户管理 反馈管理 演示地址 https://v.mypython.me/ 后台管理地址 https://v.mypython.me/myadmin/ 测试账号 用户名:admin123 密码:admin123 适合人群 python初级学员、大学生、系统设计人员、 技术栈 python/django/nginx/mysql/semantic-css/jquery/html 源码 https://github.com/geeeeeeeek/videoproject/ QA互动 Email: lovezww2011@gmail.com 个人博客: https://mypython.me github: https://github.com/geeeeeeeek/

优秀的个人博客,低调大师

基于django的视频点播网站开发-step4-首页功能

在本讲中,我们开始首页功能的开发,在开发过程中,大家将会学习到Django中的通用视图类、分页对象paginator以及foreignKey外键的使用。 效果演示 整体功能 大家可先通过 网站演示地址 浏览一下首页的效果。我们首页呢,比较简洁大方,让人一目了然。我这样设计的目的呢,是让大家把精力放到学习django上面来,不必过度关注花哨的页面效果。 我们把首页拆解为4个小的业务模块来开发,分别是:列表显示、分页功能、搜索功能、分类功能。下面我们分别对这四个功能模块进行开发讲解。 开发思路 开发一个功能的基本思路是:先新建应用,然后分析功能涉及到哪些业务,从而分析出需要的数据库字段,然后编写模型,之后就是展示阶段,通过url路由配置视图函数,来将模型里面的数据显示出来。 ok,我们通过命令建立应用,命名为video。执行后,django将为我们新建video文件夹。 python3 manage.py startapp video 下面的功能模块开发都在该应用(video)下进行。 建模型 此处,我们需要建立两个模型,分别是分类表(classification)和视频表(video)。他们是多对一的关系(一个分类对应多个视频,一个视频对应一个分类)。 首先编写Classification表,在model.py下面,我们键入如下代码。字段有title(分类名称)和status(是否启用) class Classification(models.Model): list_display = ("title",) title = models.CharField(max_length=100,blank=True, null=True) status = models.BooleanField(default=True) class Meta: db_table = "v_classification" 字段说明 title 分类名称。数据类型是CharField,最大长度为max_length=100,允许为空null=True status 是否启用。数据类型是BooleanField,默认为default=True db_table 表名 然后编写Video模型,根据网站业务,我们设置了title(标题)、 desc(描述)、 classification(分类)、file(视频文件)、cover(封面)、status(发布状态)等字段。其中classification是一个ForeignKey外键字段,表示一个分类对应多个视频,一个视频对应一个分类(多对一) class Video(models.Model): STATUS_CHOICES = ( ('0', '发布中'), ('1', '未发布'), ) title = models.CharField(max_length=100,blank=True, null=True) desc = models.CharField(max_length=255,blank=True, null=True) classification = models.ForeignKey(Classification, on_delete=models.CASCADE, null=True) file = models.FileField(max_length=255) cover = models.ImageField(upload_to='cover/',blank=True, null=True) status = models.CharField(max_length=1 ,choices=STATUS_CHOICES, blank=True, null=True) create_time = models.DateTimeField(auto_now_add=True, blank=True, max_length=20) 字段说明 title 视频标题。数据类型是charField,最大长度为max_length=100,允许为空null=True desc 视频描述。数据类型是charField,最大长度为max_length=255,允许为空null=True file 视频文件地址。数据类型是fileField。其中存的是视频文件的地址,在之后的视频管理中我们将会对视频的上传进行具体的讲解。 cover 视频封面。数据类型是ImageField。存储目录为upload_to='cover/',允许为空null=True status 视频状态。是一个选择状态,用choices设置多选元祖。 create_time 创建时间。数据类型是DateTimeField 。设置自动生成时间auto_now_add=True ForeignKey 表明一种一对多的关联关系。比如这里我们的视频和分类的关系,一个视频只能对应一个分类,而一个分类下可以有多个视频。 更多关于ForeinkKey的说明,可以参看 ForeignKey官方介绍 列表显示 要想访问到首页,必须先配置好路由。在video下建立urls.py文件,写入如下代码 from django.urls import path from . import views app_name = 'video' urlpatterns = [ path('index', views.IndexView.as_view(), name='index'), ] 一条path语句就代表一条路由信息。这样我们就可以在浏览器输入127.0.0.1:8000/video/index来访问首页了。 显示列表数据非常简单,我们使用django中内置的视图模版类ListView来显示,首先在view.py中编写IndexView类,用它来显示列表数据。键入如下代码 class IndexView(generic.ListView): model = Video template_name = 'video/index.html' context_object_name = 'video_list' 此处,我们使用了django提供的通用视图类ListView, ListView使用很简单,只需要我们简单的配置几行代码,即可将数据库里面的数据渲染到前端。比如上述代码中,我们配置了 model = Video, 作用于Video模型 template_name = 'video/index.html' ,告诉ListView要使用我们已经创建的模版文件。 context_object_name = 'video_list' ,上下文变量名,告诉ListView,在前端模版文件中,可以使用该变量名来展现数据。 之后,我们在templates文件夹下,建立video目录,用来存放视频相关的模板文件,首先我们创建首页文件index.html。并将刚才获取到的数据显示出来。 <div class="ui grid"> {% for item in video_list %} <div class="four wide column"> <div class="ui card"> <a class="image"> {% thumbnail item.cover "300x200" crop="center" as im %} <img class="ui image" src="{{ im.url }}"> {% empty %} {% endthumbnail %} <i class="large play icon v-play-icon"></i> </a> <div class="content"> <a class="header">{{ item.title }}</a> <div class="meta"> <span class="date">发布于{{ item.create_time|time_since}}</span> </div> <div class="description"> {{ item.view_count}}次观看 </div> </div> </div> </div> {% empty %} <h3>暂无数据</h3> {% endfor %} </div> 通过for循环,将video_list渲染到前端。这里我们使用到了django中的内置标签,比如for语句、empty语句。这些都是django中非常常用的语句。在之后的教程中我们会经常遇到。 另外,还使用了thumbnail标签来显示图片,thumbnail是一个很常用的python库,常常被用来做图片显示。 显示结果如下 分类功能 在写分类功能之前,我们先学习一个回调函数 get_context_data() 这是ListView视图类中的一个函数,在 get_context_data() 函数中,可以传一些额外内容到模板。因此我们可以使用该函数来传递分类数据。 要使用它,很简单。 只需要在IndexView类下面,追加get_context_data()的实现即可。 class IndexView(generic.ListView): model = Video template_name = 'video/index.html' context_object_name = 'video_list' def get_context_data(self, *, object_list=None, **kwargs): context = super(IndexView, self).get_context_data(**kwargs) classification_list = Classification.objects.filter(status=True).values() context['classification_list'] = classification_list return context 在上述代码中,我们将分类数据通过Classification.objects.filter(status=True).values()从数据库里面过滤出来,然后赋给classification_list,最后放到context字典里面。 在前端模板(templates/video/index.html)中,就可以通过classification_list来取数据。添加代码 <div class="classification"> <a class="ui red label" href="">全部</a> {% for item in classification_list %} <a class="ui label" href="">{{ item.title }}</a> {% endfor %} </div> 显示效果如下 当然现在只是实现了分类展示效果,我们还需要继续实现点击效果,即点击不同的分类,显示不同的视频列表。 我们先给每个分类按钮加上href链接 <div class="classification"> <a class="ui red label" href="{% url 'home' %}">全部</a> {% for item in classification_list %} <a class="ui label" href="?c={{ item.id }}">{{ item.title }}</a> {% endfor %} </div> 通过添加?c={{ item.id }} 这里用c代表分类的id,点击后,会传到视图类中,在视图类中,我们使用 get_queryset() 函数,将get数据取出来。通过self.request.GET.get("c", None) 赋给c,判断c是否为None,如果为None,就响应全部,如果有值,就通过get_object_or_404(Classification, pk=self.c)先获取当前类,然后classification.video_set获取外键数据。 def get_queryset(self): self.c = self.request.GET.get("c", None) if self.c: classification = get_object_or_404(Classification, pk=self.c) return classification.video_set.all().order_by('-create_time') else: return Video.objects.filter(status=0).order_by('-create_time') 更多关于ForeignKey的使用方法,可参考 这里 分页功能 在Django中,有现成的分页解决方案,我们开发者省了不少事情。如果是简单的分页,只需要配置一下paginate_by即可实现。 class IndexView(generic.ListView): model = Video template_name = 'video/index.html' context_object_name = 'video_list' paginate_by = 12 c = None painate_by = 12每页显示12条 这样每页的分页数据就能正确的显示出来来,现在来完善底部的页码条。 页码列表需要视图类和模板共同来完成,我们先来写视图类。在前面我们已经写过get_context_data了,该函数的主要功能就是传递额外的数据给模板。这里,我们就利用get_context_data来传递页码数据。 我们先定义一个工具函数,叫get_page_list。 在项目根目录下,新建一个文件helpers.py该文件当作一个全局的工具类,用来存放各种工具函数。把get_page_list放到helpers.py里面 该函数用来生产页码列表,不但这里可以使用,以后在其他地方也可以调用该函数。 def get_page_list(paginator, page): page_list = [] if paginator.num_pages > 10: if page.number <= 5: start_page = 1 elif page.number > paginator.num_pages - 5: start_page = paginator.num_pages - 9 else: start_page = page.number - 5 for i in range(start_page, start_page + 10): page_list.append(i) else: for i in range(1, paginator.num_pages + 1): page_list.append(i) return page_list 分页逻辑: if 页数>=10: 当前页<=5时,起始页为1 当前页>(总页数-5)时,起始页为(总页数-9) 其他情况 起始页为(当前页-5) 举例: 假设一共16页 情况1: 当前页==5 则页码列表为[1,2,3,4,5,6,7,8,9,10] 情况2: 当前页==8 则页码列表为[3,4,5,6,7,8,9,10,11,12] 情况3: 当前页==15 则页码列表为[7,8,9,10,11,12,13,14,15,16] 当然你看到这个逻辑会有点乱,建议大家读着代码,多试验几遍。 当拿到页码列表,我们继续改写get_context_data()函数。 将获取到的classification_list追加到context字典中。 def get_context_data(self, *, object_list=None, **kwargs): context = super(IndexView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) classification_list = Classification.objects.filter(status=True).values() context['c'] = self.c context['classification_list'] = classification_list context['page_list'] = page_list return context 你或许对 paginator = context.get('paginator') page = context.get('page_obj')这两行代码感到陌生,我们只需要知道context.get('page_obj')返回的是当前页码,context.get('paginator')返回的是分页对象,就够了。更加详细的介绍,可参考官方。 当数据传递给模板之后,模板就负责显示出来就行了。 因为分页功能比较常用,所以需要把它单独拿出来封装到一个单独的文件中,我们新建templates/base/page_nav.html文件。然后在index.html里面我们将该文件include进来。 {% include "base/page_nav.html" %} 打开page_nav.html,写入代码 {% if is_paginated %} <div class="video-page"> <div class="ui circular labels"> {% if page_obj.has_previous %} <a class="ui circular label" href="?page={{ page_obj.previous_page_number }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">&lt;</a> {% endif %} {% for i in page_list %} {% if page_obj.number == i %} <a class="ui red circular label">{{ i }}</a> {% else %} <a class="ui circular label" href="?page={{ i }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">{{ i }}</a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a class="ui circular label" href="?page={{ page_obj.next_page_number }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">&gt;</a> {% endif %} </div> </div> {% endif %} 上面代码中,我们用到了page_obj对象的几个属性:has_previous、previous_page_number、next_page_number。通过这几个属性,即可实现复杂的页码显示效果。其中我们还这href里面加了 {% if c %}&c={{c}} 代表分类的id。 搜索功能 要实现搜索,我们需要一个搜索框 因为搜索框是很多页面都需要的,所以我们把代码写到templates/base/header.html文件里面。 <div class="ui small icon input v-video-search"> <input class="prompt" value="{{ q }}" type="text" placeholder="搜索视频" id="v-search"> <i id="search" class="search icon" style="cursor:pointer;"></i> </div> 点击搜索或回车的代码写在了static/js/header.js里面。 我们还需要配置一下路由,添加一行搜索的路由。 app_name = 'video' urlpatterns = [ path('index', views.IndexView.as_view(), name='index'), path('search/', views.SearchListView.as_view(), name='search'), ] 搜索路由指向的视图类为SearchListView 下面我们来写SearchListView的代码 class SearchListView(generic.ListView): model = Video template_name = 'video/search.html' context_object_name = 'video_list' paginate_by = 8 q = '' def get_queryset(self): self.q = self.request.GET.get("q","") return Video.objects.filter(title__contains=self.q).filter(status=0) def get_context_data(self, *, object_list=None, **kwargs): context = super(SearchListView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) context['page_list'] = page_list context['q'] = self.q return context 关键代码就是Video.objects.filter(title__contains=self.q).filter(status=0)title__contains是包含的意思,表示查询title包含q的记录。利用filter将数据过滤出来。这里写了两层过滤,第一层过滤搜索关键词,第二层过滤status已发布的视频。 另外,这里也用到了get_context_data来存放额外的数据,包括分页数据、q关键词。 配置模板文件是templates/video/search.html 因此模板代码写在search.html里面 <div class="ui unstackable items"> {% for item in video_list %} <div class="item"> <div class="ui tiny image"> {% thumbnail item.cover "300x200" crop="center" as im %} <img class="ui image" src="{{ im.url }}"> {% empty %} {% endthumbnail %} </div> <div class="middle aligned content"> <a class="header" href="{% url 'video:detail' item.pk %}">{{ item.title }}</a> </div> </div> {% empty %} <h3>暂无数据</h3> {% endfor %} </div> {% include "base/page_nav.html" %} 搜索功能效果

优秀的个人博客,低调大师

NOIP-C++大神培养计划Step1.1.1基础算法——模拟算法1

模拟算法,可以说是最基础的算法了。它的基本定义没太多意思:就是去模拟题目的要求。题意要你怎么做,你就怎么做,看懂了题目,基本上就会做了。 举一个大家耳熟能详的栗子。 A+B Problem给定两个整数A和B,输出他们的和。 这是学习C++的最简单的例题之一。代码如下: #include<iostream> using namespace std; int main() { int a,b; cin>>a>>b; cout<<a+b<<endl; return 0; } 那为什么要举这么简单的栗子呢? 不知你有没有注意,其实这就是一个模拟。题目要你算A+B,你就算,这就是模拟。我们在学习C++语言时做的题很多都有模拟的气息,所以,这一个算法大家应该会比较熟悉了。 有些简单的模拟题呢,很快就能做出来,当然个别难题还是要绞尽脑汁来想了。 下面,我们就来讲解一下往年NOIP中,考到的一些模拟。 栗1.1.1-1 洛谷1003 铺地毯https://www.luogu.org/problemnew/show/P1003 题目描述为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形地毯。一共有 n 张地毯,编号从 1。到n。现在将这些地毯按照编号从小到大的顺序平行于坐标轴先后铺设,后铺的地毯覆盖在前面已经铺好的地毯之上。地毯铺设完成后,组织者想知道覆盖地面某个点的最上面的那张地毯的编号。注意:在矩形地毯边界和四个顶点上的点也算被地毯覆盖。输入输出格式输入格式: 输入共n+2行第一行,一个整数n,表示总共有n张地毯接下来的n行中,第 i+1行表示编号i的地毯的信息,包含四个正整数a,b,g,k ,每两个整数之间用一个空格隔开,分别表示铺设地毯的左下角的坐标(a,b)以及地毯在x轴和y轴方向的长度第n+2行包含两个正整数x和y,表示所求的地面的点的坐标(x,y) 输出格式: 输出共1行,一个整数,表示所求的地毯的编号;若此处没有被地毯覆盖则输出−1 输入输出样例输入样例#1: 31 0 2 30 2 3 32 1 3 32 2输出样例#1: 3 输入样例#2:31 0 2 30 2 3 32 1 3 34 5输出样例#2:-1说明【样例解释1】如下图,1 号地毯用实线表示,2 号地毯用虚线表示,3 号用双实线表示,覆盖点(2,2)的最上面一张地毯是 3 号地毯。 【数据范围】对于30% 的数据,有 n≤2 ;对于50% 的数据,0≤a,b,g,k≤100;对于100%的数据,有 0≤n≤10,000 ,0≤a,b,g,k≤100,000。noip2011提高组day1第1题 一般来说,Day1T1总是最简单的。那么,我们就来分析一下,这题该怎么去模拟。 解题思路: 首先,我们看到了题目,第一眼就想到了用二维数组,枚举每一个地毯,再给数组赋值便好! 来看一看代码: #include<iostream> using namespace std; int s[10001][10001],n;//二维数组用于存每一点的覆盖情况 int x,y,a,b;//地毯的大小 int X,Y;//询问坐标 int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>x>>y>>a>>b; for(int j=x;j<=x+a;j++)//枚举X轴 { for(int t=y;t<=y+b;t++)//枚举Y轴 { s[j][t]=i;//二维数组赋值 } } } cin>>X>>Y; if(s[X][Y]) cout<<s[X][Y]<<endl; else cout<<"-1"<<endl; return 0; } 但是,注意: 0≤n≤10,000 ,0≤a,b,g,k≤100,000。 数据范围是阻止我们AC的重要敌人,你要是开一个像s 100001 的数组,空间会爆啊啊啊! 那怎么办呢?我们来想一想办法吧。 有一个真理:NOIP真题一定有标程,也就是答案,所以,我们珂以根据数据范围来选择算法或数据类型。 10^6啊,很大,100000*100000是绝对不可能的。 那我们想一想: 如果我们用数组来存x,y,a,b,在倒序枚举每一块地板,判断(X,Y)是否在地毯所覆盖的区间内。若没有,则输出-1 这个可以堪称完美算法了。我们只需要开100000*4的数组就可以了,复杂度是O(n),完虐上一个算法QAQ 接下来我们来证明一下这个算法。 若一个点存在于一块地毯内(不是最上面的),那么他一定不存在与比它所在的地毯上层的地毯上,否则就跳出循环了。 这个算法证明很容易。记住,在考场上证明你的算法很重要。 那么,我们来看代码: #include<iostream> using namespace std; int s[10001][5],n;//二维数组存x,y,a,b int X,Y;//所求坐标 int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>s[i][1]>>s[i][2]>>s[i][3]>>s[i][4]; s[i][3]+=s[i][1]; s[i][4]+=s[i][2]; } //s数组作用为:s[i][1]:第i张地毯起始点的x轴;s[i][2]:第i张地毯起始点的y轴;s[i][3]:第i张地毯终点的x轴;s[i][4]:第i张地毯终点的x轴 cin>>X>>Y; for(int i=n;i>=1;i--) { if(s[i][1]<=X&&s[i][2]<=Y&&s[i][3]>=X&&s[i][4]>=Y) { cout<<i<<endl; return 0; } } cout<<-1<<endl;//没搜到输出-1. return 0; } 这一题就算是完整地讲完了。大家可以好好品味,去洛谷上AC这题吧! 栗1.1.1-2洛谷P1008 三连击https://www.luogu.org/problemnew/show/P1008题目描述将1,2,⋯,9共9个数分成3组,分别组成3个三位数,且使这3个三位数构成1:2:3的比例,试求出所有满足条件的3个三位数。输入输出格式 输入格式: 木有输入 输出格式: 若干行,每行3个数字。按照每行第1个数字升序排列。 输入输出样例输入样例#1:无输出样例#1:192 384 576 ... (输出被和谐了)QAQ 这就是一道大模拟题,连输入都没有! 怪不得是普及组,来练练水 那么,这题怎么来做呢? 我们来思考一下:让我们依次枚举三个三位数的个、十、百位,若遇见满足a:b:c=1:2:3时就输出,其实就行了。 我们来看看代码吧: #include<cstdio> #include<cstring> int i,j,a; bool v[10];//a[i]表示第i个数已经用过了 int main() { for(i=192;i<=327;i++)//第一个数最小192,最大327。用数学计算的,可以减小复杂度。 { memset(a,0,sizeof(a)); a=0; v[i%10]=v[i/10%10]=v[i/100]=v[i*2%10]=v[i*2/10%10]=v[i*2/100]=v[i*3%10]=v[i*3/10%10]=v[i*3/100]=1;//统计数字 for(j=1;j<=9;j++) a+=v[j];//a表示1-9这些数字是否全部齐了 if(a==9) printf("%d %d %d\n",i,i*2,i*3);//如果齐了就输出 /* 算法介绍: 这里的i表示第一个数,上面那个很长的语句就是将3个三位数的个、十、百位统计,a来计算用了几个数,若用了9个,输出。 */ } return 0; } 这就是这题的解法。题目本身没有太大的难度,只是要求一个思维的缜密,详解已经在代码中了,我想这题的理解应该没什么问题。照常,去AC一下吧! 好了,今天的课程就到这里了,下一次我们将继续通过例题讲解熟悉模拟算法。我们下次见! 如果大家有问题或者想和我讨论,可以加我的QQ:907916611,我很乐意为您解答!

优秀的个人博客,低调大师

基于django的视频点播网站开发-step14-数据总览功能

数据总览功能,是对网站中产生的数据进行一个统计,统计出视频数、发布数、用户数、评论数,等等。让管理者对网站数据有一个清晰的认识,做到心中有数。 在本站中,笔者一共列举了下面几种数据:视频数、发布中未发布、用户数、用户新增、评论数、评论新增,等几项内容。 我们把所有的数据都封装到了一个函数里面,即 IndexView 它位于后台管理的首页。 path('', views.IndexView.as_view(), name='index'), IndexView代码如下 class IndexView(AdminUserRequiredMixin, generic.View): def get(self, request): video_count = Video.objects.get_count() video_has_published_count = Video.objects.get_published_count() video_not_published_count = Video.objects.get_not_published_count() user_count = User.objects.count() user_today_count = User.objects.exclude(date_joined__lt=datetime.date.today()).count() comment_count = Comment.objects.get_count() comment_today_count = Comment.objects.get_today_count() data = {"video_count": video_count, "video_has_published_count": video_has_published_count, "video_not_published_count": video_not_published_count, "user_count": user_count, "user_today_count": user_today_count, "comment_count": comment_count, "comment_today_count": comment_today_count} return render(self.request, 'myadmin/index.html', data) 与视频相关的统计,我们封装到了Video的models.py下面, class VideoQuerySet(models.query.QuerySet): # 视频总数 def get_count(self): return self.count() # 发布数 def get_published_count(self): return self.filter(status=0).count() # 未发布数 def get_not_published_count(self): return self.filter(status=1).count() 以上数据,大都使用了filter过滤器进行了过滤,最后通过count()函数返回给业务方。 与用户相关的统计,我们直接通过count和exclude将相关数据过滤出来。 与评论相关的统计,封装到了Comment的models.py下面, class CommentQuerySet(models.query.QuerySet): # 评论总数 def get_count(self): return self.count() # 今日新增 def get_today_count(self): return self.exclude(timestamp__lt=datetime.date.today()).count() 其中,今日新增评论,我们通过exclude来过滤时间,使用了 lt 标签来过滤。更多标签的使用方法,可参考 官方文档

优秀的个人博客,低调大师

基于django的视频点播网站开发-step15-项目部署

项目部署是一个比较大的工程,但这是笔者最喜欢的一个环节,因为在部署过程中,会遇到很多疑难问题,这些问题能促使我学到很多很多的技术。 项目部署,顾名思义,就是将自己的程序存放到远程服务器上面,让全世界的网友都能访问到。在部署之前,我们需要提前准备好一些部署的知识,包括购买服务器、安装服务器软件、服务器配置、等等。 笔者专门购买了服务器来部署我们的视频播放网站,为了避免备案,笔者购买了国外的服务器。 购买服务器 购买服务器的地方有很多,国内有阿里云、腾讯云、百度云等,国外有aws、bwh、Linode等等。如果你是学生的话,建议购买 阿里云 的服务器,因为阿里云有学生认证,打折幅度非常大。 操作系统建议选择ubuntu系统 笔者购买的是aws服务器,操作系统是Ubuntu16.04 安装软件 需要安装的软件有nginx、mysql、python、uwsgi、django,另外还有项目的依赖库。 在Ubuntu系统安装nginx非常简单,只需一行命令 sudo apt-get install nginx nginx的常用命令有 sudo service nginx start # 启动nginx sudo service nginx restart # 重启nginx sudo service nginx stop # 关闭nginx 安装mysql sudo apt-get install mysql-server 启动mysql sudo service mysql start 安装uwsgi pip3 install uwsgi 安装django pip3 install Django 拉取代码 代码仓库为https://github.com/geeeeeeeek/videoproject.git git clone https://github.com/geeeeeeeek/videoproject.git 项目里面 uwsgi.ini 为配置文件 [uwsgi] chdir = /var/www/videoproject module = videoproject.wsgi master = true processes = 2 socket = 127.0.0.1:8001 chmod-socket = 664 uid = www-data gid = www-data vacuum = true daemonize = %(chdir)/uwsgi.log nginx就是通过该配置文件和uwsgi通信的,uwsgi是以socket方式运行。 然后运行该配置 uwsgi --ini uwsgi.ini 服务器配置 需要配置nginx在/etc/nginx/sites-enabled/下新建配置文件video.conf,并写入 upstream django { server 127.0.0.1:8001; } server { listen 80; server_name yourhost.com; # 配置成你自己的域名 rewrite ^(.*) https://$host$1 permanent; } server { listen 443; server_name yourhost.com; # 配置成你自己的域名 ssl on; ssl_certificate /home/ubuntu/1_v.mypython.me_bundle.crt; # 配置成你自己的https证书 ssl_certificate_key /home/ubuntu/2_v.mypython.me.key; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; location /static/ { alias /var/www/videoproject/static/; } location /upload/ { alias /var/www/videoproject/upload/; } location / { include uwsgi_params; uwsgi_pass django; } } nginx 与 uwsgi通信方式: server 127.0.0.1:8001 最后重启nginx即可。 访问页面

优秀的个人博客,低调大师

基于django的视频点播网站开发-step12-后台订阅功能

本讲我们会讲到一些关于发邮件的技术。我们知道,订阅功能是一个很常见的功能,当用户订阅某个网站后,网站会通过后台给用户发送网站最新的一些动向,一般是通过邮件来发送的。当你阅读完本节内容,会对发邮件的流程有一个大概的了解。 配置相关参数 发送邮件是需要配置相关参数的,且每个邮件服务商都有自己的配置值,笔者使用的是163邮箱,在settings.py追加如下配置 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 邮件配置 EMAIL_USE_SSL = True EMAIL_HOST = 'smtp.163.com' EMAIL_PORT = 465 EMAIL_HOST_USER = 'net936@163.com' EMAIL_HOST_PASSWORD = 'your pwd' 一定要记得配置 EMAIL_BACKEND ,因为django默认配置的是一种模拟发邮件的 BackEnd ,并不能使用,故要替换。 实现发邮件 我们先来写订阅推送的路由, path('subscribe/', views.SubscribeView.as_view(), name='subscribe'), 将路由设置为 SubscribeView 我们先贴出 SubscribeView 的代码 class SubscribeView(SuperUserRequiredMixin, generic.View): def get(self, request): video_list = Video.objects.get_published_list() return render(request, "myadmin/subscribe.html" ,{'video_list':video_list}) def post(self, request): video_id = request.POST['video_id'] video = Video.objects.get(id=video_id) subject = video.title context = {'video': video,'site_url':settings.SITE_URL} html_message = render_to_string('myadmin/mail_template.html', context) email_list = User.objects.filter(subscribe=True).values_list('email',flat=True) # 分组 email_list = [email_list[i:i + 2] for i in range(0, len(email_list), 2)] if email_list: for to_list in email_list: try: send_html_email(subject, html_message, to_list) except smtplib.SMTPException as e: logger.error(e) return JsonResponse({"code": 1, "msg": "发送失败"}) return JsonResponse({"code": 0, "msg": "success"}) else: return JsonResponse({"code": 1, "msg": "邮件列表为空"}) 这是一个普通的视图类,功能是由get和post共同来完成的。get中设置了要显示的模板文件myadmin/subscribe.html,它是下面的样子 当我们要给用户发送邮件的时候,需要先选择要推送的视频。然后点击通知订阅用户,即可触发ajax发送代码,ajax代码位于static/js/myadmin/send_mail.js,里面最终调用的是SubscribeView中的post方法,中post方法中,我们先通过主键获取到视频的当前实例,并且还获取到所有订阅用户的email地址放到email_list中,最后调用send_html_email将邮件发送出去,send_html_email封装在helpers.py,它的具体代码是 def send_html_email(subject, html_message, to_list): plain_message = strip_tags(html_message) from_email = settings.EMAIL_HOST_USER send_mail(subject, plain_message, from_email, to_list, html_message=html_message) def send_email(subject, content, to_list): try: message = (subject, content, settings.EMAIL_HOST_USER, to_list) send_mass_mail((message,)) except smtplib.SMTPException : print("--> send fail") return HttpResponse("fail") else: print("--> send success") return HttpResponse("success") 从代码可以看出,程序最终调用的是django自带的 send_mass_mail 函数,该函数封装了发送邮件的细节。当然还可以使用 send_mail 函数,send_mail每次发邮件都会建立一个连接,发多封邮件时建立多个连接。而 send_mass_mail 是建立单个连接发送多封邮件,所以一次性发送多封邮件时 send_mass_mail 要优于 send_mail。

优秀的个人博客,低调大师

基于django的视频点播网站开发-step13-后台反馈功能

用户反馈管理功能,是对前端用户反馈的问题进行展示,并可实现删除功能。是一个很实用的功能,也算是当代网站的标配。它可以实时的跟踪到用户对网站的各种意见和吐槽,开发者能及时修缮网站功能或者修改网站bug。 Demo地址 反馈管理包括反馈列表和反馈删除,它们的路由是 path('feedback_list/', views.FeedbackListView.as_view(), name='feedback_list'), path('feedback_delete/', views.feedback_delete, name='feedback_delete'), 分别表示反馈列表和反馈删除功能。 列表是由FeedbackListView负责来展示,代码如下 class FeedbackListView(AdminUserRequiredMixin, generic.ListView): model = Feedback template_name = 'myadmin/feedback_list.html' context_object_name = 'feedback_list' paginate_by = 10 q = '' def get_context_data(self, *, object_list=None, **kwargs): context = super(FeedbackListView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) context['page_list'] = page_list context['q'] = self.q return context def get_queryset(self): self.q = self.request.GET.get("q", "") return Feedback.objects.filter(content__contains=self.q).order_by('-timestamp') 同样是继承自ListView通用视图,同样是配置了model、template、分页。在get_context_data()函数中,传递了分页数据;在get_queryset()中,传递了搜索数据。 展示如下 当你删除一条的时候,会触发相应的ajax代码,ajax位于static/js/myadmin/feedback_list.js,ajax最终的幕后黑手是feedback_delete接口,它位于myadmin/views.py, 即 @ajax_required @require_http_methods(["POST"]) def feedback_delete(request): feedback_id = request.POST['feedback_id'] instance = Feedback.objects.get(id=feedback_id) instance.delete() return JsonResponse({"code": 0, "msg": "success"}) 获取到 feedback 实例后,通过instance.delete()删除之。

优秀的个人博客,低调大师

基于django的视频点播网站开发-step8-后台登录功能

从本讲起,我们会介绍后台管理系统的开发,后台管理,主要是对数据库中的数据进行增、删、改、查的操作,满足网站管理员对网站的管理与维护的需求。 其实,django自带的也有一个后台管理系统(/admin),但是自带的后台非常简陋,无论是界面,还是功能上,都无法满足用户的需求,因此,我们自己开发了一套后台管理系统。 demo地址 测试账号: admin123 密码: admin123 后台管理属于一个单独的模块,我们创建一个新的应用,命名为myadmin python3 manage.py startapp myadmin 好,之后的功能都是基于myadmin来实现的。 因为前面我们已经创建了user模块,所以此处的登录功能是基于之前的user模块来实现的。 首先在myadmin/urls.py中添加登录和登出的路由 from django.urls import path from . import views app_name = 'myadmin' urlpatterns = [ path('login/', views.login, name='login'), path('logout/', views.logout, name='logout'), ] 我们来写login函数 def login(request): if request.method == 'POST': form = UserLoginForm(request=request, data=request.POST) if form.is_valid(): username = form.cleaned_data.get('username') password = form.cleaned_data.get('password') user = authenticate(username=username, password=password) if user is not None and user.is_staff: auth_login(request, user) return redirect('myadmin:index') else: form.add_error('', '请输入管理员账号') else: form = UserLoginForm() return render(request, 'myadmin/login.html', {'form': form}) 这里我们使用了user模型中的一个字段is_staff,用它来表示是否是管理员,所以通过if user is not None and user.is_staff来判断管理员,如果是管理员,则auth_login登录并redirect跳转到主页。 下面我们来实现logout函数 def logout(request): auth_logout(request) return redirect('myadmin:login') 登出后,直接跳转到login页面。 效果展示

优秀的个人博客,低调大师

基于django的视频点播网站开发-step3-注册登录功能

用户注册登录是一个网站的基本功能,django对这部分进行了很好的封装,我们只需要在django的基础上做些简单的修改就可以达到我们想要的效果。在本讲中,我们会用到user中的用户授权方面的一些函数,还会对django中的user进行扩展,以及django中的form验证。 效果展示 注册页面 登录页面 创建users应用 django的设计哲学是,一个应用只提供一种功能,比如users应用只提供用户相关功能,comment应用只提供评论相关功能,这能提高代码的重复利用率。 在django中,只需要下面一条命令,即可建立users应用 python3 manage.py startapp users 建表 我们需要一个用户表,用来实现登录注册功能,虽然django已经自带来用户登录注册功能,也有相应的表,但是不符合中国人习惯,需要我们对user模型进行自定义。实现自定义User模型最简单的方式就是继承AbstractBaseUser,AbstractBaseUser实现了User的核心功能,我们只需加一些额外的字段进行补充即可。 User模型原有的字段有: username password last_login is_superuser first_name last_name email is_staff is_active date_joined 这些都是最基本的字段,并不能满足我们的需求。 根据网站自身业务,我们又添加了下面的字段 nickname(昵称) avatar(头像) mobile(手机号) gender(性别) subscribe(是否订阅) 我们只需在users/models.py中写入代码 from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): GENDER_CHOICES = ( ('M', '男'), ('F', '女'), ) nickname = models.CharField(blank=True, null=True, max_length=20) avatar = models.FileField(upload_to='avatar/') mobile = models.CharField(blank=True, null=True, max_length=13) gender = models.CharField(max_length=1, choices=GENDER_CHOICES,blank=True, null=True) subscribe = models.BooleanField(default=False) class Meta: db_table = "v_user" gender是性别字段,其中用到了choices=GENDER_CHOICES。这种方式常常用在下拉框或单多选框,例如 M对应男 F对应女。 url配置 在user文件夹下面,新建url.py文件,写入登录、注册和退出的url信息。app_name是命名空间,我们命名为'users'。 from django.urls import path from . import views app_name = 'users' urlpatterns = [ path('login/', views.login, name='login'), path('signup/', views.signup, name='signup'), path('logout/', views.logout, name='logout'), ] url路由配置好了,我们下面就开始写视图函数代码了 注册函数 我们先来写注册函数,写注册,当然得有注册表单了,幸运的是,在django中,可以用代码来生成表单。我们只需在users下新建forms.py文件,然后写入注册表单的代码。 class SignUpForm(UserCreationForm): username = forms.CharField(min_length=4,max_length=30, error_messages={ 'min_length': '用户名不少于4个字符', 'max_length': '用户名不能多于30个字符', 'required': '用户名不能为空', }, widget=forms.TextInput(attrs={'placeholder': '请输入用户名'})) password1 = forms.CharField(min_length=8, max_length=30, error_messages={ 'min_length': '密码不少于8个字符', 'max_length': '密码不能多于30个字符', 'required': '密码不能为空', }, widget=forms.PasswordInput(attrs={'placeholder': '请输入密码'})) password2 = forms.CharField(min_length=8,max_length=30, error_messages={ 'min_length': '密码不少于8个字符', 'max_length': '密码不能多于30个字符', 'required': '密码不能为空', }, widget=forms.PasswordInput(attrs={'placeholder': '请确认密码'})) class Meta: model = User fields = ('username', 'password1', 'password2',) error_messages = {'password_mismatch': '两次密码不一致', } 我们的表单一共有三个字段:username、password1、password2,它们都是CharField类型,widget分别是TextInput和PasswordInput。而且django是自带验证的,只需要我们配置好error_messages字典,当form验证的时候,就会显示我们自定义的错误信息。有了注册表单后,就可以在前端模板和视图函数中使用它。 下面是注册视图函数。 ... from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout def signup(request): if request.method == 'POST': form = SignUpForm(request.POST) if form.is_valid(): form.save() username = form.cleaned_data.get('username') raw_password1 = form.cleaned_data.get('password1') user = authenticate(username=username, password=raw_password1) auth_login(request, user) return redirect('home') else: print(form.errors) else: form = SignUpForm() return render(request, 'registration/signup.html', {'form': form}) 在signup函数中,我们通过form = SignUpForm初始化一个表单,并在render函数中传递给模板。 注册模板文件写在了templates/registration/signup.html 关键代码是 <form class="ui large form" novalidate method="post" action="{% url 'users:signup' %}" enctype="multipart/form-data" > {% csrf_token %} <div class="ui stacked segment"> <div class="field"> {{form.username}} </div> <div class="field"> {{form.password1}} </div> <div class="field"> {{form.password2}} </div> <button class="ui submit button" type="submit">注册</button> </div> {% include "base/form_errors.html" %} </form> form的action为 {% url 'users:signup' %} 即在url.py中定义的signup函数。通过post请求传递给signup,在signup中,通过如下四行代码来实现注册,并自动登录的。 username = form.cleaned_data.get('username') raw_password1 = form.cleaned_data.get('password1') user = authenticate(username=username, password=raw_password1) auth_login(request, user) 登录函数 登录函数与注册函数的模式是一样的,都是先写form,写模板,最后写视图函数。由于form和模板的代码和注册功能类似,这里就不贴了,大家可以上github查看。 重点讲一下login视图函数 def login(request): if request.method == 'POST': next = request.POST.get('next', '/') form = UserLoginForm(request=request, data=request.POST) if form.is_valid(): username = form.cleaned_data.get('username') password = form.cleaned_data.get('password') user = authenticate(username=username, password=password) if user is not None: auth_login(request, user) return redirect(next) else: print(form.errors) else: next = request.GET.get('next', '/') form = UserLoginForm() print(next) return render(request, 'registration/login.html', {'form': form, 'next':next}) 在login函数中,我们多了一个next变量,next对应的是登录后要跳转的url,其实这是一种场景,假如你在购物网站买东西,最后付款的时候,会跳转到付款页,假如你没有登录,网站会提示你登录,登录后,会再次跳转到付款页。 当然了,跳转到登录页的时候,需要你在url后追加next参数,如 aaa. com/login/?next=bbb. com 这样用户登录后就会跳到bbb. com 退出函数 from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout def logout(request): auth_logout(request) return redirect('home') 退出功能,仅需要一行代码 auth_logout(request) 就ok了。

优秀的个人博客,低调大师

基于django的视频点播网站开发-step11-后台用户管理功能

用户管理功能,包含用户添加、列表展示、编辑、删除四大功能。下面我们一一揭晓。 用户添加 我们先实现用户添加功能,我们现在urls.py下添加相关的路由 path('user_add/', views.UserAddView.as_view(), name='user_add'), path('user_list/', views.UserListView.as_view(), name='user_list'), path('user_edit/<int:pk>',views.UserEditView.as_view(), name='user_edit'), path('user_delete/', views.user_delete, name='user_delete'), 用户添加的视图类是UserAddView class UserAddView(SuperUserRequiredMixin, generic.View): def get(self, request): form = UserAddForm() return render(self.request, 'myadmin/user_add.html', {'form': form}) def post(self, request): form = UserAddForm(data=request.POST) if form.is_valid(): user = form.save(commit=False) password = form.cleaned_data.get('password') user.set_password(password) user.save() return render(self.request, 'myadmin/user_add_success.html') return render(self.request, 'myadmin/user_add.html', {'form': form}) 这是一个普通的视图类,通过get()和post()来完成用户添加的功能,get里面负责页面的展示,post里面负责逻辑处理。在get中,初始化form为UserAddForm,因为添加的用户是有类别的,所以我们在UserAddForm中应用了is_staff字段来表示管理员。在post中,我们通过user.set_password(password)来设置新密码。user.save()来保存记录到数据库。保存成功后会跳转到myadmin/user_add_success.html页面。 用户列表 用户添加成功后,当你点击用户列表,即可看到用户列表数据,用户列表我们配置的路由是 path('user_list/', views.UserListView.as_view(), name='user_list'), 使用的是UserListView视图类,该类是继承自ListView通用视图类的。因此 只需要我们简单的配置几下,就能实现列表功能。UserListView代码如下 class UserListView(AdminUserRequiredMixin, generic.ListView): model = User template_name = 'myadmin/user_list.html' context_object_name = 'user_list' paginate_by = 10 q = '' def get_context_data(self, *, object_list=None, **kwargs): context = super(UserListView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) context['page_list'] = page_list context['q'] = self.q return context def get_queryset(self): self.q = self.request.GET.get("q", "") return User.objects.filter(username__contains=self.q).order_by('-date_joined') 我们知道ListView是有多个回调函数的,这里就是通过get_context_data()和get_queryset()回调函数来实现列表中的功能的。在get_context_data()中实现了列表分页功能,在get_queryset()中实现了搜索功能。 配置的模板是myadmin/user_list.html,显示效果如下 用户编辑 当你点击编辑按钮的时候,即可进入编辑页面。对应的路由是 path('user_edit/<int:pk>',views.UserEditView.as_view(), name='user_edit'), 即UserEditView class UserEditView(SuperUserRequiredMixin, generic.UpdateView): model = User form_class = UserEditForm template_name = 'myadmin/user_edit.html' def get_success_url(self): messages.success(self.request, "保存成功") return reverse('myadmin:user_edit', kwargs={'pk': self.kwargs['pk']}) 同样是继承自UpdateView,仅需要配置好model、form_class、template_name即可,简单至极。 效果如下 用户删除 当你点击删除按钮的时候,会弹出确认框让你删除。然后网站通过ajax调用user_delete来实现真正的删除操作,其中,ajax代码位于static/js/myadmin/user_list.js 真正的删除函数是user_delete,下面是它的真面目 @ajax_required @require_http_methods(["POST"]) def user_delete(request): user_id = request.POST['user_id'] instance = User.objects.get(id=user_id) instance.delete() return JsonResponse({"code": 0, "msg": "success"}) 首先获取到当前用户的实例,然后通过 instance.delete() 删除之。

优秀的个人博客,低调大师

基于django的视频点播网站开发-step10-后台评论管理功能

本讲中,我们会讲到评论管理功能,数据库中的每一条是来自用户的评价,因此后台中的评论管理只有评论列表和评论删除功能,没有增加评论和编辑评论。 照例我们先添加评论管理的相关路由 path('comment_list/', views.CommentListView.as_view(), name='comment_list'), path('comment_delete/', views.comment_delete, name='comment_delete'), 首先是评论列表的展示,我们通过CommentListView视图类来实现,该类依然是继承ListView来实现的。代码如下 class CommentListView(AdminUserRequiredMixin, generic.ListView): model = Comment template_name = 'myadmin/comment_list.html' context_object_name = 'comment_list' paginate_by = 10 q = '' def get_context_data(self, *, object_list=None, **kwargs): context = super(CommentListView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) context['page_list'] = page_list context['q'] = self.q return context def get_queryset(self): self.q = self.request.GET.get("q", "") return Comment.objects.filter(content__contains=self.q).order_by('-timestamp') 通过继承ListView来实现评论列表的展示,通过get_context_data()来实现分页功能,通过get_queryset()来实现搜索功能。 效果如下 下面我们继续实现删除功能,该功能比较简单,只需要通过ajax将video_id传给删除接口即可,ajax的代码位于static/js/myadmin/comment_list.js,删除评论的接口是api_comment_delete,最终会调用到comment_delete,代码如下 @ajax_required @require_http_methods(["POST"]) def comment_delete(request): comment_id = request.POST['comment_id'] instance = Comment.objects.get(id=comment_id) instance.delete() return JsonResponse({"code": 0, "msg": "success"}) 逻辑还算清晰,即先拿到评论的id,然后获取到该条评论,最后instance.delete()删除之。

优秀的个人博客,低调大师

基于django的视频点播网站开发-step5-详情页功能

在本讲中,我们开始详情页功能的开发,详情页就是对单个视频进行播放并展示视频的相关信息,比如视频标题、描述、评论信息、相关推荐等。我们将会学习到通用视图类DetailView的使用、评论动态加载、以及如何通过ajax实现喜欢和收藏功能,并通过一段段很酷的代码来说明这些功能。 效果展示 整体功能 大家可先通过 网站演示地址 浏览一下网站效果。点击某个视频即可浏览详情页。详情页实现了是对单个视频进行展示,用户可看到视频的一些元信息,包括标题、描述、观看次数、喜欢数、收藏数等等。另外,网站还实现了评论功能,通过上拉网页即可分页加载评论列表,用户还能添加评论。网页侧栏是推荐视频列表,这里使用的推荐逻辑比较简单,就是推荐观看次数最多的视频。 我们把详情页分为4个小的业务模块来开发,分别是:视频详情显示、喜欢和收藏功能、评论功能、推荐功能。下面我们分别对这四个功能模块进行开发讲解。 视频详情显示 因为在上一讲中,我们已经建立了video模型,所以不必再新建模型,我们就在video模型的基础上进行扩展。上一讲,我们创建的字段有title、desc、classification、file、cover、status、create_time。这些字段目前是不够用的,我们再加几个字段,需要加观察次数、喜欢的用户、收藏的用户。video模型扩展后如下 class Video(models.Model): STATUS_CHOICES = ( ('0', '发布中'), ('1', '未发布'), ) title = models.CharField(max_length=100,blank=True, null=True) desc = models.CharField(max_length=255,blank=True, null=True) classification = models.ForeignKey(Classification, on_delete=models.CASCADE, null=True) file = models.FileField(max_length=255) cover = models.ImageField(upload_to='cover/',blank=True, null=True) status = models.CharField(max_length=1 ,choices=STATUS_CHOICES, blank=True, null=True) view_count = models.IntegerField(default=0, blank=True) liked = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="liked_videos") collected = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="collected_videos") create_time = models.DateTimeField(auto_now_add=True, blank=True, max_length=20) 新增了3个字段 view_count 观看次数。数据类型是IntegerField,默认是0 liked 喜欢的用户。数据类型是ManyToManyField,这是一种多对多的关系,表示一个视频可以被多个用户喜欢,一个用户也可以喜欢多个视频。记得设置用户表为settings.AUTH_USER_MODEL collected 收藏的用户。数据类型是ManyToManyField,这是一种多对多的关系,表示一个视频可以被多个用户收藏,一个用户也可以收藏多个视频。设置用户表为settings.AUTH_USER_MODEL 更多关于ManyToManyField的使用介绍,可以查询django官网的介绍。 下面就是详情展示阶段,我们先配置好详情页的路由信息,在video/urls.py中追加detail的路由信息。 app_name = 'video' urlpatterns = [ path('index', views.IndexView.as_view(), name='index'), path('search/', views.SearchListView.as_view(), name='search'), path('detail/<int:pk>/', views.VideoDetailView.as_view(), name='detail'), ] path('detail/<int:pk>/', views.VideoDetailView.as_view(), name='detail')即表示详情信息,注意每条视频都是有自己的主键的,所以设置路径匹配为detail/<int:pk>/,其中<int:pk>表示主键,这是django中表示主键的一种方法。这样我们就可以在浏览器输入127.0.0.1:8000/video/detail/xxx来访问详情了。 怎么显示详情呢,聪明的django为我们提供了DetailView。urls.py中设置的视图类是VideoDetailView,我们让VideoDetailView继承DetailView即可。 class VideoDetailView(generic.DetailView): model = Video template_name = 'video/detail.html' 看起来超级简单,django就是如此的酷,只需要我们配置几行代码,就能实现很强大的功能。这里我们配置model为Video模型,模板为video/detail.html,其它的工作都不用管,全都交给django去干,oh,这棒极了。 模板文件位于templates/video/detail.html,它的代码比较简单,这里就不贴了。 从效果图上我们看到还有个观看次数的展示,这里的观看次数本质上就是数据库里的一个自增字段,每次观看的时候,view_count自动加1。对于这个小需求,我们需要做两件事情,首先这video模型里面,添加一个次数自增函数,命名为increase_view_count,这很简单,如下所示: def increase_view_count(self): self.view_count += 1 self.save(update_fields=['view_count']) 然后,还需要我们在VideoDetailView视图类里面调用到这个函数。这个时候get_object()派上用场了。因为每次调用DetailView的时候,django都会回调get_object()这个函数。因此我们可以把increase_view_count()放到get_object()里面执行。完美的代码如下 class VideoDetailView(generic.DetailView): model = Video template_name = 'video/detail.html' def get_object(self, queryset=None): obj = super().get_object() obj.increase_view_count() # 调用自增函数 return obj 目前为止,我们就能在详情页看到标题、描述、观看次数、收藏次数、喜欢次数。预览如下 虽然可以显示收藏人数、喜欢人数。但是目前还没实现点击喜欢/收藏的功能。下面我们来实现。 收藏和喜欢功能 收藏和喜欢是一组动作,因此可以用ajax来实现:用户点击后调用后端接口,接口返回json数据,前端显示结果。 既然需要接口,那我们先添加喜欢/收藏接口的路由,在video/urls.py追加代码如下 path('like/', views.like, name='like'), path('collect/', views.collect, name='collect'), 由于喜欢和收藏的功能实现非常类似,限于篇幅,我们只实现喜欢功能。 我们先写like函数: @ajax_required @require_http_methods(["POST"]) def like(request): if not request.user.is_authenticated: return JsonResponse({"code": 1, "msg": "请先登录"}) video_id = request.POST['video_id'] video = Video.objects.get(pk=video_id) user = request.user video.switch_like(user) return JsonResponse({"code": 0, "likes": video.count_likers(), "user_liked": video.user_liked(user)}) 首先判断用户是否登录,如果登录了则调用switch_like(user)来实现喜欢或不喜欢功能,最后返回json。注意这里添加了两个注解@ajax_required和@require_http_methods(["POST"]),分别验证request必须是ajax和post请求。 switch_like()函数则写在了video/model.py里面 def switch_like(self, user): if user in self.liked.all(): self.liked.remove(user) else: self.liked.add(user) 所有的后端工作都准备好了,我们再把视线转向前端。前端主要是写ajax代码。 由于ajax代码量较大,我们封装到一个单独的js文件中 ==> static/js/detail.js 在detail.js中,我们先实现喜欢的ajax调用: $(function () { // 写入csrf $.getScript("/static/js/csrftoken.js"); // 喜欢 $("#like").click(function(){ var video_id = $("#like").attr("video-id"); $.ajax({ url: '/video/like/', data: { video_id: video_id, 'csrf_token': csrftoken }, type: 'POST', dataType: 'json', success: function (data) { var code = data.code if(code == 0){ var likes = data.likes var user_liked = data.user_liked $('#like-count').text(likes) if(user_liked == 0){ $('#like').removeClass("grey").addClass("red") }else{ $('#like').removeClass("red").addClass("grey") } }else{ var msg = data.msg alert(msg) } }, error: function(data){ alert("点赞失败") } }); }); 上述代码中,关键代码是$.ajax()函数,我们传入了参数:video_id和csrftoken。其中csrftoken可通过/static/js/csrftoken.js生成。在success回调中,通过判断user_liked的值来确定自己是否喜欢过,然后改变模板中相应的css。 推荐功能 每个网站都有自己的推荐功能,且都有自己的推荐逻辑。我们视点的推荐逻辑是根据访问次数最高的n个视频来降序排序,然后推荐给用户的。 实现起来非常容易,我们知道详情页实现用的是VideoDetailView,我们可以在get_context_data()中把推荐内容传递给前端模板。只需要我们改写VideoDetailView的get_context_date()函数。 def get_context_data(self, **kwargs): context = super(VideoDetailView, self).get_context_data(**kwargs) form = CommentForm() recommend_list = Video.objects.get_recommend_list() context['form'] = form context['recommend_list'] = recommend_list return context 改写后,我们添加了一行 recommend_list = Video.objects.get_recommend_list() 我们把获取推荐列表的函数get_recommend_list()封装到了Video模型里面。在Video/models.py里面我们追加代码: class VideoQuerySet(models.query.QuerySet): def get_recommend_list(self): return self.filter(status=0).order_by('-view_count')[:4] 关键是self.filter(status=0).order_by('-view_count')[:4],通过order_by把view_count降序排序,并选取前4条数据。 注意此处我们用了VideoQuerySet查询器,需要我们在Video下面添加一行依赖。表示用VideoQuerySet作为Video的查询管理器。 objects = VideoQuerySet.as_manager() 当模板拿到数据后,即可渲染显示。这里我们将推荐侧栏的代码封装到templates/video/recommend.html里面。 # templates/video/recommend.html {% load thumbnail %} <span class="video-side-title">推荐列表</span> <div class="ui unstackable divided items"> {% for item in recommend_list %} <div class="item"> <div class="ui tiny image"> {% thumbnail item.cover "300x200" crop="center" as im %} <img class="ui image" src="{{ im.url }}"> {% empty %} {% endthumbnail %} </div> <div class="middle aligned content"> <a class=" header-title" href="{% url 'video:detail' item.pk %}">{{ item.title }}</a> <div class="meta"> <span class="description">{{ item.view_count }}次观看</span> </div> </div> </div> {% empty %} <h3>暂无推荐</h3> {% endfor %} </div> 并在detail.html中将它包含进来 {% include "video/recommend.html" %} 评论功能 评论区位于详情页下侧,显示效果如下。共分为两个部分:评论form和评论列表。 评论功能是一个独立的模块,该功能通用性较高,在其他很多网站中都有评论功能,为了避免以后开发其他网站时重复造轮子,我们建立一个新的应用,命名为comment python3 manage.py startapp comment 接下来,我们创建comment模型 # 位于comment/models.py class Comment(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) nickname = models.CharField(max_length=30,blank=True, null=True) avatar = models.CharField(max_length=100,blank=True, null=True) video = models.ForeignKey(Video, on_delete=models.CASCADE) content = models.CharField(max_length=100) timestamp = models.DateTimeField(auto_now_add=True) class Meta: db_table = "v_comment" user 用户。数据类型是ForeignKey,外键是settings.AUTH_USER_MODEL,并设置为级联删除on_delete=models.CASCADE nickname 用户昵称。数据类型是CharField。 avatar 头像。数据类型是CharField。 video 对应的视频。数据类型是ForeignKey,对应Video模型,级联删除 on_delete=models.CASCADE content 评论内容。 数据类型是CharField。 timestamp 评论时间。 数据类型是DateTimeField。 有了模型之后,我们就可以专心写业务代码了,首先在comment下建立路由文件urls.py。并写入代码: from django.urls import path from . import views app_name = 'comment' urlpatterns = [ path('submit_comment/<int:pk>',views.submit_comment, name='submit_comment'), path('get_comments/', views.get_comments, name='get_comments'), ] 我们配置了两条路由信息:评论提交 和 获取评论。 提交评论,需要一个form,我们把form放到video/forms.py from django import forms from comment.models import Comment class CommentForm(forms.ModelForm): content = forms.CharField(error_messages={'required': '不能为空',}, widget=forms.Textarea(attrs = {'placeholder': '请输入评论内容' }) ) class Meta: model = Comment fields = ['content'] 然后在video/views.py的VideoDetailView下添加form的相关代码。 class VideoDetailView(generic.DetailView): model = Video template_name = 'video/detail.html' def get_object(self, queryset=None): obj = super().get_object() obj.increase_view_count() return obj def get_context_data(self, **kwargs): context = super(VideoDetailView, self).get_context_data(**kwargs) form = CommentForm() context['form'] = form return context 在get_context_data()函数里面,我们把form传递给模板。 同样的,提交评论也是异步的,我们用ajax实现,我们打开static/js/detail.js,写入 // 提交评论 var frm = $('#comment_form') frm.submit(function () { $.ajax({ type: frm.attr('method'), url: frm.attr('action'), dataType:'json', data: frm.serialize(), success: function (data) { var code = data.code var msg = data.msg if(code == 0){ $('#id_content').val("") $('.comment-list').prepend(data.html); $('#comment-result').text("评论成功") $('.info').show().delay(2000).fadeOut(800) }else{ $('#comment-result').text(msg) $('.info').show().delay(2000).fadeOut(800); } }, error: function(data) { } }); return false; }); 评论通过ajax提交后,我们在submit_comment()中就能接收到这个请求。处理如下 def submit_comment(request,pk): video = get_object_or_404(Video, pk = pk) form = CommentForm(data=request.POST) if form.is_valid(): new_comment = form.save(commit=False) new_comment.user = request.user new_comment.nickname = request.user.nickname new_comment.avatar = request.user.avatar new_comment.video = video new_comment.save() data = dict() data['nickname'] = request.user.nickname data['avatar'] = request.user.avatar data['timestamp'] = datetime.fromtimestamp(datetime.now().timestamp()) data['content'] = new_comment.content comments = list() comments.append(data) html = render_to_string( "comment/comment_single.html", {"comments": comments}) return JsonResponse({"code":0,"html": html}) return JsonResponse({"code":1,'msg':'评论失败!'}) 在接收函数中,通过form自带的验证函数来保存记录,然后将这条记录返回到前端模板。 下面我们开始评论列表的开发。 评论列表部分,我们使用了的是上拉动态加载的方案,即当页面拉到最下侧时,js加载代码会自动的获取下一页的数据并显示出来。前端部分,我们使用了一种基于js的开源加载插件。基于这个插件,可以很容易实现网页的上拉动态加载效果。它使用超级简单,仅需要调用$('.comments').dropload({})即可。我们把调用的代码封装在static/js/load_comments.js里面。 完整的调用代码如下: $(function(){ // 页数 var page = 0; // 每页展示15个 var page_size = 15; // dropload $('.comments').dropload({ scrollArea : window, loadDownFn : function(me){ page++; $.ajax({ type: 'GET', url: comments_url, data:{ video_id: video_id, page: page, page_size: page_size }, dataType: 'json', success: function(data){ var code = data.code var count = data.comment_count if(code == 0){ $('#id_comment_label').text(count + "条评论"); $('.comment-list').append(data.html); me.resetload(); }else{ me.lock(); me.noData(); me.resetload(); } }, error: function(xhr, type){ me.resetload(); } }); } }); }); 不用过多的解释,这段代码已经非常非常清晰了,本质还是ajax的接口请求调用,调用后返回结果更新前端网页内容。 我们看到ajax调用的接口是get_comments,我们继续来实现它,它位于comment/views.py中。代码如下所示,这段代码也很简单,没有什么复杂的技术。当获取到page和page_size后,使用paginator对象来实现分页。最后通过render_to_string将html传递给模板。 def get_comments(request): if not request.is_ajax(): return HttpResponseBadRequest() page = request.GET.get('page') page_size = request.GET.get('page_size') video_id = request.GET.get('video_id') video = get_object_or_404(Video, pk=video_id) comments = video.comment_set.order_by('-timestamp').all() comment_count = len(comments) paginator = Paginator(comments, page_size) try: rows = paginator.page(page) except PageNotAnInteger: rows = paginator.page(1) except EmptyPage: rows = [] if len(rows) > 0: code = 0 html = render_to_string( "comment/comment_single.html", {"comments": rows}) else: code = 1 html = "" return JsonResponse({ "code":code, "html": html, "comment_count": comment_count })

优秀的个人博客,低调大师

基于django的视频点播网站开发-step9-后台视频管理功能

从本讲开始,我们开始视频管理功能的开发,视频管理包括视频上传、视频列表、视频编辑、视频删除。另外还有视频分类的功能,会一同讲解。这一讲非常重要,因为你将学习到一些之前没有学过的技术,比如大文件上传技术。 视频上传 我们先来实现视频的上传,视频的上传采用的是分块上传的策略,并用了分块上传类库:django_chunked_upload,使用该类库,再配合前端上传js库(jquery.fileupload.js),即可完美的实现文件的分块上传功能。 照例先编写添加视频的路由 添加视频,当然需要上传视频的页面,我们的页面是video_add路由来显示,通过urls .py中指定 path('video_add/', views.AddVideoView.as_view(), name='video_add'), AddViewView仅仅用来显示上传页面,它的代码很简单 class AddVideoView(SuperUserRequiredMixin, TemplateView): template_name = 'myadmin/video_add.html' 只是继承了TemplateView来显示myadmin/video_add.html myadmin/video_add.html中实现了上传视频的全过程,视频的上传采用的是分块上传的策略,前端使用的是js上传库(jquery.fileupload.js),后端使用的是django_chunked_upload,上传的逻辑是这样的:前端先选择一个文件,通过jquery.fileupload.js中的$.fileupload()方法来上传文件,后端接收到后分批返回已上传块的进度,前端根据进度来更新界面。由于上传前需要做一些校验的操作,代码较复杂,所以我们把上传的代码封装到了一个js中:static/js/myadmin/video_upload.js,主要的代码如下: $("#chunked_upload").fileupload({ url: api_chunked_uplad, dataType: "json", maxChunkSize: 100000, // Chunks of 100 kB formData: form_data, add: function(e, data) { // Called before starting upload var fileSize = data.originalFiles[0]['size']; var type = data.originalFiles[0]['type']; if(fileSize > 100000000){ alert('文件太大了,请上传100M以内的文件'); return; } if(!type.startsWith("video/")){ alert('视频格式不正确'); return; } form_data.splice(1); calculate_md5(data.files[0], 100000); // Again, chunks of 100 kB data.submit(); $('#progress_label').on('click', false); $('#progress_layout').show() }, chunkdone: function (e, data) { // Called after uploading each chunk if (form_data.length < 2) { form_data.push( {"name": "upload_id", "value": data.result.upload_id} ); } var progress = parseInt(data.loaded / data.total * 100.0, 10); console.log(progress); if(progress > lastprogress){ lastprogress = progress $('#upload_progress').progress({ percent: progress }); } }, done: function (e, data) { // Called when the file has completely uploaded $.ajax({ type: "POST", url: api_chunked_upload_complete, data: { csrfmiddlewaretoken: csrf, upload_id: data.result.upload_id, md5: md5 }, dataType: "json", success: function(data) { console.log(data) $('#upload_label').text('上传成功'); $('#upload_progress').progress({ percent: 100 }); $('#next_layout').show(); $('#next').click(function(){ window.location = '/myadmin/video_publish/' + data.video_id }); } }); }, }); 在$.fileupload()方法中,有一个回调方法chunkdone(),该方法是用来更新进度的,告诉前端已经上传了多少字节。另外还有一个回调方法done(),该方法表示上传完毕,前端可在里面做一些额外的事情。 上传完毕后,调用了一个接口api_chunked_upload_complete,来给后端发送一个回执:我已上传完毕。 api_chunked_upload和api_chunked_upload_complete的路由是 path('chunked_upload/', views.MyChunkedUploadView.as_view(), name='api_chunked_upload'), path('chunked_upload_complete/', views.MyChunkedUploadCompleteView.as_view(),name='api_chunked_upload_complete'), 在MyChunkedUploadCompleteView中,我们在利用Video模型创建了这条视频 class MyChunkedUploadCompleteView(ChunkedUploadCompleteView): model = MyChunkedUpload def get_response_data(self, chunked_upload, request): video = Video.objects.create(file=chunked_upload.file) return {'code': 0, 'video_id': video.id, 'msg': 'success'} 上传完毕效果如下 然后用户点击下一步,进入video_publish页面,开始发布前的资料填写 video_publish的路由是 path('video_publish/<int:pk>/', views.VideoPublishView.as_view(), name='video_publish'), video_publish的视图类是VideoPublishView,它的代码如下 class VideoPublishView(SuperUserRequiredMixin, generic.UpdateView): model = Video form_class = VideoPublishForm template_name = 'myadmin/video_publish.html' def get_context_data(self, **kwargs): context = super(VideoPublishView, self).get_context_data(**kwargs) clf_list = Classification.objects.all().values() clf_data = {'clf_list':clf_list} context.update(clf_data) return context def get_success_url(self): return reverse('myadmin:video_publish_success') 对应的页面是myadmin/video_publish.html 就是下面这个页面 要填写的视频资料有视频标题、描述、分类、封面, 其中分类是通过get_context_data()带过来的, 填写后,点击发布,django将通过UpdateView自动为你更新视频信息。并通过get_success_url跳转到成功页面myadmin:video_publish_success,它的路由是 path('video_publish_success/', views.VideoPublishSuccessView.as_view(), name='video_publish_success'), 对应VideoPublishSuccessView是 class VideoPublishSuccessView(generic.TemplateView): template_name = 'myadmin/video_publish_success.html' 如下 我们点击视频列表即可查看视频 视频列表 视频列表的路由是 path('video_list/', views.VideoListView.as_view(), name='video_list'), 对应的视图类是VideoListView class VideoListView(AdminUserRequiredMixin, generic.ListView): model = Video template_name = 'myadmin/video_list.html' context_object_name = 'video_list' paginate_by = 10 q = '' def get_context_data(self, *, object_list=None, **kwargs): context = super(VideoListView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) context['page_list'] = page_list context['q'] = self.q return context def get_queryset(self): self.q = self.request.GET.get("q", "") return Video.objects.get_search_list(self.q) 这里继承了ListView来显示视频列表,并通过get_queryset实现了搜索功能,通过get_context_data()实现了分页功能。 最后展示效果如下 你可能会发现,页面中还有编辑和删除的功能。编辑呢,是对单个视频对资料进行更新,删除即删除本条视频和视频文件。 视频编辑 我们先实现编辑功能,路由是 path('video_edit/<int:pk>/', views.VideoEditView.as_view(), name='video_edit'), 对应对视图类是VideoEditView,这个视图类是需要传递主键的。 class VideoEditView(SuperUserRequiredMixin, generic.UpdateView): model = Video form_class = VideoEditForm template_name = 'myadmin/video_edit.html' def get_context_data(self, **kwargs): context = super(VideoEditView, self).get_context_data(**kwargs) clf_list = Classification.objects.all().values() clf_data = {'clf_list':clf_list} context.update(clf_data) return context def get_success_url(self): messages.success(self.request, "保存成功") return reverse('myadmin:video_edit', kwargs={'pk': self.kwargs['pk']}) 其实编辑页面和发布页面很相似,都是继承UpdateView视图类,并在get_context_data()里面传递分类信息。最终成功后通过messages.success(self.request, "保存成功")消息告之前端。 视频删除 删除功能就更加简单了。路由是 path('video_delete/', views.video_delete, name='video_delete'), 这里通过video_delete函数来实现,前端通过ajax(ajax代码位于static/js/myadmin/video_list.js)调用这个函数。 @ajax_required @require_http_methods(["POST"]) def video_delete(request): video_id = request.POST['video_id'] instance = Video.objects.get(id=video_id) instance.delete() return JsonResponse({"code": 0, "msg": "success"}) 获取该视频,然后instance.delete()删除之。 视频分类 分类管理功能包括分类的增删改查。 增删改查的路由是 path('classification_add/', views.ClassificationAddView.as_view(), name='classification_add'), path('classification_list/', views.ClassificationListView.as_view(), name='classification_list'), path('classification_edit/<int:pk>/', views.ClassificationEditView.as_view(), name='classification_edit'), path('classification_delete/', views.classification_delete, name='classification_delete'), 先来看分类添加的功能 分类添加是通过ClassificationAddView视图类来实现的,代码如下 class ClassificationAddView(SuperUserRequiredMixin, generic.View): def get(self, request): form = ClassificationAddForm() return render(self.request, 'myadmin/classification_add.html', {'form': form}) def post(self, request): form = ClassificationAddForm(data=request.POST) if form.is_valid(): form.save(commit=True) return render(self.request, 'myadmin/classification_add_success.html') return render(self.request, 'myadmin/classification_add.html', {'form': form}) 此处是通过get和post一同来实现的,get()负责展示界面,post()负责逻辑判断。在post()中,直接调用form.save来保存记录,然后跳转到成功页myadmin/classification_add_success.html。 分类添加 添加成功 然后点击视频列表,即可查看列表,视频列表的视图类是ClassificationListView,即 class ClassificationListView(AdminUserRequiredMixin, generic.ListView): model = Classification template_name = 'myadmin/classification_list.html' context_object_name = 'classification_list' paginate_by = 10 q = '' def get_context_data(self, *, object_list=None, **kwargs): context = super(ClassificationListView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) context['page_list'] = page_list context['q'] = self.q return context def get_queryset(self): self.q = self.request.GET.get("q", "") return Classification.objects.filter(title__contains=self.q) 继承ListView来显示列表,通过get_queryset()来实现搜索功能,通过get_context_data()来实现分页功能,通过template_name来指定模板 效果如下 接着来实现编辑和删除功能。 编辑对应的视图类是ClassificationEditView,它的实现超级简单,继承UpdateView即可。 class ClassificationEditView(SuperUserRequiredMixin, generic.UpdateView): model = Classification form_class = ClassificationEditForm template_name = 'myadmin/classification_edit.html' def get_success_url(self): messages.success(self.request, "保存成功") return reverse('myadmin:classification_edit', kwargs={'pk': self.kwargs['pk']}) 编辑页面和添加页面很相似,这里就不贴图了。 最后是删除功能,是通过ajax来实现的,ajax代码位于static/js/myadmin/classification_list.js,在ajax中,通过调用删除接口classification_delete来实现删除功能, 接口classification_delete的代码: @ajax_required @require_http_methods(["POST"]) def classification_delete(request): classification_id = request.POST['classification_id'] instance = Classification.objects.get(id=classification_id) instance.delete() return JsonResponse({"code": 0, "msg": "success"}) 功能略多,同学们可根据自身情况,根据后台demo地址的演示来一步步学习。

优秀的个人博客,低调大师

基于django的视频点播网站开发-step6-个人中心功能

从本讲起,我们开始个人中心功能的开发。个人中心里面包括个人资料、修改密码、订阅设置、意见反馈这四部分。通过这部分的开发,我们将会接触到更多django的用法。 感兴趣的伙伴可通过网站演示预览网站,登录后点击右上角头像即可弹出个人中心相关菜单。 整体功能 个人中心模块是对用户的信息进行展示并可以编辑。其中个人资料、修改密码、订阅设置是对用户信息的编辑,反馈建议是属于创建新数据。 个人资料 这里主要是对个人资料进行编辑,先显示用户原有的信息,然后用户即可对其进行修改并保存,对于编辑功能,django有自己的解决方案,即通过通用视图类UpdateView对模型进行更改。关于Update的介绍,同学们可查阅官网介绍 因为前面已经建立过user模型,所以这里就不用再次建立了,我们直接使用之前的user模型即可。 需要我们做的就是在users/urls.py中添加个人资料的路由, path('profile/<int:pk>/', views.ProfileView.as_view(), name='profile'), 可以看到,这里我们需要传一个int参数做为主键,并传递给视图类ProfileView。 下面我们转向ProfileView,它的写法超级简单 class ProfileView(LoginRequiredMixin,AuthorRequiredMixin, generic.UpdateView): model = User form_class = ProfileForm template_name = 'users/profile.html' def get_success_url(self): messages.success(self.request, "保存成功") return reverse('users:profile', kwargs={'pk': self.request.user.pk}) 简单的几行代码,就实现了个人资料的更新,再次彰显了django框架的强大。我们这里继承了UpdateView来实现更新操作,和DetailView类似,我们这里也设置了model和template_name 还有form_class。当更新成功后,django会回调get_success_url来将结果告诉模板,因此我们可以在get_success_url里面做一些定制的工作,我们可以传一些自己的参数。 可以看到我们还继承了LoginRequiredMixin和AuthorRequiredMixin两个类,这两个类属于公共类,其中LoginRequiredMixin的用途是:只允许登录的用户访问该视图类,AuthorRequiredMixin的用途是:只允许用户自己查看自己的个人资料,别人是无法查看的。其中AuthorRequiredMixin的代码位于videoproject/helpers.py。 修改密码 同样的,修改密码也是属于更新操作。 模型当然是用user模型,不必再建。 我们先添加路由 path('change_password/', views.change_password, name='change_password'), 修改密码比较特殊,需要对密码进行特殊处理,因此我们通过视图函数change_password来手写代码 def change_password(request): if request.method == 'POST': form = ChangePwdForm(request.user, request.POST) if form.is_valid(): user = form.save(commit=False) if not user.is_staff and not user.is_superuser: user.save() update_session_auth_hash(request, user) # 更新session 非常重要! messages.success(request, '修改成功') return redirect('users:change_password') else: messages.warning(request, '无权修改管理员密码') return redirect('users:change_password') else: print(form.errors) else: form = ChangePwdForm(request.user) return render(request, 'registration/change_password.html', { 'form': form }) 代码中,当拿到form之后,通过验证form合法性,然后调用user.save()来保存修改。然后通过update_session_auth_hash来更新session。 这样就实现了修改密码功能。 订阅设置 很多网站都有订阅设置功能,当用户订阅了网站内容之后,网站有了新内容,即向订阅用户推送相关内容。有利于让用户获取网站内容的最新更新。 我们先在users/urls.py下添加订阅功能的路由 path('subscribe/<int:pk>/', views.SubscribeView.as_view(), name='subscribe'), 我们设置的订阅视图类为SubscribeView,因为订阅的功能和修改个人资料功能类似,也是属于更新操作,所以同样是使用UpdateView来更新。 class SubscribeView(LoginRequiredMixin,AuthorRequiredMixin, generic.UpdateView): model = User form_class = SubscribeForm template_name = 'users/subscribe.html' def get_success_url(self): messages.success(self.request, "保存成功") return reverse('users:subscribe', kwargs={'pk': self.request.user.pk}) 反馈与建议 这里我们需要在users/models.py下新建一个反馈表,命名为Feedback, class Feedback(models.Model): contact = models.CharField(blank=True, null=True, max_length=20) content = models.CharField(blank=True, null=True, max_length=200) timestamp = models.DateTimeField(auto_now_add=True, null=True) class Meta: db_table = "v_feedback" 该表一共有3个字段,分别是 contact 联系方式 content 内容 timestamp 时间 写完model之后,我们就能写业务代码了。 先添加路由 path('feedback/', views.FeedbackView.as_view(), name='feedback'), 我们设置路由指向FeedbackView视图类。 我们直接贴出FeedbackView的代码 class FeedbackView(LoginRequiredMixin, generic.CreateView): model = Feedback form_class = FeedbackForm template_name = 'users/feedback.html' @ratelimit(key='ip', rate='2/m') def post(self, request, *args, **kwargs): was_limited = getattr(request, 'limited', False) if was_limited: messages.warning(self.request, "操作太频繁了,请1分钟后再试") return render(request, 'users/feedback.html', {'form': FeedbackForm()}) return super().post(request, *args, **kwargs) def get_success_url(self): messages.success(self.request, "提交成功") return reverse('users:feedback') 我们看到这个地方继承的是CreateView类,该类属于新建通用视图类。只要我们配置好model、form_class、template_name,django就自动为我们创建记录。 另外,我们还使用了一种限流量的技术:ratelimit。这是一个第三方类库,通过使用他,可以防止恶意提交数据。它使用超级简单,只需要配置好key和rate即可,key代表业务,rate代表速率,这里我们设置key为ip,即限制ip地址,rate为'2/m',表示每分钟限制请求2次。超过2次就提示用户操作频繁。 这样我们就完美的实现了用户反馈。 下面是效果展示 个人资料:修改密码:订阅设置:反馈建议:

优秀的个人博客,低调大师

基于django的视频点播网站开发-step7-我的收藏喜欢功能

从本讲起,我们会讲到个人菜单中另外两个比较重要的功能,“我的收藏”与“我的喜欢”。感兴趣的同学可以点击网站的右上角,即可弹出个人菜单,你就能看到“我的收藏”与“我的喜欢”了。通过学习这两个功能,我们会加深对django中通用视图类的理解与应用。 Demo预览 我的收藏 我们先来讨论模型,我们分析一下,我收藏的视频与我喜欢的视频,其中的视频都是属于video模型,因此我们应该在video模型上进行修改。因为是我的收藏、我的喜欢,都是与我关联,又因为,我可以收藏多个视频,视频也可以被多个用户收藏,所以用户与视频是属于多对多的关系。所以我们在video模型上添加两个字段liked和collected,分别对应我喜欢和我收藏。代码如下 class Video(models.Model): STATUS_CHOICES = ( ('0', '发布中'), ('1', '未发布'), ) title = models.CharField(max_length=100,blank=True, null=True) desc = models.CharField(max_length=255,blank=True, null=True) classification = models.ForeignKey(Classification, on_delete=models.CASCADE, null=True) file = models.FileField(max_length=255) cover = models.ImageField(upload_to='cover/',blank=True, null=True) status = models.CharField(max_length=1 ,choices=STATUS_CHOICES, blank=True, null=True) view_count = models.IntegerField(default=0, blank=True) liked = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="liked_videos") collected = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="collected_videos") create_time = models.DateTimeField(auto_now_add=True, blank=True, max_length=20) 可以看出liked和collected字段都是属于ManyToManyField类型,表示视频与用户是多对多的关系。并分别设置它们的别名为"liked_videos"和"collected_videos",有django基础的同学应该明白,通过别名也可以访问到数据。 下面我们来添加两者的路由,添加在users/urls.py下面。 path('<int:pk>/collect_videos/', views.CollectListView.as_view(), name='collect_videos'), path('<int:pk>/like_videos/', views.LikeListView.as_view(), name='like_videos'), 其中,我的收藏的视图类是CollectListView,我的喜欢的视图类是LikeListView。我们先来实现CollectListView class CollectListView(generic.ListView): model = User template_name = 'users/collect_videos.html' context_object_name = 'video_list' paginate_by = 10 def get_context_data(self, *, object_list=None, **kwargs): context = super(CollectListView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) context['page_list'] = page_list return context def get_queryset(self): user = get_object_or_404(User, pk=self.kwargs.get('pk')) videos = user.collected_videos.all() return videos 与首页展示的功能类似,这里同样继承了ListView通用视图类。并使用了公共函数get_page_list对数据进行分页。在获取收藏数据列表时,我们用的是user.collected_videos.all(),其中collected_videos就是前面定义的别名。 并通过配置template_name将数据传递给模板文件users/collect_videos.html。 模板文件关键代码 <h3 class="ui header">我的收藏</h3> <div class="ui unstackable items"> {% for item in video_list %} <div class="item"> <div class="ui tiny image"> {% thumbnail item.cover "300x200" crop="center" as im %} <img class="ui image" src="{{ im.url }}"> {% empty %} {% endthumbnail %} </div> <div class="middle aligned content"> <a class="header" href="{% url 'video:detail' item.pk %}">{{ item.title }}</a> </div> </div> {% empty %} <h3>暂无数据</h3> {% endfor %} </div> {% include "base/page_nav.html" %} 最终展示效果 我的喜欢 下面来开发我的喜欢功能 该功能与我的收藏功能类似。因为前面已经添加了like_videos路由,我们直接写LikeListView的代码 class LikeListView(generic.ListView): model = User template_name = 'users/like_videos.html' context_object_name = 'video_list' paginate_by = 10 def get_context_data(self, *, object_list=None, **kwargs): context = super(LikeListView, self).get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') page_list = get_page_list(paginator, page) context['page_list'] = page_list return context def get_queryset(self): user = get_object_or_404(User, pk=self.kwargs.get('pk')) videos = user.liked_videos.all() return videos 与我的收藏的模式一模一样,同样是继承ListView并设置相关model与template_name变量。最终通过users/like_videos.html来渲染。 like_videos.html关键代码 <h3 class="ui header">我的喜欢</h3> <div class="ui unstackable items"> {% for item in video_list %} <div class="item"> <div class="ui tiny image"> {% thumbnail item.cover "300x200" crop="center" as im %} <img class="ui image" src="{{ im.url }}"> {% empty %} {% endthumbnail %} </div> <div class="middle aligned content"> <a class="header" href="{% url 'video:detail' item.pk %}">{{ item.title }}</a> </div> </div> {% empty %} <h3>暂无数据</h3> {% endfor %} </div> {% include "base/page_nav.html" %} 最终展示效果

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册