• <Django>博客项目


    0.项目的通用流程

    1. 项目立项
    2. 需求分析
    3. 原型
    4. 前端
      1. 页面设计
      2. UI及交互实现
    5. 后端
      1. 架构设计
      2. 数据库设计
      3. 代码模板实现
      4. 单元测试
    6. 网站整合
    7. 功能及集成测试
    8. 网站发布           

    1.BBS项目需求分析

    1. 需要哪些表
      1. UserInfo表
        1. username
        2. password
        3. avatar----头像  
      2. 文章表
        1. title
        2. publish_date
        3. desc----摘要
        4. author
        5. 详细内容 一对一关联,文章详情表
      3. 文章详情表
        1. info  
      4. 评论表
        1. user
        2. 评论时间
        3. 评论内容
        4. 评论和文章的关联关系(1个文章多个评论,1对多,写在多的那方)  
        5. 是谁的子评论,是不是回复别人的评论
      5. 标签
        1. 标签名
        2. 标签名和文章,多对多
      6. 分类
        1. 分类名
        2. 分类和文章的关联关系,多对多或一对多  
      7. 点赞
        1. 是赞还是踩
        2. 时间
        3. 谁点的 关联user
        4. 点的是哪个文章  

    2.BBS的注册功能

    1. 基于Form表单和Ajax的注册    

    form表单的作用:

    1. 生成HTML代码
    2. 验证
    3. 将验证的错误显示在页面上并保留原始的数据

    创建项目和对应APP后

    settings.py设置

    编码格式

    # coding=utf-8

    静态文件所在位置

    STATIC_URL = '/static/'# 静态文件夹的别名
    
    # 静态文件夹的位置
    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, "static"),
    ]

    数据库连接

    # 数据库相关的配置
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',  # 连接的数据库类型
            'HOST': '127.0.0.1',  # 连接数据库的地址
            'PORT': 3306,  # 端口
            'NAME': "bbs",  # 数据库名称
            'USER': 'root',  # 用户
            'PASSWORD': 'root'  # 密码
        }
    } 

    APP设置

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'blog.apps.BlogConfig',
    ]

    模板设置,

    因为是PyCharm创建的项目,自动设置好了

    认证指定表

    # 告诉Django项目用哪张表做认证
    AUTH_USER_MODEL = 'blog.UserInfo'

    static设置

    放入必须的文件 https://pan.baidu.com/s/1EgrzvxIRGAJ_J3g77rL4rQ

    __init__.py-----与settings.py同级的

    # coding=utf-8
    import pymysql
    # 告诉Django用pymysql来代替默认的MySQLdb
    pymysql.install_as_MySQLdb()
    

      

    models.py

    表结构,生成相应的迁移文件和执行迁移

    # coding=utf-8
    from django.db import models
    
    # Create your models here.
    
    from django.contrib.auth.models import AbstractUser
    
    
    class UserInfo(AbstractUser):
        """ 用户信息表"""
        nid = models.AutoField(primary_key=True)
        phone = models.CharField(max_length=11, null=True, unique=True)
        # 传入的文件都上传到avatars文件夹下
        avatar = models.FileField(upload_to="avatars/", default="avatars/default.png", verbose_name="头像")
        create_time = models.DateTimeField(auto_now_add=True)
    
        blog = models.OneToOneField(to="Blog", to_field="nid", null=True)
    
        def __str__(self):
            return self.username
    
    
    class Blog(models.Model):
        """
        博客信息
        """
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=64)  # 个人博客标题
        site = models.CharField(max_length=32, unique=True)  # 个人博客后缀
        theme = models.CharField(max_length=32)  # 博客主题
    
        def __str__(self):
            return self.title
    
    
    class Category(models.Model):
        """
        个人博客文章分类
        """
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=32)  # 分类标题
        blog = models.ForeignKey(to="Blog", to_field="nid")  # 外键关联博客,一个博客站点可以有多个分类
    
        def __str__(self):
            return self.title
    
    
    class Tag(models.Model):
        """
        标签
        """
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=32)  # 标签名
        blog = models.ForeignKey(to="Blog", to_field="nid")  # 所属博客
    
        def __str__(self):
            return self.title
    
    
    class Article(models.Model):
        """
        文章
        """
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=50)  # 文章标题
        desc = models.CharField(max_length=255)  # 文章描述
        create_time = models.DateTimeField()  # 创建时间
    
        category = models.ForeignKey(to="Category", to_field="nid", null=True)
        user = models.ForeignKey(to="UserInfo", to_field="nid")
        tags = models.ManyToManyField(  # 中介模型
            to="Tag",
            through="Article2Tag",
            through_fields=("article", "tag"),  # 注意顺序!!!
        )
    
        def __str__(self):
            return self.title
    
    
    class ArticleDetail(models.Model):
        """
        文章详情表
        """
        nid = models.AutoField(primary_key=True)
        content = models.TextField()
        article = models.OneToOneField(to="Article", to_field="nid")
    
    
    class Article2Tag(models.Model):
        """
        文章和标签的多对多关系表
        """
        nid = models.AutoField(primary_key=True)
        article = models.ForeignKey(to="Article", to_field="nid")
        tag = models.ForeignKey(to="Tag", to_field="nid")
    
        class Meta:
            unique_together = (("article", "tag"),)
    
    
    class ArticleUpDown(models.Model):
        """
        点赞表
        """
        nid = models.AutoField(primary_key=True)
        user = models.ForeignKey(to="UserInfo", null=True)
        article = models.ForeignKey(to="Article", null=True)
        is_up = models.BooleanField(default=True)
    
        class Meta:
            unique_together = (("article", "user"),)
    
    
    class Comment(models.Model):
        """
        评论表
        """
        nid = models.AutoField(primary_key=True)
        article = models.ForeignKey(to="Article", to_field="nid")
        user = models.ForeignKey(to="UserInfo", to_field="nid")
        content = models.CharField(max_length=255)  # 评论内容
        create_time = models.DateTimeField(auto_now_add=True)
        parent_comment = models.ForeignKey("self", null=True)
    
        def __str__(self):
            return self.content
    

      生成迁移文件

    python manage.py makemigrations
    

      执行迁移

    python manage.py migrate
    

      

    forms.py

    # coding=utf-8
    """bbs用到的form类"""
    
    from django import forms
    # 从from组件导入widgets属性
    from django.forms import widgets
    # 导入错误类
    from django.core.exceptions import ValidationError
    
    
    # 定义一个注册的from类
    
    class RegForm(forms.Form):
    	username = forms.CharField(
    		max_length=16,
    		label="用户名",
    		# 错误提示
    		error_messages={
    			"max_length": "用户名最长16位",
    			"required": "用户名不能为空",
    		},
    		# 设定input框的样式
    		widget=forms.widgets.TextInput(
    			attrs={"class": "form-control"}
    		)
    	)
    
    	password = forms.CharField(
    		min_length=6,
    		label="密码",
    		widget=forms.widgets.PasswordInput(
    			attrs={"class": "form-control"},
    			# 输入密码不消失
    			render_value=True,
    		),
    		# 错误提示
    		error_messages={
    			"min_length": "密码最少6位",
    			"required": "密码不能为空",
    		}
    	)
    
    	re_password = forms.CharField(
    		min_length=6,
    		label="确认密码",
    		widget=forms.widgets.PasswordInput(
    			attrs={"class": "form-control"},
    			# 输入密码不消失
    			render_value=True,
    		),
    		# 错误提示
    		error_messages={
    			"min_length": "密码最少6位",
    			"required": "密码不能为空",
    		}
    	)
    
    	email = forms.EmailField(
    		label="邮箱",
    		widget=forms.widgets.EmailInput(
    			attrs={"class": "form-control"}
    		),
    		error_messages={
    			'invalid': "邮箱格式不正确",
    			"required": "邮箱不能为空",
    		}
    	)
    
    	# 重写全局钩子函数,对确认密码做校验
    	def clean(self):
    		password = self.cleaned_data.get("password")
    		re_password = self.cleaned_data.get("re_password")
    		if re_password and re_password != password:
    			self.add_error("re_password", ValidationError("两次密码不一致"))
    		# 没错误直接返回
    		else:
    			return self.cleaned_data
    
    		'''
    	def _clean_form(self):
    		try:
                cleaned_data = self.clean()
            except ValidationError as e:
                self.add_error(None, e)
            else:
                if cleaned_data is not None:
                    self.cleaned_data = cleaned_data
    		'''
    

      

    urls.py

    # coding=utf-8
    
    from django.conf.urls import url
    from django.contrib import admin
    from blog import views
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
    
        # 注册
        url(r'^reg/', views.register),
        url(r'^index/', views.index),
    
    ]
    

      

    views.py

    # coding=utf-8
    from django.shortcuts import render, redirect, HttpResponse
    # 使用forms内的文件生成HTML
    from blog import forms, models
    
    from django.http import JsonResponse
    
    
    # Create your views here.
    
    
    # 注册的视图函数
    def register(request):
    	# from表单提交
    	if request.method == "POST":
    		# 定义一个字典做Ajax提交
    		ret = {"status": 0, "msg": "", }
    
    		# 接收提交过来的数据,只有正常的键值对,没有图像
    		form_obj = forms.RegForm(request.POST)
    		print(request.POST)
    		# 帮我做校验,先校验内置的,在校验clean_开头的规则,整个循环走完,在调用clean()方法
    		if form_obj.is_valid():
    			# 校验成功,去数据库创建一个新的用户,models继承AbstractUser,使用creat_user
    			# 多一个键值对,re_password
    			form_obj.cleaned_data.pop('re_password')
    			# 自己取文件、图片的数据
    			avatar_img = request.FILES.get("avatar")
    			models.UserInfo.objects.create_user(avatar=avatar_img, **form_obj.cleaned_data)
    			# 注册成功 跳转页面
    			ret["msg"] = "/index/"
    			return JsonResponse(ret)
    		# return HttpResponse("注册成功")
    
    		else:
    			print(form_obj.errors)
    			# 有错误将status给个值
    			ret["status"] = 1
    			# 错误封装到msg里面
    			ret["msg"] = form_obj.errors
    			return JsonResponse(ret)
    			# return render(request, "register.html", {"form_obj": form_obj})
    		# return HttpResponse("信息有误,注册失败")
    	# 生成from对象
    	form_obj = forms.RegForm()
    	return render(request, "register.html", {"form_obj": form_obj})
    
    
    def index(request):
    	return HttpResponse("这里是index页面")
    

      

    register.html

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>欢迎注册</title>
        {# 方法3:使用bootstrap样式   #}
        <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
        <link rel="stylesheet" href="/static/mystyle.css">
    </head>
    <body>
    <div class="container reg-form">
        <div class="row">
            <div class="col-md-6 col-md-offset-3">
                {#  有文件类型就需要加enctype="multipart/form-data"  novalidate不用H5浏览器帮你验证        #}
                <form novalidate action="/reg/" method="post" class="form-horizontal" enctype="multipart/form-data">
                    {# 防域名伪造 #}
                    {% csrf_token %}
                    {#     方法1:标签 + input框   #}
                    {#          {{ from_obj.username.label }}#}
                    {#                {{ from_obj.username }}#}
    
                    {# 方法2:按照forms写的顺序进行遍历   #}
                    {#    {% for field in from_obj %}#}
                    {#   froms中的每个字段标签+对象#}
                    {#        {{ field.label }}#}
                    {#        {{ field }} <br>#}
                    {#    {% endfor %}#}
                    {#   方法3:bootstrap          #}
                    <div class="form-group">
                        {# for属性为了聚焦,关联lable和input框 #}
                        <label for="{{ form_obj.username.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.username.label }}</label>
                        <div class="col-sm-8">
                            {{ form_obj.username }}
                            {#<input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}
                            {#   校验状态   #}
                            {#   错误提示              #}
                            <span id="helpBlock2" class="help-block">{{ form_obj.username.errors.0 }}</span>
                        </div>
                    </div>
    
                    <div class="form-group">
                        {# for属性为了聚焦,关联lable和input框 #}
                        <label for="{{ form_obj.password.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.password.label }}</label>
                        <div class="col-sm-8">
                            {{ form_obj.password }}
                            {#<input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}
                            {#   校验状态   #}
                            <span id="" class="help-block">{{ form_obj.password.errors.0 }}</span>
                        </div>
                    </div>
    
                    <div class="form-group">
                        {# for属性为了聚焦,关联lable和input框 #}
                        <label for="{{ form_obj.re_password.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.re_password.label }}</label>
                        <div class="col-sm-8">
                            {{ form_obj.re_password }}
                            {#<input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}
                            {#   校验状态   #}
                            <span id="helpBlock2" class="help-block">{{ form_obj.re_password.errors.0 }}</span>
                        </div>
                    </div>
    
                    <div class="form-group">
                        {# for属性为了聚焦,关联lable和input框 #}
                        <label for="{{ form_obj.email.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.email.label }}</label>
                        <div class="col-sm-8">
                            {{ form_obj.email }}
                            {#<input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}
                            {#   校验状态   #}
                            <span id="helpBlock2" class="help-block">{{ form_obj.email.errors.0 }}</span>
                        </div>
                    </div>
    
                    <div class="form-group">
                        {# for属性为了聚焦,关联lable和input框 #}
                        <label for="" class="col-sm-2 control-label">头像</label>
                        <div class="col-sm-8">
                            <label for="id_avatar">
                                <img src="/static/img/default.png" id="avatar-img">
                            </label>
                            <input type="file" name="avatar" id="id_avatar" style="display: none">
    
                            {#   校验状态   #}
                            <span id="helpBlock2" class="help-block"></span>
                        </div>
                    </div>
    
                    <div class="form-group">
                        <div class="col-sm-offset-2 col-sm-10">
                            <button type="button" class="btn btn-success" id="reg-submit">注册</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
    
    
    {#导入JS文件#}
    <script src="/static/jquery-3.3.1.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
    
    <script>
        // 找到头像的input标签绑定change事件
        $("#id_avatar").change(function () {
            // 1. 创建一个读取文件的对象
            var fileReader = new FileReader();
            // 取到当前选中的头像文件
            // console.log(this.files[0]);
            // 读取你选中的那个文件
            fileReader.readAsDataURL(this.files[0]);  // 读取文件是需要时间的
            fileReader.onload = function () {
                // 2. 等上一步读完文件之后才 把图片加载到img标签中
                $("#avatar-img").attr("src", fileReader.result);
            };
        });
        // AJAX提交注册数据
        $('#reg-submit').click(function () {
            //alert(123);
            {#// 取得用户注册数据,向后端提交#}
            //var username = $("#id_username").val();
            //var password = $("#id_password").val();
            //var re_password = $("#id_re_password").val();
            //var email = $("#id_email").val();
            // 文件类型必须使用FormData()对象
            var formData = new FormData();
            formData.append("username", $("#id_username").val());
            formData.append("password", $("#id_password").val());
            formData.append("re_password", $("#id_re_password").val());
            formData.append("email", $("#id_email").val());
            //提交图片数据
            formData.append("avatar", $("#id_avatar")[0].files[0]);
            formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
    
            $.ajax({
                url: "/reg/",
                type: "post",
                // AJAX传带文件必须设置,下面的两个参数
                processData: false,
                contentType: false,
                data: formData,
                //data: {
                    // 提交的数据
                //    username: username,
                //    password: password,
                //    re_password: re_password,
                //    email: email,
                //    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
                //},
                success: function (data) {
                    if (data.status) {
                        //有错误展示错误
                        //console.log(data.msg);
                        //将错误信息填写到页面上,用each循环
                        $.each(data.msg, function (k, v) {
                            //console.log("id_"+ k,v[0]);
                            //找到id为id_+k的input标签,下的span标签,设置文本内容为v[0]
                            $("#id_" + k).next("span").text(v[0]);
                            //设置文本的样式为has-error
                            $("#id_" + k).parent().parent().addClass("has-error");
    
                        })
                    } else {
                        //没有错误,跳转指定页面
                        location.href = data.msg;
                    }
    
                }
            })
    
        });
    
        // 将所有input框,绑定焦点事件,清空所有错误信息
        $("form input").focus(function () {
            // 清空内容
            $(this).next().text("");
            $(this).next().text("").parent().parent().removeClass("has-error")
    
        })
    </script>
    
    </body>
    </html>
    

     

    ....

    3.API

    # 根据爬虫数据库数据,生成models.py

    python manage.py inspectdb
    

      

     安装

    pip install djangorestframework
    

      

     

  • 相关阅读:
    前端数组对象名字排序有效方法
    IntelliJ IDEA项目更新静态文件时每次需要重新运行不方便,那么需要配置文件使得不用每次修改文件需要重新运行
    新建项目用flyway数据迁移时报错找不到flyway org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flyway' defined in class path resource
    docker用户映射
    ceph概念介绍
    ansible开发篇
    rancher安装k8s
    mysql 根据条件导出文件到系统
    Python c = a >b? a:b 语法
    html转pdf或者图片wkhtmltopdf wkhtmltoimage
  • 原文地址:https://www.cnblogs.com/shuimohei/p/11519266.html
Copyright © 2020-2023  润新知