Python Day 70 利用Django框架作的一個bbs小項目

  ##項目開發流程css

#1.項目需求分析
    產品經理+架構師+開發經理/組長 去到客戶的公司談需求(博弈的過程)    
#2.項目架構設計
    架構師設計(數據庫(主庫:MySQL,從庫:redis,mongodb),框架的選擇,項目功能劃分...) 
        報價:每一個開發人員1500~3000     300萬
        多部門聯合審批    
#3.分組開發
    開會 分任務 
    小組人員開始搬磚 (忘生成好的框架內填寫代碼便可)
#4.測試
    1.開發人員本身測試(顯而易見的bug)
    2.測試人員(妹紙)   
    
#5.交付上線
    1.本身的服務器
    2.對方的服務器

  ##項目表結構設計html

#項目最最重要是表結構設計
        若是一張表內的數據分紅兩塊 一塊是常用的 一塊是不常用的,這個時候考慮數據優化的問題
        一對一關係
            1.一張表拆成了兩張表
            2.兩張表中的數據是一一對應的
        
        用戶表(利用auth_user表)
            phone
            avatar
            create_time
            blog            一對一我的站點
            
                ps:DateField()
                    auto_now:每次操做數據都會將當前時間更新
                    auto_now_add:自動將建立該數據的時間記錄下來以後再也不改變
                
        我的站點表
            站點名稱 site_name
            站點標題 site_title
            站點樣式 site_theme
        
        文章表    
            文章標題 title
            文章簡介 desc
            文章內容 content
            發佈時間 create_time
            
            blog            一對多我的站點
            category        一對多分類表
            tag             多對多標籤表
            
            # 數據庫設計優化(******)
            up_num
            down_num
            comment_num 
            # 當你操做點贊點踩表或者評論表的時候 只要保證上面三個同步更新
            
            
        
        標籤表
            tag_name
            blog          一對多我的站點
        
        分類表
            category_name
            blog            一對多我的站點
            
        
        點贊點踩表
            user            一對多user表
            article            一對多article表  
            is_up           0/1
        
        文章評論表
            user            一對多user表
            article         一對多article表  
            content         
            create_time
            parent          自關聯評論表 (to='self')  # self表示的就是當前表
            
        
        表與表之間關係判斷
            表中的一條數據可否對於另一張表的多條數

  ##表之間的關係前端

  ##各個功能分析及技術點python

#一、forms組件  
    models是models forms是forms 二者沒有任何關係
    只是咱們認爲用forms組件校驗models裏面的數據
    
    一般是用來校驗模型表的數據   ModelForm
        1.渲染標籤
        2.校驗數據
        3.展現信息
    from django import forms
    class MyForm(forms.Form):
        username = forms.CharField(...)
        
    將數據當作字典傳入類中
    is_valid()  校驗數據多傳不校驗少傳默認下不行的 
    cleaned_data
    errors
    
    渲染標籤三種方式(render渲染)
        form_obj = MyForm()  # get請求和post變量名必須是同樣的
        if request.method == "POST":
            form_obj = MyForm(request.POST)
        return render(request,'...html',locals())

        1.{{ form_obj.as_p }}
        2.{{ form_obj.username.label }}{{ form_obj.username }}
        3.{% for foo in form_obj %}
                {{foo.label}}{{foo}}
                {{foo.errors.0}}
            {%endfor%}
    鉤子函數
        
    
#二、註冊功能
    1.利用forms組件渲染前端頁面
        1.局部鉤子 校驗用戶名是否存在
        2.全局鉤子 校驗密碼是否一致
    2.本身寫前端獲取用戶頭像
        1.利用label標籤可以自動指向指定的input
        2.將img標籤放入了label標籤中,將默認的type=file框隱藏了
    3.用戶選擇頭像以後動態展現出來
        """
        img標籤的src的值
            1.圖片的路徑地址
            2.圖片的二進制數據
            3.後端url
        """
        1.利用ajax給input綁定了一個change事件
        2.利用內置對象FileReader讀取文件 
            var fileReader = new FileReader();
            var fileObj = $('input[type="file"]')[0].files[0];
            # 交給文件閱讀器讀取文件內容
            fileReader.readAsDataURL(fileObj)  # 異步操做
            # 等待文件閱讀器讀取完畢
            fileReader.onload = function(){
                $('#img_avatar').attr('src',fileReader.result)
            }
        3.利用ajax提交post請求
            1.當用戶點擊提交按鈕你須要作的事
                1.利用form標籤的內置的可序列化方法將普通獲取用戶輸入的文本框鍵值對自動獲取出來
                $('#myform').serializeArray()  # 獲取到的是一個數組內部是一個個自定義對象
                2.利用each循環循環取出自定義對象裏面的鍵值對(username,password,confirm_password,email,csrfmiddlewaretoken)
                3.既要傳文件有要傳數據 利用內置的FormData對象
                    循環普通鍵值對
                    var formData = new FormData();
                    formData.append('','')
                    # 添加文件數據
                    formData.append('my_avatar',$('input[type="file"]')[0].files[0])
                4.發送formdata類型的數據  應該考慮 先後端傳輸數據的編碼格式
                    ps:1.urlencoded(form與ajax默認的提交數據編碼格式),2.formdata,3.application/json
                    
                    data:formData
                    contentType:false,
                    processData:false,
                
        4.post請求後端邏輯
            1.將reuqest.POST中的數據直接交給forms組件校驗
            2.若是校驗經過 cleaned_data   ps:在存儲數據的時候models.Userinfo.objects.create_user(username='',password='')
            3.向將多餘的鍵值對的去除 將文件鍵值對手動添加進去
            4.直接將字典打散傳入 
            
            
            5.當數據校驗不經過 返回錯誤信息
                錯誤信息(form_obj.errors)以json的形式返回給ajax的回調函數
                forms組件在渲染input框的時候 id值有固定格式   id_字段名
            6.回調函數中利用each循環手動拼接input框的id值
                將錯誤渲染到對應的input下面的span標籤中
            7.健壯性功能  用戶再次輸入的時候 將報錯信息移除
                $('input').focus(function(){
                    $(this).next().html('').parent().removeClass('has-error')
                })
        

#四、登錄功能
    1.手動搭建獲取用戶登陸的前端頁面 用戶名 密碼 驗證碼
    
    2.驗證碼功能
        ps:利用的是img標籤src屬性可以支持後端url書寫 自動訪問該url
        1.直接將後端一個圖片以二進制形式發送
        2.利用pillow模塊 動態生成圖片
            pip3 install pillow
            from PIL import Image,ImageDraw,ImageFont
            # 生成圖片Image.new('RGB',(280,40),'red')/Image.new('RGB',(280,40),(255,255,255))
            # 生成畫筆對象 將生成好的圖片對象傳入ImageDraw.draw(img_obj)
            # 生成字體對象 將字體文件(.ttf)和字體大小傳入ImageFont.truetype(字體文件,字體大小)
            
            # 如何生成隨機驗證碼
            ps:chr,ord
            import random
            code = ''
            for i in range(5):
                upper_str = chr(random.randint(65,90))
                lower_str = chr(random.randint(97,122))
                random_int = str(random.randint(0,9))
                # 隨機獲取上面的一個   寫到圖片上
                res = random.choice([upper_str,lower_str,random_int])
                draw_obj.text((20+i*45,20),res,get_random(),font_obj)
                code += res
            # 須要將驗證碼存入session中以便後續的校驗
            request.session['code'] = code
            """
            1.自動生成一個隨機字符串
            2.將鍵值對存入django本身建立session表中
                ps:django session默認的超時時間 兩週 
            3.將生成好隨機字符串返回給瀏覽器保存
            """
            
    3.ajax提交數據
        1 先校驗驗證碼是否一致
        2 在校驗用戶名和密碼是否正確(auth模塊自動auth.authenticate(username='',password=''))
        3.登錄成功以後記錄用戶狀態 auth.login(request,user_obj)
            

