BBS(仿博客园项目代码部分)

一 BBS

1  __init__.py

 

import pymysql

pymysql.install_as_MySQLdb()

 

2  setting.py

"""
Django settings for BBS project.

Generated by 'django-admin startproject' using Django 1.11.9.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

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 = '4ko*)y@ov6np97g)o!v@jf!yuc@bm_5q0!r2u1&aef&nn64p+q'

# 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',
'blog.apps.BlogConfig',
]

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',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '',
}
}

# 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'
# 后台管理界面变成中文
LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True
# 不用UTC时间
USE_TZ = False

# 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_USER_MODEL = 'blog.UserInfo'

random_color = 255

# avatar = models.FileField(upload_to='avatar/', default='/static/img/default.png')
# 如果不配置MEDIA_ROOT,和MEDIA_URL,默认存到avatar
# 如果配置了MEDIA_ROOT,和MEDIA_URL,默认存到media/avatar
# 存到数据库中,不带media
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_BBS = os.path.join(BASE_DIR, "BBS")


LOGIN_URL='/login/'


EMAIL_HOST = 'smtp.qq.com' # 如果是 163 改成 smtp.163.com 以什么邮箱发送
EMAIL_PORT = 465 #端口号
EMAIL_HOST_USER = '306334678@qq.com' # 帐号 发送者邮箱账号
EMAIL_HOST_PASSWORD = 'odokxrzlfejkcbbh' # 密码 不是密码, 授权码
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
#这样收到的邮件,收件人处就会这样显示
#DEFAULT_FROM_EMAIL = 'lqz<'306334678@qq.com>'
EMAIL_USE_SSL = True #使用ssl,qq只支持这种

3 Urls.py
"""BBS URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.contrib import admin
from blog import views
from django.views.static import serve
from BBS import settings
urlpatterns = [
# 放到最上面或者中间,都不合适
# url(r'^(?P<username>[\w]+)', views.user_blog),
url(r'^$', views.index),
url(r'^admin/', admin.site.urls),
url(r'^login/', views.login),
url(r'^get_valid_code/', views.get_valid_code),
url(r'^register/', views.register),
url(r'^check_username/', views.check_username),
url(r'^index/', views.index),
url(r'^logout/', views.logout),
# 有名分组
# url第一个参数正则表达式,第二个参数,函数的内存地址,第三个参数:字典,它会以关键字参数的形式,传到(第二个参数的)函数中,第四个参数,别名
# 当你从浏览器输入:media/后面的路径回去settings.MEDIA_ROOT这个变量对应的文件夹下去寻找
url(r'^media/(?P<path>.*)', serve,{'document_root':settings.MEDIA_ROOT}),
# url(r'^bb/(?P<path>.*)', serve,{'document_root':settings.MEDIA_BBS}),

# # 个人站点过滤分类的路由
# url(r'^(?P<username>[\w]+)/category/(?P<category_id>\d+)', views.user_blog),
# # 个人战点,标签过滤的路由
# url(r'^(?P<username>[\w]+)/tag/(?P<tag_id>\d+)', views.user_blog),
# # 随笔档案的路由(又得写好多)
# # url(r'^(?P<username>[\w]+)/tag/(?P<tag_id>\d+)', views.user_blog),

# 点赞的路由
url(r'^diggit/$', views.diggit),
# 评论的路由
url(r'^commit_content/$', views.commit_content),
url(r'^backend', views.backend),
url(r'^add_article', views.add_article),
# 富文本编辑器上传图片
url(r'^upload_img', views.upload_img),
# 修改头像
url(r'^update_head', views.update_head),
# 修改文章
url(r'^update_article/(?P<pk>\d+)', views.update_article),
# ajax获取文件的口
url(r'^get_article/(?P<pk>\d+)', views.get_article),



# 三个过滤(分类,标签,归档),走一条路由
# 分组分出三个(用户名,category|tag|archive中的一个,可能是分类id,tag_id,时间)
url(r'^(?P<username>[\w]+)/(?P<condition>category|tag|archive)/(?P<param>.*)', views.user_blog),
url(r'^(?P<username>[\w]+)/article/(?P<id>\d+)', views.article_detail),



# 放到最后,都匹配完成,没有匹配到,再匹配它
url(r'^(?P<username>[\w]+)$', views.user_blog),

]

