0%

Django搭建博客

本文持续更新中,项目地址

环境

  • 本地开发环境Windows
    • Python 3.8.3
    • Django 3.0.8
    • mysqlclient 2.0.1
  • 数据库(CentOS Docker)
    • MySQL 5.0.7

开始项目

新建项目与全局配置

  1. 新建项目

    1
    $ django-admin startproject blog
  2. 新建app

    因为使用hAdmin后台管理模板,所以后台管理app命名为hadmin

    1
    $ python manage.py startapp hadmin
  3. 新建静态目录

    在项目根目录下新建一个文件夹static

  4. 配置settings.py

    • 注册app
    • 更改数据库并配置
    • 更改时区与语言
    • 添加静态路径映射
    • 设置Cookie有效期
    settings.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    ...
    # Application definition

    INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    + 'hadmin',
    ]
    ...
    # Database
    # https://docs.djangoproject.com/en/3.0/ref/settings/#databases

    DATABASES = {
    'default': {
    - 'ENGINE': 'django.db.backends.sqlite3',
    - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    + 'ENGINE': 'django.db.backends.mysql',
    + 'NAME': '数据库名',
    + 'USER': '用户名',
    + 'PASSWORD': '密码',
    + 'HOST': '地址',
    + 'PORT': 端口,
    }
    }
    ...
    # Internationalization
    # https://docs.djangoproject.com/en/3.0/topics/i18n/

    -LANGUAGE_CODE = 'en-us'
    +LANGUAGE_CODE = 'zh-hans'

    -TIME_ZONE = 'UTC'
    +TIME_ZONE = 'Asia/Shanghai'

    USE_I18N = True

    USE_L10N = True

    -USE_TZ = True
    +USE_TZ = False

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

    STATIC_URL = '/static/'

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

    +SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

    +SESSION_EXPIRE_AT_BROWSER_CLOSE = True

    别忘了新建一个数据库

    1
    mysql> CREATE DATABASE `blog` CHARACTER SET utf8;
  5. 路由设置

    这里不需要使用Django自带的后台管理模板

    • 在hadmin下新建一个urls.py

      hadmin/urls.py
      1
      2
      3
      4
      5
      from django.conf.urls import url
      from . import views

      urlpatterns = [
      ]
    • 配置全局路由

      blog/urls.py
      1
      2
      3
      4
      5
      6
      7
      8
      -from django.contrib import admin
      -from django.urls import path
      +from django.urls import path, include

      urlpatterns = [
      - path('admin/', admin.site.urls),
      + path('admin/', include('hadmin.urls')),
      ]

登陆逻辑

Model

Python源码

hadmin/models.py
1
2
3
class Admin(models.Model):
name = models.CharField(max_length=100)
password = models.CharField(max_length=100)

模型迁移

1
2
3
4
5
6
7
8
9
10
$ python manage.py makemigrations hadmin
Migrations for 'hadmin':
hadmin\migrations\0001_initial.py
- Create model Admin

$ python manage.py migrate hadmin
Operations to perform:
Apply all migrations: hadmin
Running migrations:
Applying hadmin.0001_initial... OK

插入数据

1
mysql> INSERT INTO `hadmin_admin`( `name`, `password` ) VALUES( 'admin', 'admin' );

View

hadmin/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from django.shortcuts import render

# Create your views here.
from django.views.decorators.csrf import csrf_exempt

from hadmin import models


def login(request):
return render(request, 'hadmin/login.html')


@csrf_exempt
def doLogin(request):
m = models.Admin.objects.get(name=request.POST['name'])
if m.password == request.POST['password']:
request.session['id'] = m.id
request.session['name'] = m.name
return HttpResponse("True")
else:
return HttpResponse("Your username and password didn't match.")


def index(request):
return render(request, "hadmin/index.html")
hadmin/urls.py
1
2
3
4
5
6
7
8
from django.conf.urls import url
from . import views

urlpatterns = [
url('index/', views.index),
url(r'^login/$', views.login),
url(r'^doLogin/$', views.doLogin),
]

Template

相关静态资源的匹配与放置不在赘述

重点是利用jQuery实现登陆

templates/hadmin/login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
$("#entry_btn").click(function () {
var name = $("#name").val();
var password = $("#password").val();

$.post("/admin/doLogin/", {"name": name, "password": password}, function (data) {
if (data == "True") {
alert('True');
window.location.href = "/admin/index/"
} else {
alert(data)
}

});
});
</script>

过滤与重定向

过滤

在hadmin下新建一个中间件loginMiddlerware.py

loginMiddlerware.py
1
2
3
4
5
6
7
8
9
class LoginCheck(MiddlewareMixin):
def process_request(self, request):
path = request.path_info.lstrip('/')
if path.find('admin') != -1: # 如果admin开头的url
urls = {'admin/', 'admin/login/', 'admin/doLogin/'} # 可放行url
if path not in urls:
ID = request.session.get('id')
if not ID: # 如果session中没有id,则返回登录页面
return HttpResponseRedirect('/admin/login')

注册中间件

settings.py
1
2
3
4
5
6
7
8
9
10
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',
+ 'hadmin.loginMiddleware.LoginCheck',
]

重定向

