效果预览
文章列表
添加文章
编辑文章|文章详情|删除文章
项目的基本文件
项目的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'), ]
项目的视图
# -*- 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 %}
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 %}
3、文章详情的页面:
{% extends 'layout.html' %} {% block content %} <h2 class="text-danger">{{ title }}</h2> <div> {# 加上safe #} {{ content|safe }} </div> {% endblock content %}
项目的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__'
模板中用一个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')
~