#五、主頁
    1.將網站全部的文章都展現出來
        藉助於django admin快速錄入數據
            1.createsuperuser
            2.將須要操做的模型表註冊到對應的應用下的admin.py文件中
                admin.site.register(models.Userinfo)
            3.錄入數據的時候 關係綁定必定要考慮周全
                eg:用戶與我的站點你得手動去綁定
    2.網站用的靜態的資源默認放在static文件夾下
        用戶上傳的靜態文件應該單獨再開一個文件夾存放(media)
    
    3.去settings.py中配置路徑
        MEDIA_ROOT = os.path.join(BASE_DIR,'media')
        # 用戶上傳的文件就會自動方法media文件夾
    
    4.將media文件夾暴露給用戶
        手動開路由
        from django.views.static import serve
        from BBS import settings
        url(r'^media/(?P<path>.*)',serve,{'document_root':settins.MEDIA_ROOT})
    5.media配置能夠暴露後端任意文件夾中的資源

#六、我的站點
    1.展現的是當前用戶本身寫的全部文章
    2.側邊欄展現
    
        article_list  # 當前用戶全部的文章
        1.文章標籤
            # 查詢當前用戶下的每個標籤及標籤下的文章數
            models.Tag.objects.filter(blog=blog).annotate(c=Count('article__id')).values_list('c','tag_name')
        2.文章分類
            # 同上 理
        3.日期歸檔
            # 參考的是官方文檔
    3.側邊欄篩選功能
        1.視圖函數 共用site
            url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<params>.*)/',views.site)
        2.site視圖函數形參修改
            site(request,username,**kwargs)
        3.基於當前用戶全部的文章再作一層篩選 利用queryset對象能夠無限制的調用queryset方法
            1.判斷kwargs有沒有值
            2.獲取kwargs裏面的值
                condition = kwargs.get('condition')
                params = kwargs.get('params')
            3.判斷condition從而決定按照什麼條件篩選
                category_id
                tag__id
                基於雙下滑線 __year,__month
                
#七、文章詳情
    1.模板繼承 須要解決側邊欄展現的問題
    2.自定義inclusion_tag
        from django.template import Library
        register = Library()
        
        @register.inclusion_tag('left_menu.html')
        def left_menu(username):
            # 將我的站點視圖函數中側邊欄相關代碼直接拷貝過來 須要什麼補什麼
            ...
    3.使用 {% load my_tag %}   {% left_menu username %}
    4.點贊點踩
        1.拷貝博客園樣式(html+css)
        2.給贊和踩設置一個公共的類屬性 而後給這個類屬性綁定點擊事件
        3.經過$(this).hasClass()判斷用戶點擊的是贊仍是踩
        4.ajax發送
        5.後端接收
            1.is_up是一個字符串形式的js布爾值類型
                你能夠利用json直接轉成python布爾值
            2.首先判斷當前全球是不是ajax全球
            3.判斷用戶是否登錄request.user.is_authenticated()
            4.判斷當前文章是不是當前用戶本身寫的
                根據前端傳過來的id值 查詢出對應的文章對象
                文章對象獲取用戶對象跟request.user比較
            5.判斷當前文章是否已經被當前用戶點過
                只須要去點贊點踩查數據 若是有值就意味着 點過
                沒有 就沒有點過
            6.數據庫操做
                文章表裏面的普通字段
                        根據is_up判斷是up_num仍是down_num更新
                點贊點踩表記錄數據
                        user 
                        article
                        is_up
        6.前端渲染成功或者失敗的信息
            若是點贊點踩成功 你應該講 對應的數字加1  注意數字應該先轉成number類型再加一
            若是錯誤 找到span標籤 將信息填入便可
    5.評論
        1.根評論
            1.前端頁面搭建(參考博客園樣式)
            2.給提交按鈕綁點擊事件
                1.將用戶評論內容 當前文章id 發送給後端
                2.判斷是否登錄(login_required)
                    局部和全局配置
                    @login_required(login_url='/login/')
                    LOGIN_URL = '/login/'
                3.判斷是否ajax請求
                4.利用事務
                    """事務四大特性:ACID"""
                    from django.db import transaction
                    with transaction.atomic():
                        # 事務操做
            3.渲染評論樓
                render渲染
                    查詢當前文章全部的評論 
                    前端循環展現
                        forloop
                            counter0
                            counter
                            first
                            last
                        
                DOM臨時渲染
                    利用ecs6新語法 模板字符串
                    var userName = 'jason'
                    `my name is ${userName}`
            
            
            
            
            
        2.子評論
            1.parent字段設置的是null=True 因此在建立數據的時候該字段傳None也不會有問題
            2.點擊回覆按鈕要作的事情
                1.將回覆按鈕對應的評論的人名渲染到textarea中
                    @人名
                2.textarea自動聚焦 並換行
                    $('input').focus()
                ps:將評論對應的用戶名和評論id存到了回覆按鈕
                3.將全局parentID設值
                
            3.每次提交成功成功將parentId再次清空
            4.將子評論中的@人名文本去除
                1.前端利用slice去除
                2.後端
            5.render渲染子評論
                1.判斷當前評論對象是否有parant
                2.跨表查詢 利用parent跨本身查根評論的用戶名
    
#八、後臺管理
    1.只有登陸的用戶才能看到本身的後臺管理
    2.羅列出當前用戶寫的全部的文章
    3.添加文章
        """
        XSS攻擊
        文章簡介
        """
        1.kindeditor編輯器
        2.直接利用form表單發送post請求
        3.文章標題 文章內容  文章簡介
            1.利用bs4模塊
            from bs4 import Beautifulsoup
            res = Beautifulsoup(content,'html.parser')
            res = res.find_all()
            for tag in res:
                print(tag.name)
                if tag.name == 'script':
                    tag.decompose()
            desc = res.text[0:150]
            models.Article.objects.create(...content=str(res))
    4.上傳圖片   去文檔裏面找指定方法
    
    5.編輯
    6.刪除
    
#九、用戶頭像修改
    若是用queryset的update方法 不會自動拼接avatar前綴
    可使用對象點的方式修改
功能需求技術點

  ##代碼實現mysql

 

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'm3^_^92%936ajzk9o#+f%(2858aw_yyjimi1#ncr(tghqghdr!'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

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

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'bbs.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'bbs.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    # }
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bbs',
        'USER':'root',
        'PASSWORD':'123',
        'HOST':'127.0.0.1',
    }
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static'),
]


#auth模塊
AUTH_USER_MODEL =  'app01.Userinfo'

#規定用戶上傳到的靜態資源統一放到media文件夾
MEDIA_ROOT = os.path.join(BASE_DIR,'media')


#配置默認的登陸地址
LOGIN_URL = '/login/'
settings
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.


#定義用戶表,擴展auth模塊
class Userinfo(AbstractUser):
    phone = models.CharField(max_length=11,null=True,blank=True)#blank=True是用來admin後臺管理 該參數能夠爲空
    #當用戶上傳本身的投降的時候 會將用戶上傳的頭像文件自動存入avatar文件夾下
    avatar = models.FileField(upload_to='avatar',default='avatar/default.jpg')
    #建立數據的時候自動添加當前時間,注意auto_now_add(建立時自動建立時間,後續不會自動修改)和auto_now(實時更新)的區別
    create_time = models.DateField(auto_now_add=True)
    #用戶表和我的站點表一對一關係
    blog = models.OneToOneField(to='Blog',null=True)

    class Meta:
        verbose_name_plural = '用戶表'  #跟數據庫無關,僅僅根admin後臺相關