将/admin/重定向至/admin/index/

hadmin/views.py
1
2
def redirectToLogin(request):
return HttpResponseRedirect('/admin/index/')

路由配置省略

后台页面

引用静态资源

1
2
3
4
若要使用版本号,请使用如下方式
<script src="{% static 'hadmin/js/hAdmin.js' %}?v=4.1.0"></script>
而非
<script src="{% static 'hadmin/js/hAdmin.js?v=4.1.0' %}"></script>

登出逻辑

views.py
1
2
3
4
5
6
7
8
9
def logout(request):
request.session.clear() # 清空的是值
return HttpResponseRedirect('/admin/login')
或者把Cookie一起删掉
def logout(request):
request.session.clear() # 清空的是值
response = redirect('/admin/login')
response.delete_cookie('sessionid')
return response

博文管理

添加博文

model

hadmin/models.py
1
2
3
4
5
6
class Article(models.Model):
title = models.CharField(max_length=100, default="untitled")
articleType = models.IntegerField() # 0为markdown,1为富文本
content = models.TextField()
createTime = models.DateTimeField(auto_now_add=True)
updateTime = models.DateTimeField(auto_now=True)
1
2
3
4
5
6
7
8
9
10
$ python manage.py makemigrations hadmin
Migrations for 'hadmin':
hadmin\migrations\0002_article.py
- Create model Article

$ python manage.py migrate hadmin
Operations to perform:
Apply all migrations: hadmin
Running migrations:
Applying hadmin.0002_article... OK

view

hadmin/views.py
1
2
3
4
5
@csrf_exempt
def uploadArticle(request):
models.Article.objects.create(title=request.POST['title'], articleType=request.POST['articleType'],
content=request.POST['content'])
return HttpResponse('ok')

template

new_markdowm.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{% extends 'hadmin/index.html' %}
{% block contentBody %}

{% load static %}
...
<script>
$("#submit").click(function () {
var title = $("#title").val();
var content = $("[name=editor-markdown-doc]").val();
var articleType = 0;
$.post("/admin/uploadArticle/", {"title": title, "content": content, "articleType": articleType}, function (data) {
alert(data);
});
});
</script>
...
{% endblock %}

由于使用的markdown渲染不相同,这里只保留关键部分

博文列表

1
2
3
4
5
6
7
8
9
10
{# 后端分页以后再说 #}
{% for item in items %}
<tr class="gradeX">
<td>{{ item.title }}</td>
<td>{{ item.createTime }}</td>
<td>{{ item.updateTime }}</td>
<td><a href="/admin/updateMarkdown/{{ item.id }}/">编辑文章</a></td>
<td ><a href="/admin/deleteMarkdown/{{ item.id }}/">删除文章</a></td>
</tr>
{% endfor %}
1
2
def markdownList(request):
return render(request, 'hadmin/markdown_list.html', {'items': models.Article.objects.all()})

删除文章

1
2
3
def deleteMarkdown(request, ID):
models.Article.objects.filter(id=ID).delete()
return render(request, 'hadmin/markdown_list.html', {'items': models.Article.objects.all()})

编辑文章

views.py
1
2
3
4
5
6
7
8
9
10
11
12
def updateMarkdown(request, ID):
obj = models.Article.objects.get(id=ID)
content = obj.content.replace('"', r'\"').replace('\n', r'\n') # 因为前端用的是双引号把字符包裹起来,所以只用转义这个
return render(request, 'hadmin/update_markdown.html', {'item': obj, 'content': content})

@csrf_exempt
def doUploadArticle(request):
item = models.Article.objects.get(id=request.POST['id'])
item.title = request.POST['title']
item.content = request.POST['content']
item.save()
return HttpResponse('ok')

用这种方法可以自动添加更新时间。

1
2
$("#title").val("{{ item.title }}");
$("[name=editor-markdown-doc]").val("{% autoescape off %}{{ content }}{% endautoescape %}");

这里有个坑,Django会自动把特殊字符比如<转义成&#lt;之类的,所以我们要用标签把它忽略转义。

图片上传

参考文档

settings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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',
+ 'django.template.context_processors.media',
],
},
},
]
...
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

与static一样,然后新建文件夹。

urls.py
1
2
3
4
5
6
7
8
9
from django.urls import path, include, re_path
from django.views.static import serve

from blog import settings

urlpatterns = [
re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
path('admin/', include('hadmin.urls')),
]
views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@csrf_exempt
def uploadImg(request):
"""
图片上传
:param request:
:return:
"""
img = request.FILES['editormd-image-file']
img_uuid = str(uuid.uuid1())
img_end = img.name.split('.')[-1]
filename = '%s/upload/%s' % (settings.MEDIA_ROOT, img_uuid + '.' + img_end)
with open(filename, 'wb') as pic:
for c in img.chunks():
pic.write(c)
return JsonResponse({'success': 1, 'message': "上传成功", 'url': '/media/upload/' + img_uuid + '.' + img_end})

UUID保证图片名字唯一。

如果遇到Xframoption错误,那就把中间件的

'django.middleware.clickjacking.XFrameOptionsMiddleware',注释掉即可

-----------看到底线啦 感谢您的阅读-------------