Django的media配置与富文本编辑器使用的实例

效果预览

文章列表

添加文章

编辑文章|文章详情|删除文章

项目的基本文件

项目的Model

from django.db import models
# 导入富文本编辑器相关的模块
from ckeditor_uploader.fields import RichTextUploadingField

class Category(models.Model):
    name = models.CharField(max_length=12,verbose_name='分类名称')

    def __str__(self):
        return self.name


class ArticleDetail(models.Model):
    # 使用富文本编辑器
    content = RichTextUploadingField(verbose_name='文章详情')


class Article(models.Model):
    title = models.CharField(verbose_name='文章标题',max_length=56)
    summary = models.CharField(verbose_name='文章摘要',max_length=256)
    create_at = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
    # 相对路径
    img = models.ImageField(upload_to='img/article/',verbose_name='显示图片',blank=True,default='img/article/default.jpg')

    category = models.ForeignKey(to=Category,blank=True,null=True,verbose_name='文章分类')
    # 如果设置成null=True,会出现“文章详情有数据但是其中的文章的这个字段为空”的情况
    detail = models.OneToOneField(to=ArticleDetail,verbose_name='文章详情',null=True,blank=True)

项目的url配置

项目的url做了“伪html页面配置”~~注意正则匹配的时候把点号用转义一下~

from django.conf.urls import url

from backend.views import article

urlpatterns = [

    url(r'^article_list.html/$', article.article_list,name='article_list'),
    url(r'article_add.html/$',article.article_add,name='article_add'),
    url(r'article_edit.html/(?P<pk>d+)/$',article.article_edit,name='article_edit'),
    url(r'article_del.html/(?P<pk>d+)/$',article.article_del,name='article_del'),
    url(r'article_detail.html/(?P<pk>d+)/$',article.article_detail,name='article_detail'),

]
项目的url

项目的视图

# -*- coding:utf-8 -*-
import os
import re
from django.shortcuts import render,redirect
from django.conf import settings

from repository import models
from backend import forms


def article_list(request):
    if request.method == 'GET':
        all_articles = models.Article.objects.all()
        return render(request,'article/article_list.html',locals())


# 添加文章有一个异常情况:
# 文章详情的content中不输入数据也能通过校验!———— 所以必须判断一下content中是否有输入!
def article_add(request):
    # 一个页面加两个form校验表单:文章与文章详情
    form_obj = forms.ArticleForm()
    detail_form = forms.ArticleDetailForm()
    title = '新增文章'
    if request.method == 'GET':
        return render(request,'article/article_form.html',{'title':title,'form_obj':form_obj,'detail_form':detail_form})
    elif request.method == 'POST':
        # 这里有个异常情况:文章详情的content中不输入数据也能通过校验!———— 所以必须判断一下content中是否有输入!
        if request.POST.get('content'):
            # 文章详情
            detail_form = forms.ArticleDetailForm(request.POST)
            if detail_form.is_valid():
                detail_form.save()
            # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
            # QueryDict需要copy()一下才能修改!
            qd = request.POST.copy()
            # 注意!键是Article表做外键关联的那个属性值;值是对应的ArticleDetail中的对应的对象的pk值~先用instance取出文章详情的对象
            qd['detail'] = detail_form.instance.pk
            print(qd)
            # 文章表中有数据和文件,文件需要用request.FILES获取
            # 注意~这里的数据应该是qd了!!!
            form_obj = forms.ArticleForm(data=qd,files=request.FILES)
            if form_obj.is_valid():
                # print(111111111111111111111111)
                form_obj.save()
                return redirect('backend:article_list')

            # 这种情况是:文章详情添加成功但是文章添加失败了~需要把文章详情添加的数据删除了
            # 文章与文章详情的数据保持一致
            elif detail_form.is_valid() and detail_form.instance:
                detail_form.instance.delete()
                # print(222222222222222222)

            return render(request, 'article/article_form.html',
                              {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})
        else:
            return render(request, 'article/article_form.html',
                          {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})