#定義我的站點表
class Blog(models.Model):
    # 站點名稱
    site_name = models.CharField(max_length=32)
    # 站點標題
    site_title = models.CharField(max_length=32)
    #站點樣式:存放css文件路徑
    site_theme = models.CharField(max_length=64)

    def __str__(self):
        return self.site_name

    class Meta:
        verbose_name_plural = '我的站點表'  #跟數據庫無關,僅僅根admin後臺相關
#定義文件標籤表
class Tag(models.Model):
    #標題名稱
    tag_name = models.CharField(max_length=32)
    # 我的站點外鍵:一對多
    blog = models.ForeignKey(to='Blog')

    def __str__(self):
        return self.tag_name
    class Meta:
        verbose_name_plural = '標籤表'  #跟數據庫無關,僅僅根admin後臺相關
#定義文章分類表
class Category(models.Model):
    # 分類名稱
    category_name = models.CharField(max_length=32)
    # 我的站點外鍵:一對多
    blog = models.ForeignKey(to='Blog')

    def __str__(self):
        return self.category_name
    class Meta:
        verbose_name_plural = '分類表'  #跟數據庫無關,僅僅根admin後臺相關
#定義文章表
class Article(models.Model):
    # 文章標題
    title = models.CharField(max_length=32)
    # 文章描述
    desc = models.CharField(max_length=255)
    # 文本內容:存放大段文本
    content = models.TextField()
    create_time = models.DateField(auto_now_add=True)

    # 數據庫優化相關字段(不須要跨表查詢啦),# 當你操做點贊點踩表或者評論表的時候 只要保證上面三個同步更新
    # 點贊
    up_num = models.IntegerField(default=0)
    # 點踩
    down_num = models.IntegerField(default=0)
    # 評論數
    comment_num = models.IntegerField(default=0)

    # 我的站點表:一對多
    blog = models.ForeignKey(to='Blog',null=True)
    # 分類表:一對多
    category = models.ForeignKey(to='Category',null=True)
    # 標籤表:多對多,這裏使用到了through屬性,由於django中創建多對多表時不須要手動建立,這種是全自動,可是咱們該如何給
    # 自動建立的第三張表添加本身須要的字段呢?因此須要到了該屬性
    tag = models.ManyToManyField(to='Tag',through='Artical2Tag',through_fields=('article','tag'))

    class Meta:
        verbose_name_plural = '文章'  #跟數據庫無關,僅僅根admin後臺相關

    def __str__(self):
        return self.title
# 定義多對多表中的第三張表
class Artical2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')



# 定義點贊點踩表
class UpAndDown(models.Model):
    # 外鍵用戶表:一對多
    user = models.ForeignKey(to='Userinfo')
    # 外鍵文章表:一對多
    article = models.ForeignKey(to='Article')
    # 傳佈爾類型 數據庫裏面會存成 0/1
    is_up = models.BooleanField()

    class Meta:
        verbose_name_plural = '點贊點踩表'  #跟數據庫無關,僅僅根admin後臺相關
# 定義評論表
class Comment(models.Model):
    # 外鍵用戶表:一對多
    user = models.ForeignKey(to='Userinfo')
    # 外鍵文章表:一對多
    article = models.ForeignKey(to='Article')
    # 評論內容
    content = models.TextField()
    create_time = models.DateField(auto_now_add=True)
    # 自關聯,能夠指使用'self',更能通俗易懂,也能夠用代表
    parent = models.ForeignKey(to='self',null=True)

    class Meta:
        verbose_name_plural = '評論表'  #跟數據庫無關,僅僅根admin後臺相關
models

 

from django.conf.urls import url
from django.contrib import admin
from django.views.static import serve
from bbs import settings
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^register/', views.register),
    url(r'^login/', views.login),
    url(r'^get_code/', views.get_code),
    url(r'^home/', views.home),

    #用戶相關功能
    url(r'^set_password/', views.set_password),
    url(r'^logout/', views.logout),

    #路由相關配置
    url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}),

    url(r'^up_or_down/',views.up_or_down),

    #評論
    url(r'^comment/',views.comment),

    #後臺管理
    url(r'^backend/',views.backend),
    url(r'^add_article/',views.add_article),
    url(r'^upload_img/',views.upload_img),

    #用戶修改頭像
    url(r'^set_avatar/',views.set_avatar),
    #我的站點,正則匹配起別名佔位
    url(r'^(?P<username>\w+)/$',views.site),

    #側邊欄帥選功能
    # url(r'^(?P<username>\w+)/(?P<category>category)/(?P<paream>\d+)/',views.site),
    # url(r'^(?P<username>\w+)/(?P<tag>tag)/(?P<paream>\d+)/',views.site),
    # url(r'^(?P<username>\w+)/(?P<archive>archive)/(?P<paream>\w+)/',views.site),
    # 一條路由實現上面三條路由的功能
    #url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<pareams>.*)/',views.site),
    url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<params>.*)/',views.site),

    #文章詳情頁
    url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/',views.article_detail),


]
url
#forms組件驗證模板類

from django import forms
from app01 import models
class RegForm(forms.Form):
    username = forms.CharField(max_length=8,min_length=3,label='用戶名',
                               error_messages={
                                   'max_length':'用戶最長八位',
                                   'min_length':'用戶最短三位',
                                   'required':'用戶名不能爲空',
                               },widget=forms.widgets.TextInput(attrs={'class':'form-control'})
                               )
    password = forms.CharField(max_length=8, min_length=3, label='密碼',
                               error_messages={
                                   'max_length': '密碼最長八位',
                                   'min_length': '密碼最短三位',
                                   'required': '密碼不能爲空',
                               }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})  #forms.widgets.PasswordInput密碼不顯示
                               )
    confirm_password = forms.CharField(max_length=8, min_length=3, label='確認密碼',
                               error_messages={
                                   'max_length': '密碼最長八位',
                                   'min_length': '密碼最短三位',
                                   'required': '密碼不能爲空',
                               }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                               # forms.widgets.PasswordInput密碼不顯示
                               )
    email = forms.EmailField(label='郵箱',error_messages={
        'invalid':'郵箱格式不正確',
        'required':'郵箱不能爲空'
    },widget=forms.widgets.EmailInput(attrs={'class':'form-control'})
                             )
    # 局部鉤子  判斷當前用戶名是否存在
    def clean_username(self):
        #判斷當前用戶是否存在
        username = self.cleaned_data.get('username')
        user_obj = models.Userinfo.objects.filter(username=username).first()
        if user_obj:
            self.add_error('username','用戶已存在')
        else:
            return username
    # 全局鉤子  校驗兩次密碼是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')

        if not password == confirm_password:
            self.add_error('confirm_password','兩次密碼不一致')
        else:
            return self.cleaned_data
myforms
from django.contrib import admin
from app01 import models