4 wsgi.py
"""
WSGI config for BBS project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BBS.settings")

application = get_wsgi_application()

二 BLOG
migrations
1 initial.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.9 on 2018-11-27 02:26
from __future__ import unicode_literals

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0008_alter_user_username_max_length'),
]

operations = [
migrations.CreateModel(
name='UserInfo',
fields=[
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('nid', models.AutoField(primary_key=True, serialize=False)),
('phone', models.CharField(max_length=32, null=True)),
('avatar', models.FileField(default='/static/img/default.png', upload_to='avatar/')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='Article',
fields=[
('nid', models.AutoField(primary_key=True, serialize=False)),
('title', models.CharField(max_length=64)),
('desc', models.CharField(max_length=255)),
('content', models.TextField()),
('create_time', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='ArticleTOTag',
fields=[
('nid', models.AutoField(primary_key=True, serialize=False)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.Article')),
],
),
migrations.CreateModel(
name='Blog',
fields=[
('nid', models.AutoField(primary_key=True, serialize=False)),
('title', models.CharField(max_length=64)),
('site_name', models.CharField(max_length=32)),
('theme', models.CharField(max_length=64)),
],
),
migrations.CreateModel(
name='Category',
fields=[
('nid', models.AutoField(primary_key=True, serialize=False)),
('title', models.CharField(max_length=64)),
('blog', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.Blog')),
],
),
migrations.CreateModel(
name='Commit',
fields=[
('nid', models.AutoField(primary_key=True, serialize=False)),
('content', models.CharField(max_length=255)),
('create_time', models.DateTimeField(auto_now_add=True)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.Article')),
('parent_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.Commit')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Tag',
fields=[
('nid', models.AutoField(primary_key=True, serialize=False)),
('title', models.CharField(max_length=64)),
('blog', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.Blog')),
],
),
migrations.CreateModel(
name='UpAndDown',
fields=[
('nid', models.AutoField(primary_key=True, serialize=False)),
('is_up', models.BooleanField()),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.Article')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='articletotag',
name='tag',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.Tag'),
),
migrations.AddField(
model_name='article',
name='blog',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.Blog'),
),
migrations.AddField(
model_name='article',
name='category',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.Category'),
),
migrations.AddField(
model_name='article',
name='tag',
field=models.ManyToManyField(through='blog.ArticleTOTag', to='blog.Tag'),
),
migrations.AddField(
model_name='userinfo',
name='blog',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='blog.Blog'),
),
migrations.AddField(
model_name='userinfo',
name='groups',
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
),
migrations.AddField(
model_name='userinfo',
name='user_permissions',
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
),
migrations.AlterUniqueTogether(
name='upanddown',
unique_together=set([('user', 'article')]),
),
]


templatetags
1 admin
from django.contrib import admin
from blog import models
# Register your models here.


admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Article)
admin.site.register(models.Tag)
admin.site.register(models.Category)
admin.site.register(models.Commit)
admin.site.register(models.UpAndDown)
admin.site.register(models.ArticleTOTag)


2 apps.py
from django.apps import AppConfig


class BlogConfig(AppConfig):
name = 'blog'

3 models.py
from django.db import models
from django.contrib.auth.models import AbstractUser


# Create your models here.
# UserInfo这个表,继承AbstractUser,因为要用auth组件

class UserInfo(AbstractUser):
nid = models.AutoField(primary_key=True)

# username=models.CharField(max_length=32,unique=True)
# 该字段可以为空,为该字段设置默认值,default='123455666'
# blank=True 只是admin中表单提交的时候,做校验,如果设置成True,就是不校验了
phone = models.CharField(max_length=32,null=True,blank=True)
# upload_to需要传一个路径(avatar文件夹会自动创建)
avatar = models.FileField(upload_to='avatar/', default='avatar/default.png')
# 如果avatar用charfield,如何处理?
# 一对一关联blog表,to_field如果不写,默认主键
# blog_id字段存的数据是什么?blog表的---nid这个字段
blog = models.OneToOneField(to='Blog', to_field='nid',null=True)

class Meta:
# admin中显示表名
verbose_name='用户表'
# admin中表名s去掉
verbose_name_plural = verbose_name

# user表
# id name blog_id
# 1 111 1
# 2 111 1
class Blog(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=64)
site_name = models.CharField(max_length=32)
theme = models.CharField(max_length=64)
def __str__(self):
return self.site_name


class Category(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=64)
# ForeignKey跟OneToOneField的区别?
#OneToOneField unique=True
blog = models.ForeignKey(to='Blog', to_field='nid', null=True)
def __str__(self):
return self.title


class Tag(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=64)
blog = models.ForeignKey(to='Blog', to_field='nid', null=True)
def __str__(self):
return self.title



class Article(models.Model):
nid = models.AutoField(primary_key=True)
# verbose_name='文章标题' 修改admin中表单的文字显示
title = models.CharField(max_length=64,verbose_name='文章标题')
# 摘要,简单描述
desc = models.CharField(max_length=255)
# 大文本TextField()
content = models.TextField()
# 存时间类型,auto_now_add每插入一条数据,时间自动写入当前时间,
# auto_now,这条数据修改的时候,会更新成当前时间
create_time = models.DateTimeField(auto_now_add=True)

# 因为查询多,写入少,所以加这三个字段,以后不需要再连表查询了
commit_num=models.IntegerField(default=0)
up_num=models.IntegerField(default=0)
down_num=models.IntegerField(default=0)

blog = models.ForeignKey(to='Blog', to_field='nid', null=True)

category = models.ForeignKey(to='Category', to_field='nid', null=True)
# through_fields应该怎么写?
# 中介模型,手动创建第三张表,through是通过哪个表跟Tag建立关系,through_fields为了查询用的
tag = models.ManyToManyField(to='Tag', through='ArticleTOTag', through_fields=('article', 'tag'))
# 这样写,会自动创建第三张表
# tag = models.ManyToManyField(to='Tag')
def __str__(self):
return self.title+'----'+self.blog.userinfo.username


# 手动创建第三张表
class ArticleTOTag(models.Model):
nid = models.AutoField(primary_key=True)
article = models.ForeignKey(to='Article', to_field='nid')
tag = models.ForeignKey(to='Tag', to_field='nid')
# article和tag应不应该联合唯一?

# article_id 1
# tag_id 1


class Commit(models.Model):
nid = models.AutoField(primary_key=True)
user = models.ForeignKey(to='UserInfo', to_field='nid')
article = models.ForeignKey(to='Article', to_field='nid')
content = models.CharField(max_length=255)
create_time = models.DateTimeField(auto_now_add=True)
# 这样写是可以的
# parent_id=models.IntegerField()
# 自关联
# parent_id=models.ForeignKey(to='Commit',to_field='nid')
parent = models.ForeignKey(to='self', to_field='nid',null=True,blank=True)


class UpAndDown(models.Model):
nid = models.AutoField(primary_key=True)
user = models.ForeignKey(to='UserInfo', to_field='nid')
article = models.ForeignKey(to='Article', to_field='nid')
is_up = models.BooleanField()

class Meta:
# 写这些,只是为了不写脏数据,联合唯一
unique_together = (('user', 'article'),)

4 myforms.py
from django import forms

from django.forms import widgets
from django.core.exceptions import ValidationError
from blog import models


class RegForm(forms.Form):
username = forms.CharField(max_length=18, min_length=2, label='用户名',
widget=widgets.TextInput(attrs={'class': 'form-control'}),
error_messages={'max_length':'太长了','min_length':'太短了','required':'必须填'}
)
password = forms.CharField(max_length=18, min_length=2, label='密码',
widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
error_messages={'max_length': '太长了', 'min_length': '太短了', 'required': '必须填'}
)
re_password = forms.CharField(max_length=18, min_length=2, label='确认密码',
widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
error_messages={'max_length': '太长了', 'min_length': '太短了', 'required': '必须填'}
)
email = forms.EmailField(label='邮箱',
widget=widgets.TextInput(attrs={'class': 'form-control'}),
error_messages={'invalid':'格式不合法', 'required': '必须填'}
)

# 局部校验钩子函数
def clean_username(self):
name = self.cleaned_data.get('username')
# 去数据库校验
ret = models.UserInfo.objects.filter(username=name).first()
if ret:
raise ValidationError('用户名已存在')
return name
# if ret:
# self.add_error(name, ValidationError('用户名已存在'))
# else:
# return name

# 全局校验钩子函数
def clean(self):
pwd = self.cleaned_data.get('password')
re_pwd = self.cleaned_data.get('re_password')
if pwd and re_pwd:
if pwd == re_pwd:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致')

5 tests.py
from django.test import TestCase

# Create your tests here.
6 views.py
from django.shortcuts import render, HttpResponse, redirect
from PIL import Image, ImageDraw, ImageFont
import os
from BBS import settings
import random
from io import BytesIO
from django.contrib import auth
from django.http import JsonResponse
from blog import myforms
from blog import models

from django.db.models import Count
from django.contrib.auth.decorators import login_required
import json
# random_code=''
from django.db import transaction
from django.db.models import F


# Create your views here.
def login(request):
if request.method == 'GET':
dic = {'name': 'lqz', 'age': 18}
return render(request, 'login.html', locals())
# elif request.method=='POST':
# 判断前台发的请求是不是ajax的请求
elif request.is_ajax():
response = {'user': None, 'msg': None}
name = request.POST.get('name')
pwd = request.POST.get('pwd')
valid_code = request.POST.get('valid_code')
# 判断传过来的验证码是否正确
# 从session中取出来
if valid_code.upper() == request.session.get('valid_code').upper():
user = auth.authenticate(request, username=name, password=pwd)
if user:
# ajax请求,不能再返回render页面,或者redirect,只能返回字符串
# 校验通过,一定要登录
auth.login(request, user)
response['user'] = name
# response['url'] = '/index/'
response['msg'] = '登录成功'
else:
# 用户名密码错误
response['msg'] = '用户名密码错误'
else:
response['msg'] = '验证码错误'
return JsonResponse(response)


from BBS import settings


def get_random_color():
return (
random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
)


def get_valid_code(request):
# 第一种方式
# with open('static/img/lhf.jpg','rb') as f:
# # 图片二进制
# data=f.read()
# return HttpResponse(data)
# 第二种方式:随机生成一张图片
# pip3 install Pillow
# pillow 是一个图形处理的模块,功能很强强大
# 生成一张图片,第一个参数是模式:RGB,第二个参数是图片大小,第三个参数是图片颜色
# img = Image.new('RGB', (320, 35), color=get_random_color())
# # 保存到本地
# with open('valid_code.png', 'wb') as f:
# # 直接用img的save方法,第一个参数是空文件,第二个参数图片格式
# img.save(f, 'png')
# # 打开文件,再返回
# with open('valid_code.png', 'rb') as f:
# data = f.read()
# return HttpResponse(data)
# 第三种方式
# 在内存中生成一个空文件(把它想象成 open('valid_code.png', 'wb') as f:)
# 一个是在硬盘上,一个是在内存中
# img = Image.new('RGB', (320, 35), color=get_random_color())
# f = BytesIO()
# # 把图片保存到f中
# # 放到内存中,存取比较快,而且有自动清理
# img.save(f, 'png')
#
# data = f.getvalue()
# return HttpResponse(data)
# 第四种方式,在图片上写文字
# img = Image.new('RGB', (320, 35), color=get_random_color())
# # 拿到画笔,把图片传入画笔
# img_draw=ImageDraw.Draw(img)
# # 生成一个字体对象,第一个参数是字体文件的路径,第二个参数是字体大小
# font=ImageFont.truetype('static/font/ss.TTF',size=25)
#
# # 第一个参数,xy的坐标,第二个参数:要写的文字,第三个参数:写文字的颜色,第四个参数:字体
# # 不同的字体是不同的ttf文件
# img_draw.text((0,0),'python',get_random_color(),font=font)
#
# f = BytesIO()
# # 把图片保存到f中
# # 放到内存中,存取比较快,而且有自动清理
# img.save(f, 'png')
#
# data = f.getvalue()

img = Image.new('RGB', (320, 35), color=get_random_color())
# 拿到画笔,把图片传入画笔
img_draw = ImageDraw.Draw(img)
# 生成一个字体对象,第一个参数是字体文件的路径,第二个参数是字体大小
font = ImageFont.truetype('static/font/ss.TTF', size=25)

# 第一个参数,xy的坐标,第二个参数:要写的文字,第三个参数:写文字的颜色,第四个参数:字体
# 不同的字体是不同的ttf文件
random_code = ''
# 弄一个循环,循环5次,每次随机写一个(数字,大写,小写字母)
for i in range(5):
char_num = random.randint(0, 9)
# 生成一个97到122的数字,然后用chr转成字符
char_lower = chr(random.randint(97, 122))
char_upper = chr(random.randint(65, 90))
char_str = str(random.choice([char_num, char_lower, char_upper]))
img_draw.text((i * 30 + 20, 0), char_str, get_random_color(), font=font)

random_code += char_str
# 把验证码保存到session中
print(random_code)
request.session['valid_code'] = random_code
'''
1 生成一个随机字符串:ddddsfassda
2 在session表中插入一条数据
3 在cook中写入 :sessionid=ddddsfassda
'''
# width = 320
# height = 35
# for i in range(10):
# x1 = random.randint(0, width)
# x2 = random.randint(0, width)
# y1 = random.randint(0, height)
# y2 = random.randint(0, height)
# # 在图片上画线
# img_draw.line((x1, y1, x2, y2), fill=get_random_color())
#
# for i in range(100):
# # 画点
# img_draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())
# x = random.randint(0, width)
# y = random.randint(0, height)
# # 画弧形
# img_draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color())

f = BytesIO()
# 把图片保存到f中
# 放到内存中,存取比较快,而且有自动清理
img.save(f, 'png')

data = f.getvalue()
return HttpResponse(data)


def register(request):
if request.method == 'GET':
my_form = myforms.RegForm()
return render(request, 'register.html', {'my_form': my_form})
elif request.is_ajax():
response = {'status': 100, 'msg': None}
print(request.POST)
my_form = myforms.RegForm(request.POST)
if my_form.is_valid():
# 存数据,返回正确信息
# 得用create_user,回忆一下为什么
# 定义一个字典,把清理的数据赋给它
dic = my_form.cleaned_data
# 移除掉确认密码字段
dic.pop('re_password')
# 取出上传的文件对象
my_file = request.FILES.get('my_file')

# 如果上传的文件为空,这个字段不传,数据库里存默认值
if my_file:
# 放到字典中
dic['avatar'] = my_file
# 存数据的时候,多肯定不行,少,可以能行(null=True),它是可以的
user = models.UserInfo.objects.create_user(**dic)
'''
models.FileField 有了这个字段,存文件,以及往数据库放文件路径,统统不需要自己做了
只需要把文件对象赋给它就可以了
'''
# user=models.UserInfo.objects.create_user(username=name,password=pwd,avatar=文件对象)
# 看看存没存进去
print(user.username)
# 要跳转的路径
response['url'] = '/login/'
else:
# 返回错误信息
response['status'] = 101
response['msg'] = my_form.errors
return JsonResponse(response)


def check_username(request):
response = {'status': 100, 'msg': None}
name = request.POST.get('name')
print(name)
user = models.UserInfo.objects.filter(username=name).first()
if user:
response['status'] = 101
response['msg'] = '用户名已被占用'
return JsonResponse(response)


def index(request):
article_list = models.Article.objects.all().order_by('-create_time')
return render(request, 'index.html', {'article_list': article_list})


def logout(request):
auth.logout(request)
return redirect('/index/')


# 老的---
# def user_blog(request, username,*args,**kwargs):
# print(username)
# user = models.UserInfo.objects.filter(username=username).first()
# if not user:
# return render(request, 'error.html')
#
#
#
# blog = user.blog
# # 过滤这个人所有的文章(基于对象的反向,按表名小写_set.all())---article_list是queryset
# article_list=blog.article_set.all()
# '''
# 分类的处理
# '''
# # 判断category_id是否有值,如果有值,直接再过滤
# # 先取出category_id
# category_id = kwargs.get('category_id', None)
# if category_id:
# article_list=article_list.filter(category__pk=category_id)
#
# '''
# 标签的处理
# 取出kwargs内的tag_id
# '''
# tag_id = kwargs.get('tag_id', None)
# if tag_id:
# article_list=article_list.filter(tag__pk=tag_id)
# # 查询当前站点下所有的分类,对应的文章数
#
# # 每个的分类,对应的文章数
# # group by谁,就以谁做基表
# # ret=models.Category.objects.all().annotate(coun=Count('article__title')).values('title','coun')
# # filter在前表示where value在前表group by
# # value在后表示取值,fileter在后,表示having
# # 先过滤出当前站点下所有的分类
# # ret=models.Category.objects.all().filter(blog=blog)
# # ret=models.Category.objects.all().filter(blog=blog).values('pk').annotate(coun=Count('article__title')).values('title','coun')
# # 结果跟上面一样
# # ret=models.Category.objects.all().filter(blog=blog).annotate(coun=Count('article__title')).values('title','coun')
# category_num = models.Category.objects.all().filter(blog=blog).annotate(coun=Count('article__title')).values_list(
# 'title', 'coun')
# print(category_num)
# # 查询当前站点下每个标签对应的文章数
# tag_num = models.Tag.objects.all().filter(blog=blog).annotate(coun=Count('article__title')).values_list('title',
# 'coun')
# print(tag_num)
# '''
# from django.db.models.functions import TruncMonth
# Sales.objects
# .annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list
# .values('month') # Group By month
# .annotate(c=Count('id')) # Select the count of the grouping
# .values('month', 'c') # (might be redundant, haven't tested) select month and count
#
# '''
# # 查询当前站点下按年月分类的文章数
# from django.db.models.functions import TruncMonth
# # y_m_num = models.Article.objects.all().filter(blog=blog).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(
# # coun=Count('y_m')).values('y_m', 'coun')
# y_m_num = models.Article.objects.all().filter(blog=blog).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(
# coun=Count('y_m')).values_list('y_m', 'coun')
# print(y_m_num)
# return render(request, 'user_blog.html', locals())

# 新的
def user_blog(request, username, *args, **kwargs):
print(username)
# username=username
user = models.UserInfo.objects.filter(username=username).first()
if not user:
return render(request, 'error.html')

blog = user.blog
article_list = blog.article_set.all()
# 取出condition中的分类/标签/时间
condition = kwargs.get('condition')
# condition可能是category|tag|archive中的一个 还可能是空
# 取出param的值,可能为标签id,分类id,或者是时间
param = kwargs.get('param')
print(condition)
print(param)
if 'tag' == condition:
article_list = article_list.filter(tag__pk=param)
elif 'category' == condition:
article_list = article_list.filter(category__pk=param)
elif 'archive' == condition:
# 2018-11 -->切分--->查询
# [2018,11]
archive_list = param.split('-')
# 过滤:发布年为2018年,月为11月的所有文章
article_list = article_list.filter(create_time__year=archive_list[0], create_time__month=archive_list[1])

#
#
# category_num = models.Category.objects.all().filter(blog=blog).annotate(coun=Count('article__title')).values_list(
# 'title', 'coun','pk')
# print(category_num)
# # 查询当前站点下每个标签对应的文章数
# tag_num = models.Tag.objects.all().filter(blog=blog).annotate(coun=Count('article__title')).values_list('title',
# 'coun','pk')
# print(tag_num)
# '''
# from django.db.models.functions import TruncMonth
# Sales.objects
# .annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list
# .values('month') # Group By month
# .annotate(c=Count('id')) # Select the count of the grouping
# .values('month', 'c') # (might be redundant, haven't tested) select month and count
#
# '''
# # 查询当前站点下按年月分类的文章数
# from django.db.models.functions import TruncMonth
# # y_m_num = models.Article.objects.all().filter(blog=blog).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(
# # coun=Count('y_m')).values('y_m', 'coun')
# y_m_num = models.Article.objects.all().filter(blog=blog).annotate(y_m=TruncMonth('create_time')).values(
# 'y_m').annotate(
# coun=Count('y_m')).values_list('y_m', 'coun')
# print(y_m_num)
return render(request, 'user_blog.html', locals())


def article_detail(request, username, id):
print(username)
username = username
user = models.UserInfo.objects.filter(username=username).first()
if not user:
return render(request, 'error.html')

blog = user.blog

article = models.Article.objects.filter(pk=id).first()
# 取出文章所有评论,从文章,查询评论
# 基于对象的反向查询
# content_list=models.Commit.objects.filter(article=article)

content_list = article.commit_set.all().order_by('pk')

return render(request, 'article_detail.html', locals())


# @login_required(login_url='/login/')
# @login_required
# def diggit(request):
# response={'status':100,'msg':None}
#
#
# if request.user.is_authenticated():
# # 从前端传过来的数据,都转成str类型
# article_id=request.POST.get('article_id')
# is_up=request.POST.get('is_up')
# print(is_up)
# print(type(is_up))
# # 用json转
# # # python中的
# # {'is_up':True}
# # # 转成json
# # {"is_up": "true"}
# is_up=json.loads(is_up)
# print(is_up)
# print(type(is_up))
# # 原子性操作.用事务
# with transaction.atomic():
# models.UpAndDown.objects.create(user=request.user,article_id=article_id,is_up=is_up)
# models.Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1)
# response['msg']='点赞成功'
# else:
# response['msg'] = '请先登录'
# response['status'] = 101
# return JsonResponse(response)


def diggit(request):
response = {'status': 100, 'msg': None}

if request.user.is_authenticated():
# 从前端传过来的数据,都转成str类型
article_id = request.POST.get('article_id')
is_up = request.POST.get('is_up')
is_up = json.loads(is_up)
user = request.user
# 存之前先查询,当前用户对该篇文章是否点过
ret = models.UpAndDown.objects.filter(user_id=user.pk, article_id=article_id).exists()
if ret:
# 当有数据,说明,已经点过赞或者踩了
response['msg'] = '您已经点过了'
response['status'] = 101
else:
# 原子性操作.用事务
with transaction.atomic():
models.UpAndDown.objects.create(user=user, article_id=article_id, is_up=is_up)
# 先取出文章的queryset对象
article = models.Article.objects.filter(pk=article_id)
if is_up:
article.update(up_num=F('up_num') + 1)
response['msg'] = '点赞成功'
else:
article.update(down_num=F('down_num') + 1)
response['msg'] = '反对成功'
else:
response['msg'] = '请先登录'
response['status'] = 102
return JsonResponse(response)


from django.core.mail import send_mail


def commit_content(request):
response = {'status': 100, 'msg': None}
if request.is_ajax():
if request.user.is_authenticated():
# 核心逻辑
user = request.user
article_id = request.POST.get('article_id')
content = request.POST.get('content')
pid = request.POST.get('pid')
with transaction.atomic():
ret = models.Commit.objects.create(user=user, article_id=article_id, content=content, parent_id=pid)
models.Article.objects.filter(pk=article_id).update(commit_num=F('commit_num') + 1)

response['msg'] = '评论成功'
response['content'] = ret.content
# 把datetime类型转成字符串,因为json是无法序列化datetime
response['time'] = ret.create_time.strftime('%Y-%m-%d %X')
response['user_name'] = ret.user.username
if pid:
# 如果是字评论,返回父评论的名字
response['parent_name'] = ret.parent.user.username
# 评论成功,发送邮件
'''
subject:邮件标题
message:邮件内容
from_email:邮件发送者
recipient_list:接收者列表,可以传多个
'''
from BBS import settings
# 会有返回值,邮件发送成功是true
# 拿到文章标题
atricle_name = ret.article.title
# 被当前登录人评论
usre_name = request.user.username
# 这个是一个同步的操作:等邮件发完,才能继续往下走(耗时的操作,应该怎么做?可以开一个线程)
# ret=send_mail('您的%s文章被%s评论了'%(atricle_name,usre_name),'这个人评论了:%s'%(content,),settings.EMAIL_HOST_USER,['616564099@qq.com'] )
from threading import Thread
# 实例化
t1 = Thread(target=send_mail, args=(
'您的%s文章被%s评论了' % (atricle_name, usre_name), '这个人评论了:%s' % (content,), settings.EMAIL_HOST_USER,
['616564099@qq.com']))
t1.start()

else:
response['status'] = 101
response['msg'] = '您没有登录'
else:
response['status'] = 101
response['msg'] = '您请求非法'
return JsonResponse(response)


@login_required(login_url='/login/')
def backend(request):
if request.method == 'GET':
# 查询出当前登录用户的所有文章
blog = request.user.blog
article_list = models.Article.objects.filter(blog=blog)
return render(request, 'back/backend.html', {"article_list": article_list})


from bs4 import BeautifulSoup


@login_required(login_url='/login/')
def add_article(request):
'''
用一个模块:BeautifulSoup4---bs4---做爬虫,解析html页面
-安装
-使用 导入 from bs4 import BeautifulSoup
:param request:
:return:
'''
if request.method == 'GET':
return render(request, 'back/add_article.html', )

elif request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')
# 第一个参数:要解析的html内容,第二个参数:以什么解析器,解析我的页面
# html.parser 是bs4 内置的解析器.也可以lxml,但是需要安装
soup = BeautifulSoup(content, 'html.parser')
# soup=BeautifulSoup(content,'lxml')

# 通过bs4模块,去掉script标签,处理xss攻击
# 查询出所有的标签
tags = soup.find_all()
# tags=soup.find_all('span',attrs={'class':'errors'})
for tag in tags:
# print(tag)
if tag.name == 'script':
# 过滤出是sctipt的标签
# 删除掉script的标签
tag.decompose()
print('-----------------------------')
print(soup)
# 取出html标签中所有文本内容
# print(soup.text)
# 截取文字的前150个,作为摘要
desc = soup.text[0:150]
ret = models.Article.objects.create(title=title, desc=desc, content=str(soup), blog=request.user.blog)
return redirect('/backend/')


# 可以局部禁用csrf
def upload_img(request):
# 传上来的图片,是文件
# 是文件,从哪取?
print(request.FILES)
myfile = request.FILES.get('myfile')

path = os.path.join(settings.BASE_DIR, 'media', 'img')
# 不存在这个文件夹
if not os.path.isdir(path):
os.mkdir(path)
file_path = os.path.join(path, myfile.name)
with open(file_path, 'wb') as f:
for line in myfile:
f.write(line)

# 按照要求来处理
'''
//成功时
{
"error" : 0,
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
'''
dic = {'error': 0, 'url': '/media/img/%s' % myfile.name}
return JsonResponse(dic)


@login_required
def update_head(request):
if request.method=='GET':
return render(request,'update_head.html')
else:
myfile = request.FILES.get('head')
# 可以只删除数据库的地址,不删实际文件
user = request.user
user.avatar = myfile
user.save()

# 如果直接这样更新,不会带avatar那个路径,所以不能用这种方式来更新
# ret=models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=myfile)

return redirect('/index/')


# def update_article(request,pk):
# if request.method=='GET':
# article=models.Article.objects.get(pk=pk)
# return render(request,'back/update_article.html',{'article':article})


def update_article(request,pk):
if request.method=='GET':
return render(request,'back/update_article2.html',{'article_id':pk})


def get_article(request,pk):
article=models.Article.objects.get(pk=pk)

return JsonResponse({'title':article.title,'content':article.content})


三 media(存放图片)
1 avatar(头像)
2 img


四 static(专门存放插件)
1.bootstrap-3.3.7-dist
2.css
(1)egon.css
.head{
height: 60px;
background-color: pink;
margin-bottom: 20px;
}
(2)lqz.css
.head{
height: 60px;
background-color: #2aabd2;
margin-bottom: 20px;
}

(3)mycss.css


#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 125px;
text-align: center;
margin-top: 10px;
}

.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/img/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/img/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;
}

input.author {
background-position: 3px -3px;
}


input.author{
background-image: url(/static/img/icon_form.gif);
background-repeat: no-repeat;
border: 1px solid #ccc;
padding: 4px 4px 4px 30px;
width: 300px;
font-size: 13px;
}

3.font
ss.ttf

4.font-awesome-4.7.0

5.img(存放要用到的图片)

6.kindeditor

7.jquery-3.3.1.js

五 templates
1.back(后台)
(1)add_article.html
{% extends 'back/back_base.html' %}

{% block home %}


<div>
<p>添加文章</p>
<form action="/add_article/" method="post">
{% csrf_token %}

<p>标题</p>
<p><input type="text" name="title" class="form-control"></p>
<p>内容(KindEdit编辑器,不支持拖放/粘贴上传图片)</p>
<p>
<textarea name="content" id="editor_id" cols="30" rows="10">


</textarea>

</p>
<input type="submit" class="btn btn-danger" value="提交">


</form>
</div>



<script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
width: '100%',
height: '500px',
//item 控制要显示的控件
//控制控件不能拖动
resizeType: 0,
//上传图片,uploadJson 指的是上传的路径,也就是咱们的路由
uploadJson:'/upload_img/',
//添加一些额外的参数
extraFileUploadParams:{
'csrfmiddlewaretoken':'{{ csrf_token }}',
'article_id':'1'
},
//修改默认上传文件的名字
filePostName:'myfile'

})

});
</script>
{% endblock %}

(2) back_base.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
{# bootstrap.js 是基于jquery的,所以要用它,必须先导入jquery .并且在它上面导入#}
<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script>
<title>后台管理</title>
<style>
.head {
height: 60px;
background: #3e8f3e;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="head">

<p>后台管理</p>
</div>
<div class="container-fluid">
<div class="row">

<div class="col-md-3">


<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>
</div>


</div>
<div class="col-md-9">
<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 home %}
{% 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>
</div>
</body>
</html>

(3) backend.html
{% extends 'back/back_base.html' %}

{% block home %}
<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 article_list %}
<tr>
<td><a href="">{{ article.title }}</a></td>
<td>{{ article.create_time|date:'Y-m-d H:i:s' }}</td>
<td>{{ article.commit_num }}</td>
<td>{{ article.up_num }}</td>
<td><a href="/update_article/{{ article.pk }}">修改</a></td>
<td><a href="">删除</a></td>
</tr>

{% endfor %}

</tbody>

</table>
{% endblock %}


(4)update_article.html
{% extends 'back/back_base.html' %}

{% block home %}


<div>
<p>修改文章</p>
<form action="/add_article/" method="post">
{% csrf_token %}

<p>标题</p>
<p><input type="text" name="title" class="form-control" value="{{ article.title }}"></p>
<p>内容(KindEdit编辑器,不支持拖放/粘贴上传图片)</p>
<p>
<textarea name="content" id="editor_id" cols="30" rows="10">
{{ article.content }}

</textarea>

</p>
<input type="submit" class="btn btn-danger" value="提交">


</form>
</div>



<script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
width: '100%',
height: '500px',
//item 控制要显示的控件
//控制控件不能拖动
resizeType: 0,
//上传图片,uploadJson 指的是上传的路径,也就是咱们的路由
uploadJson: '/upload_img/',
//添加一些额外的参数
extraFileUploadParams: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'article_id': '1'
},
//修改默认上传文件的名字
filePostName: 'myfile'

})

});
</script>
{% endblock %}
(5)update_article2.html
{% extends 'back/back_base.html' %}

{% block home %}


<div>
<p>修改文章</p>
<form action="/add_article/" method="post">
{% csrf_token %}

<p>标题</p>
<p><input type="text" name="title" class="form-control" id="title" article_id="{{ article_id }}"></p>
<p>内容(KindEdit编辑器,不支持拖放/粘贴上传图片)</p>
<p>
<textarea name="content" id="editor_id" cols="30" rows="10">


</textarea>

</p>
<input type="submit" class="btn btn-danger" value="提交">


</form>
</div>



<script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script>
<script>

KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
width: '100%',
height: '500px',
//item 控制要显示的控件
//控制控件不能拖动
resizeType: 0,
//上传图片,uploadJson 指的是上传的路径,也就是咱们的路由
uploadJson: '/upload_img/',
//添加一些额外的参数
extraFileUploadParams: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'article_id': '1'
},
//修改默认上传文件的名字
filePostName: 'myfile'

})

});

//当页面加载完成以后,发ajax请求,拿回文章数据

//jquery 的页面加载完成

$(function () {
var id = $("#title").attr('article_id')
$.ajax({
url: '/get_article/' + '{{ article_id }}',
type: 'get',
success: function (data) {
console.log(data)
$("#title").val(data.title)
// 设置HTML内容
window.editor.html(data.content);

}

})
})
/*
window.onload = function () {
//拿到我隐藏的id
var id = $("#title").attr('article_id')
$.ajax({
url: '/get_article/' + '{{ article_id }}',
type: 'get',
success: function (data) {
console.log(data)
$("#title").val(data.title)
// 设置HTML内容
window.editor.html(data.content);

}

})

}
*/
</script>
{% endblock %}


2.article_datail.html
{% extends 'base.html' %}



{% block content %}

{#{% csrf_token %}#}

<div>
<p><h4 class="text-center">{{ article.title }}</h4></p>
<div>
{{ article.content|safe }}
</div>
{#点赞#}
<div class="clearfix">
<div id="div_digg">
<div class="diggit action">
<span class="diggnum" id="digg_count">{{ article.up_num }}</span>
</div>
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;"></div>
</div>
</div>

{#评论#}
<div>
<ul class="list-group cotent_ul">
{% for content in content_list %}
<li class="list-group-item">
<p><span>#{{ forloop.counter }}楼</span>
<span>{{ content.create_time|date:'Y-m-d H:i:s' }}</span>
<span><a href="/{{ content.user.username }}">{{ content.user.username }}</a></span>
<span class="pull-right replay" username="{{ content.user.username }}"
content_id="{{ content.pk }}">回复</span>
</p>
{% if content.parent %}
{#content.parent 拿到的是什么? 拿到父评论的对象#}
<p class="well">@{{ content.parent.user.username }}</p>

{% endif %}
{{ content.content }}
</li>
{% endfor %}


</ul>
</div>
<div>
<p>发表评论</p>
<p>
昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="刘清政">
</p>
<p>评论内容:</p>
<p>
<textarea name="" id="content" cols="60" rows="10">


</textarea>
</p>
<button class="btn btn-primary submit">提交</button>


</div>
</div>

<script>
//全局变量
var pid = ''
//评论相关(跟评论的方法)
/*
$('.submit').click(function () {
//评论要提交什么内容
//谁?又不需要传对哪篇文章评论了什么内容

var content = $("#content").val()
$("#content").val("")
$.ajax({
url: '/commit_content/',
type: 'post',
data: {
'article_id': '{{ article.pk }}',
'content': content,
'pid': '',
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (data) {
console.log(data)
//拼 用户名:时间
// 评论内容 这些数据都应该从后台返回
var time=data.time
var content=data.content
var user_name=data.user_name
var ss=`
<li class="list-group-item">
<p>
<span>${ user_name }</span>:
<span>${ time }</span>
</p>
${content}
</li>
`
$(".cotent_ul").append(ss)

}
})


})
*/
//字评论和根评论
$('.submit').click(function () {
var content = $("#content").val()
$("#content").val("")

if (pid) {
//pid有值,才切掉头部
//拿到content要切的起始位置indexOf(),取到指定值的索引值
//取到 \n 索引位置的后一位
var index = content.indexOf('\n') + 1
alert(index)
//slice传一个起始位置,一个结束位置,就可以切出来
content = content.slice(index)
}


$.ajax({
url: '/commit_content/',
type: 'post',
data: {
'article_id': '{{ article.pk }}',
'content': content,
'pid': pid,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (data) {
//console.log(data)
//拼 用户名:时间
// 评论内容 这些数据都应该从后台返回
var time = data.time
var content = data.content
var user_name = data.user_name
var ss =''
if (pid){
//需要清空一下父评论的id
pid=''
var parent_name=data.parent_name
ss=`
<li class="list-group-item">
<p>
<span>${ user_name }</span>
<span>${ time }</span>
</p>
<p class="well">@${parent_name }</p>
${content}
</li>
`
}else {
ss= `
<li class="list-group-item">
<p>
<span>${ user_name }</span>:
<span>${ time }</span>
</p>
${content}
</li>
`
}

$(".cotent_ul").append(ss)

}
})
})
$(".replay").click(function () {

//拿到span标签中username属性对应的值
var username = $(this).attr('username')
//alert(username)
//把拼接的字符串放到content内,并且换行
$('#content').val('@' + username + '\n')
//让光标聚焦到这个控件
$('#content').focus()
//给pid赋值(pid是父评论的id)
pid = $(this).attr('content_id')


})
//点赞相关
$(".action").click(function () {
//判断当前点击的div控件,有没有diggit类
var is_up = $(this).hasClass('diggit')

var obj = $(this).children('span')
//alert(is_up)
$.ajax({
url: '/diggit/',
type: 'post',
//谁对哪篇文章,点赞
//谁,可以不传吗?从后台取
data: {article_id: '{{ article.pk }}', is_up: is_up, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
success: function (data) {
console.log(data)
//在点赞下方显示信息提示
$("#digg_tips").html(data.msg)
if (data.status == 100) {
//如果返回成功,点赞数或点踩数加1(第一种方案)
/*
if(is_up){
//这个值是字符串
var count= $("#digg_count").text()
//把count转成int类型
//这两种方式都可以
//$("#digg_count").text(parseInt(count)+1)
$("#digg_count").text(Number(count)+1)
}else{
var count= $("#bury_count").text()
$("#bury_count").text(Number(count)+1)
}
*/
//第二种方案
//ojb是当前点击div中的span标签,先取出span中的数字,+1,然后放到span中
obj.text(Number(obj.text()) + 1)

}


//过三秒清除提示
setTimeout(function () {
$("#digg_tips").html("")
}, 3000)

}
})

})
/*
$(".diggit").click(function () {


})

function test() {

$.ajax({
url: '/diggit/',
type: 'post',
//谁对哪篇文章,点赞
//谁,可以不传吗?从后台取
data: {article_id: '{{ article.pk }}', is_up: true, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
success: function (data) {
console.log(data)

}
})

}
*/


</script>


{% endblock %}

3.base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ blog.userinfo.username }}-的个人博客</title>
{% block mytitle %}
{% endblock %}
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/{{ blog.theme }}">
<link rel="stylesheet" href="/static/css/mycss.css">
{% block mycss %}

{% endblock %}
<script src="/static/jquery-3.3.1.js"></script>

<style>
* {
margin: 0;
padding: 0;
}

</style>
</head>
<body>
<div class="head">

<p>{{ blog.title }}</p>
</div>
<div class="container-fluid">
<div class="row">

<div class="col-md-3">
{% load my_tag %}
{% classify username %}

</div>
<div class="col-md-9">
{% block content %}

{% endblock %}

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

4.classify.html
<div>
<div class="panel panel-primary">
<div class="panel-heading">我的标签</div>
<div class="panel-body">
{% for foo in tag_num %}
{# <p><a href="tag/{{ foo.2 }}">{{ foo.0 }}({{ foo.1 }})</a></p>#}
<p><a href="/{{ username }}/tag/{{ foo.2 }}">{{ foo.0 }}({{ foo.1 }})</a></p>
{% endfor %}
</div>
</div>

<div class="panel panel-primary">
<div class="panel-heading">我的分类</div>
<div class="panel-body">
{% for foo in category_num %}
<p><a href="/{{ username }}/category/{{ foo.2 }}">{{ foo.0 }}({{ foo.1 }})</a></p>
{% endfor %}

</div>
</div>

<div class="panel panel-primary">
<div class="panel-heading">随笔档案</div>
<div class="panel-body">
{% for foo in y_m_num %}
<p><a href="/{{ username }}/archive/{{ foo.0|date:"Y-m" }}">{{ foo.0|date:"Y-m" }}({{ foo.1 }})</a></p>
{% endfor %}

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

5.error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404错误</title>
</head>
<body>

<a href="http://www.cnblogs.com/"><img src="//static.cnblogs.com/images/logo_small.gif" alt="cnblogs"></a>

<p class="d">请确认您输入的网址是否正确,如果问题持续存在,请发邮件至contact@cnblogs.com与我们联系。</p>
<p><b>404.</b> 抱歉! 您访问的资源不存在!</p><p><a href="http://www.cnblogs.com/">返回网站首页</a></p>


</body>
</html>

6.index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script>
<link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.css">
<title>博客园</title>
<style>

.article_bottom span{
margin-right: 5px;
}
</style>
</head>
<body>
<div class="head">

<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<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="#">修改密码</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>

<div class="container-fluid">
<div class="row">

<div class="col-md-2">
<div class="panel panel-danger">
<div class="panel-heading">重金求子</div>
<div class="panel-body">
<p>请联系:199999999</p>
年入60w
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">爱心接力</div>
<div class="panel-body">
<a href="http://www.baidu.com">请点击</a>
</div>
</div>


</div>

<div class="col-md-7">
{% for article in article_list %}
<div>
<h4><a href="/{{ article.blog.userinfo.username }}/article/{{ article.pk }}">{{ article.title }}</a></h4>
<div class="media">
<div class="media-left">
<a href="#">
{# avatar/lhf.jpg ----/media/#}
<img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" height="70" width="70">
</a>
</div>
<div class="media-body">

{{ article.desc }}
</div>
</div>
<div style="margin-top: 10px " class="article_bottom">
<span><a href="/{{ article.blog.userinfo.username }}">{{ article.blog.userinfo.username }}</a></span>
<span>发布于 {{ article.create_time|date:'Y-m-d H:i:s' }}</span>
{#反向查询,一对多,按表名小写_set#}
{# <span class="glyphicon glyphicon-comment"><a href="">评论({{ article.commit_num }})</a></span>#}
<span><i class="fa fa-comment" aria-hidden="true"><a href="">评论({{ article.commit_num }})</a></i></span>

{# <span class="glyphicon glyphicon-comment"><a href="">评论({{ article.commit_num }})</a></span>#}
<span class="glyphicon glyphicon-thumbs-up"><a href="">点赞({{ article.up_num }})</a></span>
</div>
<hr>
</div>
{% endfor %}


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


<div class="panel panel-danger">
<div class="panel-heading">重金求子</div>
<div class="panel-body">
<p>请联系:199999999</p>
年入60w
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">爱心接力</div>
<div class="panel-body">
<a href="http://www.baidu.com">请点击</a>
</div>
</div>


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

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

7 login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1.js"></script>
<title>登录</title>
<style>
.error{
margin-left: 20px;
color: red;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1>登录</h1>
{# label for="name" 如果指定某个id,这样点击这个label,焦点会到对应的控件上 #}
<form>
{% csrf_token %}
<div class="form-group">
<label for="name">用户名</label>
<input type="text" id="name" class="form-control">
</div>
<div class="form-group">
<label for="pwd">密码</label>
<input type="password" id="pwd" class="form-control">
</div>
<div class="form-group">
<label for="valid_code">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="valid_code" class="form-control">
</div>

<img height="35" width="320" src="/get_valid_code/" alt="" id="img_code">
</div>

</div>

<input type="button" value="登录" class="btn btn-primary " id="btn"><span class="error"></span>
</form>

</div>

</div>

</div>
</body>
<script>
$("#img_code").click(function () {
//在路径后面加一个问号
//$("#img_code")[0].src+=?
// alert(11)
$("#img_code")[0].src += '?'
})
$("#btn").click(function () {
{#尽量少用这种#}
//var obj={{ dic }}
//console.log(typeof obj)
$.ajax({
url: '/login/',
type: 'post',
//一定要记住csrf
data: {
'name': $("#name").val(),
'pwd': $("#pwd").val(),
'valid_code': $("#valid_code").val(),
//属性选择器
'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()
//'csrfmiddlewaretoken': '{{csrf_token}}',
},
success:function (data) {
console.log(data)
if(data.user){
location.href='/index/'
}else{
$(".error").html(data.msg)
}

}

})
})

</script>
</html>

8 register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1.js"></script>
<title>注册</title>
<style>
{# 把文件空间隐藏#}
#my_file{
display: none;
}
</style>

{# <script>#}
{# //等文档加载完毕之后,再进行操作#}
{# window.onload = function () {#}
{# //$("#id_name").val('lqz')#}
{# }#}
{##}
{# </script>#}
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1>注册</h1>
<form id="form">
{% csrf_token %}

{% for foo in my_form %}
<div class="form-group">
<label for="{{ foo.auto_id }}">{{ foo.label }}</label>
{{ foo }} <span class="error pull-right" style="color: red">{{ foo.errors.0 }}</span>
</div>
{% endfor %}
{# 加一个上传文件的控件#}
<div class="form-group">
<label for="my_file">头像
<img src="/static/img/default.png" id="img_file" alt="" width="80" height="80"
style="margin-left: 10px">
</label>

<input accept="image/*" type="file" id="my_file">
</div>


<input type="button" value="注册" class="btn btn-primary " id="btn"><span class="error"></span>
</form>

</div>

</div>

</div>
</body>
<script>
//这个控件值发生变化的事件
$("#my_file").change(function () {
//alert(111)
//先取出文件(图片)
var file_obj = $("#my_file")[0].files[0];
//通过文件阅读器,把图片放到img标签上
//生成一个文件阅读器对象
var filereader = new FileReader()
//把图片对象,读到filereader对象中
filereader.readAsDataURL(file_obj)
//filereader.result 这是filereader对象的值
//把文件对象渲染到img标签上(这样不行.需要加载完成才能操作)
{#$("#img_file").attr('src', filereader.result)#}
{#alert(1)#}
//等加载完成,在操作
filereader.onload=function () {
$("#img_file").attr('src', filereader.result)
}


})

$("#btn").click(function () {
//因为要上传文件,生成formdata对象
var formdata=new FormData()
/*第一种方式
formdata.append('name',$("#id_name").val())
formdata.append('pwd',$("#id_pwd").val())
formdata.append('re_pwd',$("#id_re_pwd").val())
formdata.append('email',$("#id_email").val())
formdata.append('csrfmiddlewaretoken',$('[name="csrfmiddlewaretoken"]').val())
//把文件放到formdata中
formdata.append('my_file',$('#my_file')[0].files[0])
*/
//$("#form").serializeArray()把form表单打包,转成对象(列表套字典)
var arr=$("#form").serializeArray()
//第二种方式
//jquery 的循环,传参数:第一个参数是要循环的对象,第二个参数是一个匿名函数
$.each(arr,function (key,obj) {
//obj对应的是:{name: "name", value: "sfdae"}
console.log(key)
console.log(obj)
formdata.append(obj.name,obj.value)
})
//把文件放到formdata中
formdata.append('my_file',$('#my_file')[0].files[0])
//console.log(formdata)
console.log(arr)

$.ajax({
url:'/register/',
type:'post',
//写什么来?
processData:false,
//默认urlencoded,还可以指定 application/json
contentType:false,
data:formdata,
//dataType:'json',
success:function (data) {
//console.log(data)

if(data.status==100){
//location.href='/login/'
location.href=data.url
}else{
//在之前清除
$(".form-group").removeClass('has-error')
$(".error").html("")
$.each(data.msg,function (key,value) {
console.log(key,value)

// $('[name='+key+']').
//根据key,拼上id,通过id取出控件
//原来取值
//$("#id_username").next().html('sb')
//取到下一个控件
/*
$("#id_"+key).next().html(value[0])
取到父控件
$("#id_"+key).parent().addClass('has-error')
*/
//方式二
//处理两次密码不一致的情况
if(key=='__all__'){
$("#id_re_password").next().html(value[0])
}
$("#id_"+key).next().html(value[0]).parent().addClass('has-error')

})
/*
setTimeout(function () {
//清除掉父div的has-error
//清除掉错误信息(span中的内容)
$(".form-group").removeClass('has-error')
$(".error").html("")

},3000)
*/
}

}
})

})


//name,发ajax的请求校验用户是否存
/*
$("#id_username").change(function () {
alert('change')
})
*/
$("#id_username").blur(function () {
$.ajax({
url:'/check_username/',
type:'post',
//name加引号不加引号都可以
data:{name:$(this).val(),'csrfmiddlewaretoken':'{{ csrf_token }}'},
success:function (data) {
console.log(data)
if(data.status==101){
$('#id_username').next().html(data.msg).parent().addClass('has-error')
}
}

})
})


</script>

{# <script>#}
{# //等文档加载完毕之后,再进行操作#}
{# $("#id_name").val('lqz')#}
{# </script>#}
</html>

9 update)head.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="head">
<input type="submit" value="提交">

</form>
</body>
</html>

10 user_blog.html
{% extends 'base.html' %}
{% block content %}


{#通过站点查询所有文章,反向查询,按表名小写_set.all#}
{% for article in article_list %}
{#lqz/article/1#}
<h4><a href="/{{ username }}/article/{{ article.pk }}">{{ article.title }}</a></h4>

<div>

{{ article.desc }}
</div>

<div class="clearfix">
<div style="margin-top: 10px " class="article_bottom small pull-right">


<span>posted @ {{ article.create_time|date:'Y-m-d H:i:s' }}</span>
{#反向查询,一对多,按表名小写_set#}
{# <span class="glyphicon glyphicon-comment"><a href="">评论({{ article.commit_num }})</a></span>#}

<span>{{ article.blog.userinfo.username }}</span>
<span><i class="fa fa-comment" aria-hidden="true"><a
href="">评论({{ article.commit_num }})</a></i></span>

{# <span class="glyphicon glyphicon-comment"><a href="">评论({{ article.commit_num }})</a></span>#}
<span class="glyphicon glyphicon-thumbs-up"><a href="">点赞({{ article.up_num }})</a></span>
<span><a href="">编辑</a></span>
</div>
</div>
<hr>
{% endfor %}

{% endblock %}


六 manage.py
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BBS.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv)

七 test.py

import smtplib
from email.mime.text import MIMEText
# from email.mime.image import MIMEImage
# from email.mime.base import MIMEBase
# from email.header import Header
msg_from = '306334678@qq.com' # 发送方邮箱
passwd = '****' # 填入发送方邮箱的授权码(填入自己的授权码,相当于邮箱密码)
msg_to = ['****@qq.com','**@163.com','*****@163.com'] # 收件人邮箱
# msg_to = '616564099@qq.com' # 收件人邮箱

subject = "邮件标题" # 主题
content = "邮件内容,我是邮件内容,哈哈哈"
# 生成一个MIMEText对象(还有一些其它参数)
# _text_:邮件内容
msg = MIMEText(content)
# 放入邮件主题
msg['Subject'] = subject
# 也可以这样传参
# msg['Subject'] = Header(subject, 'utf-8')
# 放入发件人
msg['From'] = msg_from
# 放入收件人
msg['To'] = '616564099@qq.com'
# msg['To'] = '发给你的邮件啊'
try:
# 通过ssl方式发送,服务器地址,端口
s = smtplib.SMTP_SSL("smtp.qq.com", 465)
# 登录到邮箱
s.login(msg_from, passwd)
# 发送邮件:发送方,收件方,要发送的消息
s.sendmail(msg_from, msg_to, msg.as_string())
print('成功')
except s.SMTPException as e:
print(e)
finally:
s.quit()
 
 
 
 




 
 





 
 
 
 




 

posted @ 2018-12-10 13:45  不沉之月  阅读(635)  评论(0编辑  收藏  举报