# 编辑文章——如果用户上传了新的图片,应该把之前的图片删掉
def article_edit(request,pk):
    # 一个页面显示两个表单
    # 文章对象
    article_obj = models.Article.objects.filter(pk=pk).first()
    # 提前将原文章中的图片对象取出来
    img_obj = article_obj.img

    form_obj = forms.ArticleForm(instance=article_obj)
    # 基于对象的跨表查询
    detail_form = forms.ArticleDetailForm(instance=article_obj.detail)
    title = '编辑文章'
    if request.method == 'GET':
        return render(request,'article/article_form.html',{'title':title,'form_obj':form_obj,'detail_form':detail_form})

    elif request.method == 'POST':
        # 文章详情
        detail_form =forms.ArticleDetailForm(data=request.POST,instance=article_obj.detail)
        if detail_form.is_valid():
            detail_form.save()
        # 文章的处理
        # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
        # QueryDict需要copy()一下才能修改!
        qd = request.POST.copy()
        qd['detail'] =detail_form.instance.pk
        # 注意:data应该是qd了!!!~~还得加上article_obj~否则成了添加了!
        form_obj = forms.ArticleForm(data=qd,files=request.FILES,instance=article_obj)
        if form_obj.is_valid():
            # 判断一下新传入的图片对象跟之前的是否相等
            # 如果没有传入图片就继续
            if not form_obj.cleaned_data.get('img'):
                pass
            if form_obj.cleaned_data.get('img') != img_obj:
                try:
                    # 将之前的图片删除了
                    os.remove(img_obj.path)
                except Exception as e:
                    pass
            form_obj.save()
            return redirect('backend:article_list')
        return render(request, 'article/article_form.html',
                      {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})


# 删除文章~~
# 把Article中的图片以及文章详情的content中的图片同时也删掉~~
def article_del(request,pk):
    # 这里注意:一对一的关系属性加在了Article类中
    # “级联删除”的功能应该是:删除ArticleDetail表中的数据后Article表中的数据跟着删除
    # 但是删除Article中的数据的话ArticleDetail中对应的数据不会跟着删除
    # 先找到Article对象,注意删除的是ArticleDetail的对象!
    article_obj = models.Article.objects.filter(pk=pk).first()
    # 基于对象的跨表查询
    detail_obj = article_obj.detail
    # 找到图片对象
    img_file_obj = article_obj.img
    # path方法找到文件的绝对路径
    img_file_path = img_file_obj.path

    content = detail_obj.content
    print(content)
    # 如果有图片~把文章中的图片也删除了
    if 'img' in content:
        # 异常情况是:服务器中没有图片文件而数据库中有记录(人为误删导致)
        try:
            # 匹配多个格式的图片文件~~~注意取消分组优先!!!
            result = re.findall(r'src=".+.(?:jpg|png|jpeg|PNG|JPG|JPEG)"',content)
            print(result) #['src="/media/ckeditor/2019/07/23/itachi3.PNG"', 'src="/media/ckeditor/2019/07/23/default.jpg"']
            for src in result:
                # 去掉前面的前缀(前面那个斜杠也去掉!)以及后面的那个引号
                path = src[6:-1]
                file_img_path = os.path.join(settings.BASE_DIR,path)
                # # 删除这张图片
                os.remove(file_img_path)
        except Exception as e:
            pass

    # 删除Article中的图片文件
    os.remove(img_file_path)
    # 删除文章详情————有级联删除~文章表对应的记录也删了(字段是在文章表中定义的)
    detail_obj.delete()
    return redirect('backend:article_list')



# 文章表详情~前端模板中用safe渲染!
def article_detail(request,pk):
    article_obj = models.Article.objects.filter(pk=pk).first()

    title = article_obj.title
    content = article_obj.detail.content
    return render(request,'article/article_detail.html',{'content':content,'title':title})
项目的视图

项目的模板文件

1、文章列表展示的页面:

{% extends 'layout.html' %}