# Register your models here.
admin.site.register(models.Userinfo)
admin.site.register(models.Blog)
admin.site.register(models.Tag)
admin.site.register(models.Category)
admin.site.register(models.Article)
admin.site.register(models.Artical2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
admin
import json
import random

from django.shortcuts import render,HttpResponse,redirect
from django.utils.safestring import mark_safe

from app01.myforms import RegForm
from app01 import models
from django.http import JsonResponse
from django.contrib import auth
from django.db.models.functions import TruncMonth
from django.db.models import F
from app01.utils.my_page import Pagination
# Create your views here.


def register(request):
    # 當你用ajax進行數據交互 一般 試圖函數都會事先定義一個字典
    back_dic = {'code':None,"msg":None}
    form_obj = RegForm()
    if request.method == 'POST':
        #利用forms組件校驗普通字段的數據,即把傳過來的數據放到ReForm去校驗
        form_obj = RegForm(request.POST)
        if form_obj.is_valid():
            # 校驗經過,從claend_data拿到全部數據
            clean_data = form_obj.cleaned_data
            """
               clean_data = {'username':'','password':'','confirm_password':'','email':''}
            """
            # 將confirm_password鍵值對從clean_data去除掉
            clean_data.pop('confirm_password')

            #先獲取頭像文件對象  用戶是否上傳
            avatar = request.FILES.get('my_avatar')
            if avatar:
                clean_data['avatar'] = avatar
            """
                clean_data = {'username':'','password':'','email':'','avatar':'文件對象'}
            """
            # print(clean_data)
            #將數據保存到數據庫中
            models.Userinfo.objects.create_user(**clean_data)

            back_dic['code'] = 2000
            back_dic['msg'] = '註冊成功'

            back_dic['url'] = '/login/'

        else:
            back_dic['code'] = 2001
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request,'register.html',locals())


def login(request):
    back_dic = {'code':None,"msg":None}
    # request.is_ajax()  # 判斷當前請求是ajax請求
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        # 驗證碼忽略大小寫進行比較
        if request.session.get('code').upper() == code.upper():
            #利用auth認證驗證用戶信息,由於密碼也是密文存入數據庫中的
            user_obj = auth.authenticate(username=username,password=password)
            # 相似於但不是等於,注意哈models.Userinfo.objects.filter(username=username,password=password).first()
            if user_obj:
                # print(request.user.is_authenticated())  # 判斷當前用戶是否登陸  這條語句在auth.login以前,結果爲False
                auth.login(request,user_obj)
                """
                做用:
                    1. 設置cookie, session,相似於request.session['username'] = user_obj
                    2. 生成request.user的對象, 這個對象能夠再視圖函數中使用 
                    3. request.user這個對象  至關於 request.session,後續能夠在任意位置經過request.user拿到當前登陸對象
                """
                back_dic['code'] =100
                back_dic['msg'] = '登陸成功'
                back_dic['url'] = '/home/'  #設置一個讓前端登錄成功後跳轉到home主頁
                # print(request.user.is_authenticated())  # 判斷當前用戶是否登陸  這條語句在auth.login以後,結果爲True
            else:
                back_dic['code'] = 200
                back_dic['msg'] = '用戶名或密碼錯誤'
        else:
            back_dic['code'] = 300
            back_dic['msg'] = '驗證碼錯誤'
        return JsonResponse(back_dic)
    return render(request,'login.html')

from PIL import Image,ImageDraw,ImageFont
from io import BytesIO

# 生成三個隨機數
def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):
    # 生成一個圖片對象
    img_obj = Image.new('RGB',(260,30),get_random())
    #在當前圖片上生成一個畫筆
    img_draw = ImageDraw.Draw(img_obj)
    # 設置樣式
    img_font = ImageFont.truetype('static/font/222.ttf',30)

    # 圖片驗證碼 (數字 小寫字母 大寫字母)  五位驗證碼  1aZd2
    # A-Z:65-90  a-z:97-122
    # 定義一個變量用來存儲驗證碼
    code = ''
    for i in range(5):
        upper_str = chr(random.randint(65,90))
        lower_str = chr(random.randint(97,122))
        random_int = str(random.randint(0,9))
        # 從上面三個裏面隨機選擇一個
        res = random.choice([upper_str,lower_str,random_int])
        #是用畫筆對象在生成的圖片上寫一個一個的驗證碼,注意書寫的座標位置是在變化的
        img_draw.text((40+ i * 40,-5),res,get_random(),img_font)
        code += res
    #保存到內存管理器中,須要用到BytesIO模塊
    # 就把它當成文件句柄
    io_obj = BytesIO()
    img_obj.save(io_obj,'png')# 圖片格式必須得傳
    #找一個公共的地方存儲驗證碼 以便後續其餘視圖函數校驗
    request.session['code']=code
    #講些好的圖片返回到前端img標籤src屬性中(再次說一下src屬性能夠存放三種:一、靜態路徑地址 二、二進制數據 三、url地址)
    # io_obj.getvalue() 獲取二進制數據,
    return HttpResponse(io_obj.getvalue())


def home(request):
    article_list = models.Article.objects.all()
    return render(request,'home.html',locals())

def set_password(request):
    if request.method == 'POST':
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        if new_password == confirm_password:
            if request.user.check_password(old_password):
                request.user.set_password(new_password)
                request.user.save()
                return redirect('/login/')
    return render(request,'set_password.html')
def logout(request):
    auth.logout(request)
    return redirect('/login/')

from django.db.models import Count
def site(request,username,*args,**kwargs):
    user_obj = models.Userinfo.objects.filter(username=username).first()
    blog = user_obj.blog
    #查詢當前用戶下對應的文章列表
    article_list = models.Article.objects.filter(blog=blog)# 當前用戶全部的文章
    if not user_obj:
        return render(request,'error.html')
    username = user_obj.username
    #判斷kwagrs是否有值,若是有值你應該對上面的article_list在作一層篩選
    if kwargs:#兩個值# {'condition':'','params':''}
        condition = kwargs.get('condition')
        params = kwargs.get('params')
        print(condition,params)
        if condition == 'category':
            #鏈式過濾查詢,獲得分類下對應的文章 注意category_id位文章表中真實存在的字段
            article_list = article_list.filter(category_id=params)
        elif condition == 'tag':
            # 鏈式過濾查詢,獲得標籤下對應的文章 注意tag__id:有文章表跨表到標籤表使用神奇雙下劃線
            article_list = article_list.filter(tag__id=params)
        elif condition == 'archive':
            # 鏈式過濾查詢,獲得年和月下對應的文章 2018-1
            year,month = params.split('-')#解壓賦值 須要分別對年和月進行刷選
            article_list = article_list.filter(create_time__year=year,create_time__month=month)


    #下面是側邊欄渲染相關

   #查詢當前用戶每個的分類及分類下的文章數,分類查文章:代表小寫
    # category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values('category_name','c')  列表套字典

    # print(category_list) <QuerySet [{'category_name': 'icon分類一', 'c': 0}, {'category_name': 'icon分類二', 'c': 1}, {'category_name': 'icon分類三', 'c': 2}]>
    #列表套元組
    category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('category_name', 'c','pk')

    #查詢當前用戶每個標籤下的文章數
    tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('tag_name', 'c','pk')
    # print(tag_list) <QuerySet [('icon標籤一', 1), ('icon標籤二', 1), ('icon標籤三', 1)]>

    #日期歸檔
    # data_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month','c')
    date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
        'month').annotate(c=Count('pk')).values_list('month', 'c')
    """
    pk:會自動幫你查找到當前表的主鍵字段
    """
    # print(date_list)
    return  render(request,'site.html',locals())



def article_detail(request,username,article_id):
    #獲取具體文章的對象
    article_obj = models.Article.objects.filter(pk=article_id).first()
    blog = article_obj.blog
    #將當前文章的全部評論內容查詢出來發給前端渲染
    comment_list  = models.Comment.objects.filter(article=article_obj)

    #分頁
    page_obj = Pagination(current_page=request.GET.get('page',1),all_count=comment_list.count())
    page_queryset = comment_list[page_obj.start:page_obj.end]
    return render(request,'article_detail.html',locals())

#點贊點踩
def up_or_down(request):
    back_dic = {"code": None, 'msg': ''}
    #這裏能夠來個騷操做,判斷ajax請求
    if request.is_ajax():
        """
        1.用戶是否登錄
        2.有沒有點過
        3.本身不能給本身點贊
        4.數據庫同步
            article
            upanddown
            數據要同步
        """
        is_up = request.POST.get('is_up')  # 你拿到的是js格式的布爾值 對應到python裏面就是字符串
        is_up = json.loads(is_up)  # 利用json模塊 將字符串形式轉成python裏面的布爾值

        article_id = request.POST.get('article_id')
        #一、判斷用戶是否登錄
        if request.user.is_authenticated():
            #二、判斷當前文章是否是用戶本身寫的,即判斷當前文章對象和當前登陸用戶是不是一我的
            #先拿到當前文章對象
            article_obj = models.Article.objects.filter(pk=article_id).first()
            if article_obj:
                if not article_obj.blog.userinfo.pk == request.user.pk:
                    #三、判斷當前文章是否被點過了,即去點贊表查是否被點過了 逆向思惟
                    is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)
                    if not is_click:
                        #沒有點過,記錄數據,問題:究竟是點贊仍是點踩
                        if is_up:#若是是點贊
                            models.Article.objects.filter(pk=article_id).update(up_num = F('up_num')+1)
                        else:
                            models.Article.objects.filter(pk=article_id).update(up_num=F('down_num') + 1)
                        #點贊點踩操做點贊點踩表寫入數據
                        models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)
                        back_dic['code'] = 2000
                        back_dic['msg'] = '點同意功' if is_up else '點踩成功'
                    else:
                        back_dic['code'] = 2001
                        back_dic['msg'] = '你已經點過了'
                else:
                    back_dic['code'] = 2002
                    back_dic['msg'] = '你個臭不要臉的 不能給本身點'
            else:
                back_dic['code'] = 2003
                back_dic['msg'] = '未知錯誤'
        else:
            back_dic['code'] = 2004
            #這裏使用後端取消轉義字符
            back_dic['msg'] = mark_safe('請先<a href="//login">登陸</a>')
        return JsonResponse(back_dic)



"""
事務:ACID
原子性
一致性
隔離性
持久性

將評論內容同步寫入到評論表和文章表,這裏用到事務的原子性,要麼一塊兒成功,要麼一塊兒失敗
"""
from django.contrib.auth.decorators import login_required
from django.db import transaction  # django中開啓事務 須要先倒入該模塊
@login_required  #全局登陸認證裝飾器
def comment(request):
    back_dic = {'code':None,'msg':''}
    if request.is_ajax():
        comment = request.POST.get('comment')
        article_id = request.POST.get('article_id')
        parent_id = request.POST.get('parent_id')
        with transaction.atomic():
            # 在with代碼塊寫的就是一個事務
            # 文章表修改comment_num字段
            models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num')+1)
            # 評論表裏面新增數據
            models.Comment.objects.create(user=request.user,article_id=article_id,content=comment,parent_id=parent_id)
            back_dic['code'] = 2000
            back_dic['msg'] = '評論成功'
        return JsonResponse(back_dic)


@login_required
def backend(request):
    article_list = models.Article.objects.filter(blog=request.user.blog)
    # 分頁
    page_obj = Pagination(current_page=request.GET.get('page', 1), all_count=article_list.count())
    page_queryset = article_list[page_obj.start:page_obj.end]
    return render(request,'backend/backend.html',locals())

from bs4 import BeautifulSoup
@login_required
def add_article(request):
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')

        # 可以幫我拿到當前用戶寫的全部的標籤 將script刪除
        res = BeautifulSoup(content,'html.parser')
        #獲取全部標籤
        tags = res.find_all()
        for tag in tags:
            # 將script所有刪除
            if tag.name == 'script':
                # 刪除指定的標籤
                tag.decompose()
        # 獲取用戶輸入的文本值
        desc = res.text[0:150]
        models.Article.objects.create(title=title,content=str(res),desc=desc,blog=request.user.blog)
        return redirect('/backend/')
    return render(request,'backend/add_article.html')

from bbs import settings
import os
@login_required
def upload_img(request):
    back_dic = {'error': ''}
    if request.method == 'POST':
        # print(request.FILES) 查看字典的key 和文件值
        img_obj = request.FILES.get('imgFile')
        # 規定編輯器上傳的圖片所有放到media文件夾裏面的upload_img文件夾下
        # 1.將文件存儲到media文件夾下
        path = os.path.join(settings.BASE_DIR, 'media', 'upload_img')
        if not os.path.exists(path):
            os.mkdir(path)
        file_path = os.path.join(path, img_obj.name)
        with open(file_path, 'wb') as f:
            for line in img_obj:
                f.write(line)
        # 2.將文件路徑返回給前端
        back_dic['error'] = 0
        back_dic['url'] = '/media/upload_img/%s' % img_obj.name
        """
        //成功時
        {
                "error" : 0,
                "url" : "http://www.example.com/path/to/file.ext"
        }
        //失敗時
        {
                "error" : 1,
                "message" : "錯誤信息"
        }

        """
    return JsonResponse(back_dic)


#用戶修改頭像
@login_required
def set_avatar(request):
    username = request.user.username
    if request.method == 'POST':
        new_avatar = request.FILES.get('new_avatar')

        # 若是用queryset對象更新頭像 不會自動幫你拼接前綴,要手動拼接
        # models.Userinfo.objects.filter(pk=request.user.pk).update(avatar = new_avatar)
        #或者直接經過對象點屬性來進行修改,可是這樣的缺點是該對象的整個信息都會重新寫一遍
        user_obj = models.Userinfo.objects.filter(pk=request.user.pk).first()
        user_obj.avatar = new_avatar
        user_obj.save()
        return redirect('/home/')
    return render(request,'set_avatar.html',locals())
views
from django.template import Library
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
register = Library()

#自定義inclusion_tag
@register.inclusion_tag('left_menu.html')
def left_menu(username):
    blog = models.Blog.objects.filter(site_name=username).first()
    # 列表套元組
    category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('category_name',
                                                                                                       'c', 'pk')

    # 查詢當前用戶每個標籤下的文章數
    tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('tag_name', 'c', 'pk')
    # print(tag_list) <QuerySet [('icon標籤一', 1), ('icon標籤二', 1), ('icon標籤三', 1)]>

    # 日期歸檔
    # data_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month','c')
    date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
        'month').annotate(c=Count('pk')).values_list('month', 'c')
    """
    pk:會自動幫你查找到當前表的主鍵字段
    """
    # print(date_list)

    return locals()