{# {% get_media_prefix %}方法需要先load static!!!#}
{% load static %}

{% block content %}

    <h2 class="text-danger">文章列表</h2>

    <a href="{% url 'backend:article_add' %}" class="btn btn-success">添加文章</a>
    <table class="table table-condensed table-bordered" style="margin-top: 22px">
        <thead>
        <tr>
            <th>序号</th>
            <th>图像</th>
            <th>标题</th>
            <th>分类</th>
            <th>操作</th>
        </tr>
        </thead>

        <tbody>
        {% for article in all_articles %}
            <tr>
                <td>{{ forloop.counter }}</td>

                {# 不要用 {{ article.img/url }} 如果没有传图片的话会报错! #}
                <td><img src="{% get_media_prefix %}{{ article.img }}" alt="" width="52px" height="52px"></td>
                <td>{{ article.title }}</td>
                <td>{{ article.category }}</td>
                <td class="tpl-table-black-operation">
                    <a href="{% url 'backend:article_edit' article.pk %}">
                        <i class="am-icon-pencil"></i> 编辑
                    </a>
                    <a href="{% url 'backend:article_detail' article.pk %}"
                       class="tpl-table-black-operation">
                        <i class="am-icon-random"></i> 文章详情
                    </a>
                    <a href="{% url 'backend:article_del' article.pk %}"
                       class="tpl-table-black-operation-del">
                        <i class="am-icon-trash"></i> 删除
                    </a>
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>


{% endblock content %}
article_list.html

2、用于校验的页面:

一个页面放了2个校验的表单,一个是文章的,一个是文章详情的

{% extends 'layout.html' %}

{% load staticfiles %}

{% block content %}
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">{{ title }}</h3>
        </div>

        <div class="panel-body">
            <form action="" class="form-horizontal" method="post" enctype="multipart/form-data" novalidate>
                {% csrf_token %}

                {# 一个页面显示2个校验表单 #}

                {# 添加文章的表单 #}
                {% for field in form_obj %}
                    {# 注意这里不显示Article表的detail属性 #}
                    {# field.name 标签对应的name属性的值 就是数据库中的字段的值 #}
                    {% if field.name != 'detail' %}
                        <div class="form-group  {% if field.errors %}has-error{% endif %}">
                            <label   {% if not field.field.required %} style="color: #777777" {% endif %}
                                                                       for="{{ field.id_for_label }}"
                                                                       class="col-sm-2 control-label">{{ field.label }}</label>
                            <div class="col-sm-8">
                                {{ field }}
                                <span class="help-block">{{ field.errors.0 }}</span>
                            </div>
                        </div>
                    {% endif %}
                {% endfor %}


                {# 添加文章详情的表单 #}
                {% for field in detail_form %}

                    <div class="form-group  {% if field.errors %}has-error{% endif %}">

                        <div class="col-lg-offset-1 col-sm-10">
                            {{ field }}
                            <span class="help-block">{{ field.errors.0 }}</span>
                        </div>
                    </div>
                {% endfor %}

                <button class="btn btn-success pull-right" style="margin-right: 244px">确认</button>
            </form>
            <div class="text-danger text-center">  {{ form_obj.non_field_errors.0 }} </div>
            <div class="text-danger text-center">  {{ detail_form.non_field_errors.0 }} </div>

        </div>

    </div>

{% endblock content %}



{% block js %}
    <script src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
    <script src="{% static 'ckeditor/ckeditor-init.js' %}"></script>
{% endblock js %}
article_form.html

3、文章详情的页面:

{% extends 'layout.html' %}


{% block content %}

    <h2 class="text-danger">{{ title }}</h2>

    <div>
        {# 加上safe #}
        {{ content|safe }}
    </div>

{% endblock content %}
article_detail.html

项目的forms校验的类

# -*- coding:utf-8 -*-
from django import forms
# 存放Model的应用是repository
from repository import models

class ArticleForm(forms.ModelForm):
    class Meta:
        model = models.Article
        fields = '__all__'

    def __init__(self,*args,**kwargs):
        super(ArticleForm, self).__init__(*args,**kwargs)

        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'


class ArticleDetailForm(forms.ModelForm):
    class Meta:
        model = models.ArticleDetail
        fields = '__all__'

settings文件相关的配置

STATIC_URL = '/static/'
# 不写dirs,默认会去从名为“static”的文件夹中去找静态文件
# STATICFILES_DIRS = [
#     os.path.join(BASE_DIR, 'web/static'),
# ]

### 媒体配置
MEDIA_URL ='/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')


### 富文本编辑器的配置
CKEDITOR_UPLOAD_PATH = 'ckeditor/'

media的配置及访问说明

本项目主要在两个地方使用到了media的配置:一个是在添加文章时选择图片那里另外一个地方是文章列表那里显示用户上传的图像

之前的博客

我之前有个博客也介绍了Django的media的相关配置及使用:https://www.cnblogs.com/paulwhw/p/9551151.html

本项目的具体配置

创建文件夹以及在settings中的配置

Django的媒体配置主要是对用户上传的文件进行统一的 管理。约定俗成的,我们习惯将媒体配置的文件夹命名为“media”,配置的路径的名字也命名为media

(1)首先,在项目的跟目录下新建一个名为media的目录

(2)然后,在项目的settings中进行如下配置

### 媒体配置
MEDIA_URL ='/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')

项目“总路由”里的配置

这里建议大家不要在分发的路由里配置~

from django.conf.urls import url,include
from django.contrib import admin
from django.conf import settings from django.views.static import serve urlpatterns = [ # 媒体配置 # media配置——配合settings中的MEDIA_ROOT的配置,就可以在浏览器的地址栏访问media文件夹及里面的文件了 url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}), ]

项目Model的配置

项目的Article这个Model里的img属性对应的是用户上传的文件,注意用的是ImageField:

class Article(models.Model):
    title = models.CharField(verbose_name='文章标题',max_length=56)
    summary = models.CharField(verbose_name='文章摘要',max_length=256)
    create_at = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
    # upload_to用相对路径,不能用/img/article/这样的绝对路径指定!
    img = models.ImageField(upload_to='img/article/',verbose_name='显示图片',blank=True,default='img/article/default.jpg')

    category = models.ForeignKey(to=Category,blank=True,null=True,verbose_name='文章分类')
    # 如果设置成null=True,会出现“文章详情有数据但是其中的文章的这个字段为空”的情况
    detail = models.OneToOneField(to=ArticleDetail,verbose_name='文章详情',null=True,blank=True)

这里需要注意:media文件夹是我们上传文件的“根目录”,如果我们想再为这个“根目录”指定“子目录”的话需要通过参数upload_to去指定,也就是说,我们上传的文件会保存在media/img/article/目录下,后面的参数default表示默认图像————比如说用户不指定图像的时候就用default参数指定的图片。以后用户上传的图片会存放在服务器的media/img/article/这个目录下。

form表单校验时的注意事项

添加与编辑功能用到了ModelForm的校验。需要特别注意了:由于我们这里有文件的上传,需要把form表单的enctype改成multipart/form-data

<form action="" class="form-horizontal" method="post" enctype="multipart/form-data" novalidate>
                {% csrf_token %}
                {# 一个页面显示2个校验表单 #}
                {# 添加文章的表单 #}
                {% for field in form_obj %}
                    {# 注意这里不显示Article表的detail属性 #}
                    {# field.name 标签对应的name属性的值 就是数据库中的字段的值 #}
                    {% if field.name != 'detail' %}
                        <div class="form-group  {% if field.errors %}has-error{% endif %}">
                            <label   {% if not field.field.required %} style="color: #777777" {% endif %}
                                                                       for="{{ field.id_for_label }}"
                                                                       class="col-sm-2 control-label">{{ field.label }}</label>
                            <div class="col-sm-8">
                                {{ field }}
                                <span class="help-block">{{ field.errors.0 }}</span>
                            </div>
                        </div>
                    {% endif %}
                {% endfor %}


                {# 添加文章详情的表单 #}
                {% for field in detail_form %}

                    <div class="form-group  {% if field.errors %}has-error{% endif %}">

                        <div class="col-lg-offset-1 col-sm-10">
                            {{ field }}
                            <span class="help-block">{{ field.errors.0 }}</span>
                        </div>
                    </div>
                {% endfor %}

                <button class="btn btn-success pull-right" style="margin-right: 244px">确认</button>
            </form>

视图函数处理时的注意事项

视图函数中接收文件类型的数据要用request.FILES!request.POST中只有用户输入的数据!

注意一下form_obj的写法(这里只截取添加功能的片段代码~~编辑的话还有一个instance=的关键字参数)

 # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
# QueryDict需要copy()一下才能修改!
qd = request.POST.copy()
# 注意!键是Article表做外键关联的那个属性值;值是对应的ArticleDetail中的对应的对象的pk值~先用instance取出文章详情的对象
qd['detail'] = detail_form.instance.pk
print(qd)
# 文章表中有数据和文件,文件需要用request.FILES获取
# 注意~这里用户输入的数据应该是qd了!!!
form_obj = forms.ArticleForm(data=qd,files=request.FILES)

图片文件的访问

关于文件的访问我上面的那篇博客介绍了一种通过注册中间件”的方式

下面介绍一下我在本项目中的“文章列表”实现图片展示的方法:

(1)首先,在模板中load static

{# {% get_media_prefix %}方法需要先load static!!!#}
{% load static %}

(2)然后,在进行for循环遍历的时候通过 get_media_prefix 方法加上图片的相对路径去展示图片

{% for article in all_articles %}
            <tr>
                <td>{{ forloop.counter }}</td>
                
       {
# 谨慎使用用 {{ article.img.url }} 如果没有传图片的话会报错!但是在Model中给每个用户设置一个默认的图像也是可以的! #} <td><img src="{% get_media_prefix %}{{ article.img }}" alt="" width="52px" height="52px"></td>
          <td>{{ article.title }}</td> <td>{{ article.category }}</td> <td class="tpl-table-black-operation"> <a href="{% url 'backend:article_edit' article.pk %}"> <i class="am-icon-pencil"></i> 编辑 </a> <a href="{% url 'backend:article_detail' article.pk %}" class="tpl-table-black-operation"> <i class="am-icon-random"></i> 文章详情 </a> <a href="{% url 'backend:article_del' article.pk %}" class="tpl-table-black-operation-del"> <i class="am-icon-trash"></i> 删除 </a> </td> </tr> {% endfor %}

富文本编辑器ckeditor的使用说明

 下载

pip install django-ckeditor

在settings中注册

INSTALLED_APPS = [
    
    'ckeditor',
    'ckeditor_uploader',
]

在Model中使用字段

from ckeditor_uploader.fields import RichTextUploadingField


class ArticleDetail(models.Model):
    content = RichTextUploadingField(verbose_name='文章详情')

在项目的“总路由”中配置

from ckeditor_uploader import views

urlpatterns = [

    # 上传文件
    url(r'^ckeditor/upload/', views.upload),
    url(r'^ckeditor/', include('ckeditor_uploader.urls')),

]

数据库迁移

如果你是在中途把富文本编辑器加进来的话,记得要进行数据库的迁移!

模板中的配置

注意在模板中的配置有一个坑!

看下面的代码可知,我们是从自己配置的static文件存放的目录下再去找:ckeditor/ckeditor/ckeditor.js文件与ckeditor/ckeditor-init.js文件。

但是,这两个文件其实是在我们的解释器下面的site-packages文件夹中的!而且里面存放这两个文件的根目录叫static!

如果你在settings中配置了STATICFILES_DIRS,并且存放静态文件的目录改成了staticfiles(反正不叫static),那么是无法找到这两个文件的!

对于这种情况,我们可以在site-packages中找到这两个文件,然后把他两存放在我们自己的staticfiles文件夹中就好了!

{{ field }}   富文本编辑框的字段

<script src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
<script src="{% static 'ckeditor/ckeditor-init.js' %}"></script>

其他细节的说明

Article表的detail字段设置成可以为空的一个坑

Model是这样设计的

class ArticleDetail(models.Model):
    # 使用富文本编辑器
    content = RichTextUploadingField(verbose_name='文章详情')


class Article(models.Model):
    title = models.CharField(verbose_name='文章标题',max_length=56)
    summary = models.CharField(verbose_name='文章摘要',max_length=256)
    create_at = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
    # 相对路径
    img = models.ImageField(upload_to='img/article/',verbose_name='显示图片',blank=True,default='img/article/default.jpg')

    category = models.ForeignKey(to=Category,blank=True,null=True,verbose_name='文章分类')

    # 如果设置成null=True,会出现“文章详情有数据但是其中的文章的这个字段为空”的情况
    detail = models.OneToOneField(to=ArticleDetail,verbose_name='文章详情',null=True,blank=True)

ModelForm中对所有的字段都进行校验

# -*- coding:utf-8 -*-
from django import forms

from repository import models

class ArticleForm(forms.ModelForm):
    class Meta:
        model = models.Article
        fields = '__all__'

    def __init__(self,*args,**kwargs):
        super(ArticleForm, self).__init__(*args,**kwargs)

        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'


class ArticleDetailForm(forms.ModelForm):
    class Meta:
        model = models.ArticleDetail
        fields = '__all__'
文章与文章详情的ModelForm校验

模板中用一个form表单对两个Model进行了校验

这里需要特别说明一下:Article表中的detail属性存放的是ArticleDetail表中的每项记录的id!

我在这里没有把Article的detail显示出来!

因为前面设置了null=True与blank=True,所以这里可以通过校验。

所以detail_id往后台传的其实是一个空的值!

然后我在后台进行数据处理的时候往按照ArticleDetail的内容往request.POST中添加了detail的键值对——由于QueryDict不能直接修改,所以需要先copy以后再往里面添加数据

{% extends 'layout.html' %}

{% load staticfiles %}

{% block content %}
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">{{ title }}</h3>
        </div>

        <div class="panel-body">
            <form action="" class="form-horizontal" method="post" enctype="multipart/form-data" novalidate>
                {% csrf_token %}
                {# 一个页面显示2个校验表单 #}
                {# 添加文章的表单 #}
                {% for field in form_obj %}
                    {# 注意这里不显示Article表的detail属性 #}
                    {# field.name 标签对应的name属性的值 就是数据库中的字段的值 #}
                    {% if field.name != 'detail' %}
                        <div class="form-group  {% if field.errors %}has-error{% endif %}">
                            <label   {% if not field.field.required %} style="color: #777777" {% endif %}
                                                                       for="{{ field.id_for_label }}"
                                                                       class="col-sm-2 control-label">{{ field.label }}</label>
                            <div class="col-sm-8">
                                {{ field }}
                                <span class="help-block">{{ field.errors.0 }}</span>
                            </div>
                        </div>
                    {% endif %}
                {% endfor %}


                {# 添加文章详情的表单 #}
                {% for field in detail_form %}

                    <div class="form-group  {% if field.errors %}has-error{% endif %}">

                        <div class="col-lg-offset-1 col-sm-10">
                            {{ field }}
                            <span class="help-block">{{ field.errors.0 }}</span>
                        </div>
                    </div>
                {% endfor %}

                <button class="btn btn-success pull-right" style="margin-right: 244px">确认</button>
            </form>
            <div class="text-danger text-center">  {{ form_obj.non_field_errors.0 }} </div>
            <div class="text-danger text-center">  {{ detail_form.non_field_errors.0 }} </div>

        </div>

    </div>

{% endblock content %}



{% block js %}
    <script src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
    <script src="{% static 'ckeditor/ckeditor-init.js' %}"></script>
{% endblock js %}

后台视图函数的处理及这个坑的处理

一个坑

先说遇到的坑:如果文章详情(就是富文本编辑框)什么也不填,往后台还是能传入数据的——此时会遇到这种情况~文章详情没有数据但是文章表中有数据。

为了避免这种情况,我在后台做了一下判断,如果request.POST.get('content')(取到的就是富文本编辑框中的内容)中的值为空的话不让他校验成功~重新返回当前的页面~当然这里做的简单了,聪明的你也许会想到更好的解决方式~这里只是说一下这个问题。

添加与编辑后台视图的处理

添加功能

(1)需要考虑一种情况:文章详情添加成功但是文章添加失败了~~这种情况需要把文章详情添加的数据删除了!

(2)给request.POST中添加键值对的话需要先copy一下!因为它是一个QueryDict类型的数据!

(3)注意form_obj的写法!有文件上传需要加files=request.FILES!

# 添加文章有一个异常情况:
# 文章详情的content中不输入数据也能通过校验!———— 所以必须判断一下content中是否有输入!
def article_add(request):
    # 一个页面加两个form校验表单:文章与文章详情
    form_obj = forms.ArticleForm()
    detail_form = forms.ArticleDetailForm()
    title = '新增文章'
    if request.method == 'GET':
        return render(request,'article/article_form.html',{'title':title,'form_obj':form_obj,'detail_form':detail_form})
    elif request.method == 'POST':
        # 这里有个异常情况:文章详情的content中不输入数据也能通过校验!———— 所以必须判断一下content中是否有输入!
        if request.POST.get('content'):
            # 文章详情
            detail_form = forms.ArticleDetailForm(request.POST)
            if detail_form.is_valid():
                detail_form.save()
            # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
            # QueryDict需要copy()一下才能修改!
            qd = request.POST.copy()
            # 注意!键是Article表做外键关联的那个属性值;值是对应的ArticleDetail中的对应的对象的pk值~先用instance取出文章详情的对象
            qd['detail'] = detail_form.instance.pk
            print(qd)
            # 文章表中有数据和文件,文件需要用request.FILES获取
            # 注意~这里的数据应该是qd了!!!
            form_obj = forms.ArticleForm(data=qd,files=request.FILES)
            if form_obj.is_valid():
                # print(111111111111111111111111)
                form_obj.save()
                return redirect('backend:article_list')

            # 这种情况是:文章详情添加成功但是文章添加失败了~需要把文章详情添加的数据删除了
            # 文章与文章详情的数据保持一致
            elif detail_form.is_valid() and detail_form.instance:
                detail_form.instance.delete()
                # print(222222222222222222)

            return render(request, 'article/article_form.html',
                              {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})
        else:
            return render(request, 'article/article_form.html',
                          {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})

编辑功能

(1)如果编辑了新的图片的话,把之前的图片删除了

(2)添加功能的说明(1)不用加了~其他的都跟添加差不多

(3)需要注意!编辑的form_obj一定要加instance=xxx~~老生常谈的问题了!

# 编辑文章——如果用户上传了新的图片,应该把之前的图片删掉
def article_edit(request,pk):
    # 一个页面显示两个表单
    # 文章对象
    article_obj = models.Article.objects.filter(pk=pk).first()
    # 提前将原文章中的图片对象取出来
    img_obj = article_obj.img

    form_obj = forms.ArticleForm(instance=article_obj)
    # 基于对象的跨表查询
    detail_form = forms.ArticleDetailForm(instance=article_obj.detail)
    title = '编辑文章'
    if request.method == 'GET':
        return render(request,'article/article_form.html',{'title':title,'form_obj':form_obj,'detail_form':detail_form})

    elif request.method == 'POST':
        # 文章详情
        detail_form =forms.ArticleDetailForm(data=request.POST,instance=article_obj.detail)
        if detail_form.is_valid():
            detail_form.save()
        # 文章的处理
        # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
        # QueryDict需要copy()一下才能修改!
        qd = request.POST.copy()
        qd['detail'] =detail_form.instance.pk
        # 注意:data应该是qd了!!!~~还得加上article_obj~否则成了添加了!
        form_obj = forms.ArticleForm(data=qd,files=request.FILES,instance=article_obj)
        if form_obj.is_valid():
            # 判断一下新传入的图片对象跟之前的是否相等
            # 如果没有传入图片就继续
            if not form_obj.cleaned_data.get('img'):
                pass
            if form_obj.cleaned_data.get('img') != img_obj:
                try:
                    # 将之前的图片删除了
                    os.remove(img_obj.path)
                except Exception as e:
                    pass
            form_obj.save()
            return redirect('backend:article_list')
        return render(request, 'article/article_form.html',
                      {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})

删除功能注意需要把文章以及文章详情的图片删了 ***

这里用到了正则表达式去匹配文章详情中的图片

# 删除文章~~
# 把Article中的图片以及文章详情的content中的图片同时也删掉~~
def article_del(request,pk):
    # 这里注意:一对一的关系属性加在了Article类中
    # “级联删除”的功能应该是:删除ArticleDetail表中的数据后Article表中的数据跟着删除
    # 但是删除Article中的数据的话ArticleDetail中对应的数据不会跟着删除
    # 先找到Article对象,注意删除的是ArticleDetail的对象!
    article_obj = models.Article.objects.filter(pk=pk).first()
    # 基于对象的跨表查询
    detail_obj = article_obj.detail
# 找到图片对象 img_file_obj =
article_obj.img
# path方法找到文件的绝对路径 img_file_path = img_file_obj.path content = detail_obj.content print(content) # 如果有图片~把文章中的图片也删除了 if 'img' in content: # 异常情况是:服务器中没有图片文件而数据库中有记录(人为误删导致) try: # 匹配多个格式的图片文件~~~注意取消分组优先!!! result = re.findall(r'src=".+.(?:jpg|png|jpeg|PNG|JPG|JPEG)"',content) print(result) #['src="/media/ckeditor/2019/07/23/itachi3.PNG"', 'src="/media/ckeditor/2019/07/23/default.jpg"'] for src in result: # 去掉前面的前缀(前面那个斜杠也去掉!)以及后面的那个引号 path = src[6:-1] file_img_path = os.path.join(settings.BASE_DIR,path) # # 删除这张图片 os.remove(file_img_path) except Exception as e: pass # 删除Article中的图片文件 os.remove(img_file_path) # 删除文章详情————有级联删除~文章表对应的记录也删了(字段是在文章表中定义的) detail_obj.delete() return redirect('backend:article_list')

~

原文地址:https://www.cnblogs.com/paulwhw/p/11240222.html