my_tag include_tag過濾器

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
     <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css">
    <script src="/static/bs-3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <h1 class="text-center">註冊</h1>
        <hr>
        <form action="" id="myform" novalidate>
            {% csrf_token %}
            {% for foo in form_obj %}
                <div class="form-group">
                    <label for="{{ foo.auto_id }}">{{ foo.label }}</label>
                    {{ foo }}
                    <span class="pull-right" style="color: red"></span>
                </div>
            {% endfor %}
            <div class="form-group">
                {#  label標籤for屬性指向input,點擊頭像調用input功能#}
                <label for="id_file">頭像
                <img src="/static/image/default.jpg" alt="" width="80px" style="margin-left: 20px" id="id_img">
                </label>

                <input type="file" name="myfile" id="id_file" class="hidden">
            </div>
            <input type="button" class="btn btn-success pull-right" value="註冊" id="id_submit">
        </form>
    </div>
</div>
</body>
<script>
    $('#id_file').change(function () {
        {#獲取文件對象#}
        var fileObj = $(this)[0].files[0];
        {#利用內置對象:文件閱讀器FileReader    #}
        var fileReader = new FileReader();
        {#將文件對象交給文件閱讀器對象,生成文件對象的二進制數據    #}
        fileReader.readAsDataURL(fileObj); //異步,不會等待加載完畢就繼續執行下面的語句
        {#DOM操做修改img標籤的src屬性值    #}
        fileReader.onload = function () {
            $('#id_img').attr('src',fileReader.result)
        };

    });
    // 點擊註冊按鈕 觸發點擊事件
    $('#id_submit').click(function () {
        // 利用內置對象FormData完成既有普通鍵值又有文件數據的發送
        var formData = new FormData()
        // 添加普通鍵值對
        // console.log($('#myform').serializeArray());  // 會將form標籤內 普通的鍵值對 自動組成一個數組的形式返回給你
        $.each($('#myform').serializeArray(),function (index,obj) {// $.each(你想要被循環的對象,函數(索引,單個單個的對象))
            formData.append(obj.name,obj.value) // 僅僅是將普通的鍵值對添加進去
        });
        {# 添加文件數據   #}
        formData.append('my_avatar',$('#id_file')[0].files[0])
        //ajax發送數據
        $.ajax({
            url:'',
            type:'post',
            data:formData,
            contentType:false,
            processData: false,
            success:function (data) {
                if (data.code == 2000){
                    window.location.href=data.url
                }else {
                    {#console.log(data.msg)#}
                    $.each(data.msg,function (index,obj) {
                        {#console.log(index,obj)#}
                        //字符串拼接獲得input對應的id值,在前端可查到input的id格式位id_username等
                        var targetid = '#id_'+ index;
                        //查找span標籤,添加文本信息這裏使用html()方法,也可以使用text()方法添加
                        $(targetid).next().html(obj[0]).parent().addClass('has-error')
                    })
                }
            }
        })
    })

    // input框獲取焦點事件
    $('input').focus(function () {
        // 將當前input所在的div移除has-error屬性 並將下面的span標籤內的內容也移除了
        $(this).next().html('').parent().removeClass('has-error')
    })
</script>
</html>
register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css">
    <script src="/static/bs-3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">登陸</h1>
            <hr>
            <div class="form-group">
                <label for="id_username">用戶名</label>
                <input type="text" name="username" id="id_username" class="form-control">
            </div>
            <div class="form-group">
                <label for="id_password">密碼</label>
                <input type="password" name="password" id="id_password" class="form-control">
            </div>
            <div class="form-froup">
                <label for="id_code">驗證碼</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" name="code" id="id_code" class="form-control">
                    </div>
                    <div class="col-md-6">
                        <img src="/get_code/" alt="" width="260px" height="30px" id="id_img">
                    </div>
                </div>
            </div>
            <br>
            <input type="button" class="btn btn-info" value="登陸" id="id_submit">
            <span style="color: red" id="id_errro" class="pull-right"></span>
        </div>

    </div>
</div>
</body>
<script>
    {#點擊驗證碼局部雙薪驗證碼    #}
    $('#id_img').click(function () {
        var oldPath = $(this).attr('src');
        {#方法一:直接在src屬性後面添加任意字符,這裏添加一個問好#}
        {#$(this).attr('src',oldPath += '?');#}

        // 方法二:優化思路 判斷當前url後面是否有問號 有問號就去掉 沒有問號就加上
        if( oldPath.includes('?')){
            oldPath.substring(0,oldPath.length-1)
        }else {
            oldPath += '?'
        }
        $(this).attr('src',oldPath)
    })

    $('#id_submit').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{
                'username':$('#id_username').val(),
                'password':$('#id_password').val(),
                'code':$('#id_code').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}'
            },
            success:function (data) {
                if (data.code == 100){
                    {# 登陸成功跳轉到主頁   #}
                    window.location.href = data.url
                }else {
                    $('#id_errro').html(data.msg)
                }
            }
        })
    })

</script>
</html>
login.html
{% extends 'base.html' %}

{% block css %}
    <style>
        #div_digg {
            float: right;
            margin-bottom: 10px;
            margin-right: 30px;
            font-size: 12px;
            width: 128px;
            text-align: center;
            margin-top: 10px;
        }

        .diggit {
            float: left;
            width: 46px;
            height: 52px;
            background: url('/static/image/upup.gif') no-repeat;
            text-align: center;
            cursor: pointer;
            margin-top: 2px;
            padding-top: 5px;
        }

        .buryit {
            float: right;
            margin-left: 20px;
            width: 46px;
            height: 52px;
            background: url('/static/image/downdown.gif') no-repeat;
            text-align: center;
            cursor: pointer;
            margin-top: 2px;
            padding-top: 5px;
        }

        .clear {
            clear: both;
        }

        .diggword {
            margin-top: 5px;
            margin-left: 0;
            font-size: 12px;
            color: gray;
        }
    </style>
{% endblock %}
{% block content %}
    <h1>{{ article_obj.title }}</h1>
    {# 取消轉義   #}
    {{ article_obj.content |safe }}
    {#點贊點踩    #}
    <div class="clearfix">
        <div id="div_digg ">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.dowm_num }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips">
                <span style="color: red" id="id_info"></span>
            </div>
        </div>
    </div>

    {#評論樓渲染    #}
    {#    #51樓 2018-12-05 15:50 漏網的小魚  #}
    <h4>評論列表</h4>
    <ul class="list-group">
        {% for comment in page_queryset %}
            <li class="list-group-item">
                <div>
                    <span>#{{ forloop.counter }}樓</span>
                    <span>{{ comment.create_time|date:'Y-m-d' }}</span>
                    <span><a href="/{{ comment.user.username }}/">{{ comment.user.username }}</a></span>
                    <span class="pull-right  reply" username="{{ comment.user.username }}" comment_id="{{ comment.id }}"><a>回覆</a></span>
                </div>
                {#                comment.parent判斷評論表中的parent字段存不存在#}
                {% if comment.parent %}
                {#                    comment.parent.user.username 從評論表跨到用戶表查詢根評論的用戶名#}
                <p><a href="#">@</a>{{ comment.parent.user.username }}</p>

                {% endif %}
                {{ comment.content }}
            </li>
        {% endfor %}
        {{ page_obj.page_html|safe }}

    </ul>


    {#評論樣式#}
    <div>
        <p><span class="glyphicon glyphicon-comment">發表評論</span></p>
        <p>
            暱稱:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
                      value="{{ request.user.username }}">
        </p>
        <p>評論內容:</p>
        <p>
            <textarea name="" id="id_comment" cols="60" rows="10"></textarea>
        </p>
        <p>
            <input type="button" class="btn btn-primary" value="提交評論" id="id_submit">
        </p>
    </div>

    <script>
        //點贊點踩
        $('.action').click(function () {
            {#判斷是點贊仍是點踩的關鍵代碼#}
            var isUp = $(this).hasClass('diggit');
            {#獲取點贊和點踩所在的div層,要對數值進行操做#}
            var $spanEle = $(this).children();
            $.ajax({
                url: '/up_or_down/',
                type: 'post',
                data: {'is_up': isUp, 'article_id':{{article_obj.pk}}, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
                success: function (data) {
                    if (data.code == 2000) {
                        {#給span標籤設置值#}
                        $('#id_info').html(data.msg);
                        $spanEle.text(Number($spanEle.text()) + 1);
                    } else {
                        {#點贊失敗後給span設置值#}
                        $('#id_info').html(data.msg)
                    }
                }
            })
        })
        //評論邏輯
        //定義一個全局的parentid變量名
        parentId = null;

        //點擊回覆按鈕發生了幾件事
        //        1.將根評論的人的名字放到了textarea中(@用戶名)
        //        2.自動換行
        //        3.textarea自動獲取焦點
        //1.在存儲數據的時候 應該將@用戶名去掉(前端 後端)
        //2.textarea應該清空
        //3.全局的parentid每次發送評論以後應該自動清空



        $('#id_submit').click(function () {
            //獲取評論內容
            var comment = $('#id_comment').val();

            if(parentId){
                // 將comment中的@人名清空掉
                // 先獲取\n所在的索引值
                var nIndex = comment.indexOf('\n'); // 根據內容查索引
                comment = comment.slice(nIndex)
            }
            $.ajax({
                url: '/comment/',
                type: 'post',
                data: {
                    'comment': comment,
                    'article_id':{{ article_id }},
                    'csrfmiddlewaretoken': '{{ csrf_token }}',
                    'parent_id':parentId
                },
                success: function (data) {
                    if(data.code ==2000){
                        //DOM操做渲染標籤
                        //獲取用戶名
                        var username = '{{ request.user.username }}';
                        //獲取評論內容
                        var comment =$('#id_comment').val()
                        //動態生成li標籤
                        var tempStr = `
                            <li class="list-group-item">
                            <div>
                            <span class='glyphicon glyphicon-comment'>${username}</span>
                            </div>
                            ${comment}
                            </li>
                        `;
                        // 查找url標籤 將上面的字符串追加到ul標籤內
                        $('.list-group').append(tempStr)
                        $('#id_comment').val('')
                        parentId = null;
                    }
                }
            })
        })
    
        //點擊恢復按鈕發生了下面三件事
        $('.reply').click(function () {
            // 獲取當前回覆按鈕所對應的根評論的用戶名
            var username = $(this).attr('username');
            //評論內容區域設置@用戶名換行,focus自動獲取焦點
            $('#id_comment').html('@'+username+'\n').focus();
            //將全局的parentId修改成被評論對象的id,這個id設置在回覆按鈕的span屬性中,爲啥呢,由於這裏用起來方便
            parentId=$(this).attr('comment_id')
        })
    </script>
{% endblock %}
article_detail.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css">
    <script src="/static/bs-3.3.7/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/media/css/{{ blog.site_theme }}">
    <style>
        .d1{
            margin-top: 5px;
        }
    </style>
    {% block css %}

    {% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">{{ blog.site_title }}</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
{#        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">#}
{#            <ul class="nav navbar-nav">#}
{#                <li class="active"><a href="#">文章<span class="sr-only">(current)</span></a></li>#}
{#                <li><a href="#">隨筆</a></li>#}
{#            </ul>#}
{##}
{#            <ul class="nav navbar-nav navbar-right">#}
{#                {% if request.user.is_authenticated %}#}
{#                    <li><a href="#">{{ request.user.username }}</a></li>#}
{#                    <li class="dropdown">#}
{#                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"#}
{#                           aria-expanded="false">更多操做 <span class="caret"></span></a>#}
{#                        <ul class="dropdown-menu">#}
{#                            <li><a href="/set_password/">修改密碼</a></li>#}
{#                            <li><a href="#">修改頭像</a></li>#}
{#                            <li role="separator" class="divider"></li>#}
{#                            <li><a href="/logout/">註銷</a></li>#}
{#                        </ul>#}
{#                    </li>#}
{#                {% else %}#}
{#                    <li><a href="/login/">登陸</a></li>#}
{#                    <li><a href="/register/">註冊</a></li>#}
{#                {% endif %}#}
{#            </ul>#}
{#        </div><!-- /.navbar-collapse -->#}
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            {% load my_tag %}
            {% left_menu username %}
        </div>
        <div class="col-md-9">
            {% block content %}

            {% endblock %}

        </div>

    </div>
</div>
</body>
</html>
base.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Error_404_資源不存在</title>
    <style type="text/css">
    body{margin:8% auto 0;max-width: 550px; min-height: 200px;padding:10px;font-family: Verdana,Arial,Helvetica,sans-serif;font-size:14px;}p{color:#555;margin:10px 10px;}img {border:0px;}.d{color:#404040;}
    </style>
</head>
<body>
<a href="http://www.cnblogs.com/"><img src="//static.cnblogs.com/images/logo_small.gif" alt="cnblogs"/></a>
<p><b>404.</b> 抱歉! 您訪問的資源不存在!</p>
<p class="d">請確認您輸入的網址是否正確,若是問題持續存在,請發郵件至contact&#64;cnblogs.com與咱們聯繫。</p>
<p><a href="/home/">返回網站首頁</a></p>
</body>
</html>
error.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css">
    <script src="/static/bs-3.3.7/js/bootstrap.min.js"></script>

    <style>
        .d1{
            margin-top: 5px;
        }
    </style>
</head>
<body>
<nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">博客園</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">文章<span class="sr-only">(current)</span></a></li>
                <li><a href="#">隨筆</a></li>
            </ul>

            <ul class="nav navbar-nav navbar-right">
                {% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">更多操做 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="/set_password/">修改密碼</a></li>
                            <li><a href="/set_avatar/">修改頭像</a></li>
                            <li><a href="/backend/">後臺管理</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="/logout/">註銷</a></li>
                        </ul>
                    </li>
                {% else %}
                    <li><a href="/login/">登陸</a></li>
                    <li><a href="/register/">註冊</a></li>
                {% endif %}
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">託兒所</h3>
                </div>
                <div class="panel-body">
                    個人劍就是你的劍
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">兒童劫</h3>
                </div>
                <div class="panel-body">
                    六一快樂
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">皮皮瞎</h3>
                </div>
                <div class="panel-body">
                    無形之刃最爲致命
                </div>
            </div>
        </div>
        <div class="col-md-8">
            {% for article in article_list %}
                <div class="media">
                    <h4 class="media-heading"><a href="/{{ article.blog.userinfo.username }}/article/{{ article.pk }}/">{{ article.title }}</a></h4>
                    <div class="media-left">
                        <a href="#">
                            <img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" alt="..." width="60">
                        </a>
                    </div>
                    <div class="media-body">
                        {{ article.desc }}
                    </div>
                    <dev class="d1 pull-right">
                        {#   MouseDong 發佈於 2019-07-24 17:50 評論(0)閱讀(128)           #}
                        <span><a href="">{{ article.blog.userinfo.username }}&nbsp;&nbsp;</a></span>
                        <span>發佈於&nbsp;&nbsp;{{ article.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
                        <span><span class="glyphicon glyphicon-comment"></span>評論{{ article.comment_num }}&nbsp;&nbsp;</span>
                        <span><span class="glyphicon glyphicon-thumbs-up"></span>點贊{{ article.up_num }}&nbsp;&nbsp;</span>
                    </dev>
                </div>
                 <hr>
            {% endfor %}

        </div>
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">賈科斯</h3>
                </div>
                <div class="panel-body">
                    你過小看我啦
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">草叢倫</h3>
                </div>
                <div class="panel-body">
                    個人大刀已經飢渴難耐
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">德邦</h3>
                </div>
                <div class="panel-body">
                    看個人
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>
home.html
<div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">文章分類</h3>
                </div>
                <div class="panel-body">
                    {% for category in category_list %}
                        <p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}</a>({{ category.1 }})</p>
                    {% endfor %}

                </div>
            </div>
<div class="panel panel-info">
    <div class="panel-heading">
        <h3 class="panel-title">文章標題</h3>
    </div>
    <div class="panel-body">
        {% for tag in tag_list %}
            <p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }}</a>({{ tag.1 }})</p>
        {% endfor %}

    </div>
</div>
<div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">日期歸檔</h3>
                </div>
                <div class="panel-body">
                        {% for date in date_list %}
                            <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y年m月'}}</a>({{ date.1 }})</p>
                        {% endfor %}
                </div>
            </div>
left_menu.html
{% extends 'base.html' %}

{% block content %}
    <h2 class="text-center">修改頭像</h2>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        {#顯示用戶當前頭像 ,頭像路徑在數據庫中存在形式爲:avatar/2.jpg  ,因此直接獲取就能夠不須要在拼接全路徑     #}
        <img src="/media/{{ request.user.avatar }}" alt="">
        <p>
            <label for="d1">選擇頭像:<input type="file" id="d1" name="new_avatar"></label>
        </p>
        <input type="submit" class="btn btn-success" value="修改">
    </form>
{% endblock %}
set_avatar.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css">
    <script src="/static/bs-3.3.7/js/bootstrap.min.js"></script>
</head>
<body>


<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">修改密碼</h1>
            <hr>
            <form action="" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label for="id_username">用戶名</label>
                    <input type="text" name="username" id="id_username" class="form-control"
                           value="{{ request.user.username }}" disabled>
                </div>
                <div class="form-group">
                    <label for="id_password">原密碼</label>
                    <input type="password" name="old_password" id="id_password" class="form-control">
                </div>
                <div class="form-group">
                    <label for="id_new_password">新密碼</label>
                    <input type="password" name="new_password" id="id_new_password" class="form-control">
                </div>
                <div class="form-group">
                    <label for="id_confirm_password">確認密碼</label>
                    <input type="password" name="confirm_password" id="id_confirm_password" class="form-control">
                </div>
                <br>
                <input type="submit" class="btn btn-info" value="提交" id="id_submit">
                <span style="color: red" id="id_errro" class="pull-right"></span>
            </form>
        </div>

    </div>
</div>
</body>

</html>
set_password.html
{% extends 'base.html' %}

{% block content %}
     {% for article in article_list %}
                <div class="media">
                    <h4 class="media-heading"><a href="/{{ username }}/article/{{ article.pk }}">{{ article.title }}</a></h4>
                    <div class="media-left">
                        <a href="#">
                            <img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" alt="..." width="60">
                        </a>
                    </div>
                    <div class="media-body">
                        {{ article.desc }}
                    </div>
                    <dev class="d1 pull-right">
                        {#   MouseDong 發佈於 2019-07-24 17:50 評論(0)閱讀(128)           #}
                        <span><a href="">{{ article.blog.userinfo.username }}&nbsp;&nbsp;</a></span>
                        <span>@{{ article.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
                        <span><span class="glyphicon glyphicon-comment"></span>評論{{ article.comment_num }}&nbsp;&nbsp;</span>
                        <span><span class="glyphicon glyphicon-thumbs-up"></span>點贊{{ article.up_num }}&nbsp;&nbsp;</span>
                        <span><a href="">編輯</a></span>
                    </dev>
                </div>
                 <hr>
            {% endfor %}
{% endblock %}
site.html
{% extends 'backend/backendbase.html' %}
{% block content %}
    <h3>文章標題</h3>
    <hr>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <p>標題</p>
        <input type="text" name="title" id="id_title" class="form-control">
        <p>內容(kindeditor編輯器,支持拖放/粘貼上傳圖片)</p>
        <p>
            <textarea name="content" id="id_content" cols="30" rows="10"></textarea>
        </p>
        <input type="submit" value="發佈" class="btn btn-danger">
    </form>
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#id_content', {
                width: '100%',
                height:'450px',
                {#0 表示不能拖拽#}
                resizeType:0,
                // 控制文件上傳的位置
                uploadJson : '/upload_img/',
                 extraFileUploadParams : {
                        'csrfmiddlewaretoken':'{{ csrf_token }}'

                }
            });
        });
    </script>
{% endblock %}
add_article.html
{% extends 'backend/backendbase.html' %}

{% block content %}
    <table class="table table-hover table-striped">
        <thead>
        <tr>
            <th>標題</th>
            <th>評論數</th>
            <th>點贊數</th>
            <th>點踩數</th>
            <th>操做</th>
            <th>操做</th>
        </tr>
        </thead>
        <tbody>
        {% for article in page_queryset %}
            <tr>
                <td><a href="/{{ request.user.username }}/article/{{ article.pk }}/">{{ article.title }}</a></td>
                <td>{{ article.comment_num }}</td>
                <td>{{ article.up_num }}</td>
                <td>{{ article.down_num }}</td>
                <td><a href="#">編輯</a></td>
                <td><a href="#">刪除</a></td>
            </tr>

        {% endfor %}

        </tbody>
    </table>
    {{ page_obj.page_html|safe }}
{% endblock %}
backend.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css">
    <script src="/static/bs-3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">後臺管理</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">{{ request.user.username }}</a></li>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
            <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
                <div class="panel panel-default">
                    <div class="panel-heading" role="tab" id="headingOne">
                        <h4 class="panel-title">
                            <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
                               aria-expanded="true" aria-controls="collapseOne">
                                操做
                            </a>
                        </h4>
                    </div>
                    <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
                         aria-labelledby="headingOne">
                        <div class="panel-body">
                            <a href="/add_article/">添加文章</a>
                        </div>
                    </div>
                    <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
                         aria-labelledby="headingOne">
                        <div class="panel-body">
                            <a href="#">添加隨筆</a>
                        </div>
                    </div>
                    <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
                         aria-labelledby="headingOne">
                        <div class="panel-body">
                            <a href="#">我的設計</a>
                        </div>
                    </div>
                    <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
                         aria-labelledby="headingOne">
                        <div class="panel-body">
                            <a href="#">重金求子</a>
                        </div>
                    </div>
                </div>
            </div>

        </div>
        <div class="col-md-10">
            <div>

                <!-- Nav tabs -->
                <ul class="nav nav-tabs" role="tablist">
                    <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
                                                              data-toggle="tab">文章</a></li>
                    <li role="presentation"><a href="#profile" aria-controls="profile" role="tab"
                                               data-toggle="tab">隨筆</a></li>
                    <li role="presentation"><a href="#messages" aria-controls="messages" role="tab"
                                               data-toggle="tab">設置</a></li>
                    <li role="presentation"><a href="#settings" aria-controls="settings" role="tab"
                                               data-toggle="tab">更多</a></li>
                </ul>

                <!-- Tab panes -->
                <div class="tab-content">
                    <div role="tabpanel" class="tab-pane active" id="home">
                        {% block content %}
                        
                        {% endblock %}
                    </div>
                    <div role="tabpanel" class="tab-pane" id="profile">隨筆頁面</div>
                    <div role="tabpanel" class="tab-pane" id="messages">設置頁面</div>
                    <div role="tabpanel" class="tab-pane" id="settings">更多頁面</div>
                </div>

            </div>
        </div>
    </div>
</div>
</body>
</html>
backendbase.html

  ##源碼地址(百度雲)jquery

相關文章
相關標籤/搜索