效果预览

文章列表

添加文章

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

项目的基本文件

项目的Model

  1. from django.db import models
  2. # 导入富文本编辑器相关的模块
  3. from ckeditor_uploader.fields import RichTextUploadingField
  4. class Category(models.Model):
  5. name = models.CharField(max_length=12,verbose_name='分类名称')
  6.  
  7. def __str__(self):
  8. return self.name
  9.  
  10. class ArticleDetail(models.Model):
  11. # 使用富文本编辑器
  12. content = RichTextUploadingField(verbose_name='文章详情')
  13.  
  14. class Article(models.Model):
  15. title = models.CharField(verbose_name='文章标题',max_length=56)
  16. summary = models.CharField(verbose_name='文章摘要',max_length=256)
  17. create_at = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
  18. # 相对路径
  19. img = models.ImageField(upload_to='img/article/',verbose_name='显示图片',blank=True,default='img/article/default.jpg')
  20.  
  21. category = models.ForeignKey(to=Category,blank=True,null=True,verbose_name='文章分类')
  22. # 如果设置成null=True,会出现“文章详情有数据但是其中的文章的这个字段为空”的情况
  23. detail = models.OneToOneField(to=ArticleDetail,verbose_name='文章详情',null=True,blank=True)

项目的url配置

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

  1. from django.conf.urls import url
  2.  
  3. from backend.views import article
  4.  
  5. urlpatterns = [
  6.  
  7. url(r'^article_list\.html/$', article.article_list,name='article_list'),
  8. url(r'article_add\.html/$',article.article_add,name='article_add'),
  9. url(r'article_edit\.html/(?P<pk>\d+)/$',article.article_edit,name='article_edit'),
  10. url(r'article_del\.html/(?P<pk>\d+)/$',article.article_del,name='article_del'),
  11. url(r'article_detail\.html/(?P<pk>\d+)/$',article.article_detail,name='article_detail'),
  12.  
  13. ]

项目的url

项目的视图

  1. # -*- coding:utf-8 -*-
  2. import os
  3. import re
  4. from django.shortcuts import render,redirect
  5. from django.conf import settings
  6.  
  7. from repository import models
  8. from backend import forms
  9.  
  10. def article_list(request):
  11. if request.method == 'GET':
  12. all_articles = models.Article.objects.all()
  13. return render(request,'article/article_list.html',locals())
  14.  
  15. # 添加文章有一个异常情况:
  16. # 文章详情的content中不输入数据也能通过校验!———— 所以必须判断一下content中是否有输入!
  17. def article_add(request):
  18. # 一个页面加两个form校验表单:文章与文章详情
  19. form_obj = forms.ArticleForm()
  20. detail_form = forms.ArticleDetailForm()
  21. title = '新增文章'
  22. if request.method == 'GET':
  23. return render(request,'article/article_form.html',{'title':title,'form_obj':form_obj,'detail_form':detail_form})
  24. elif request.method == 'POST':
  25. # 这里有个异常情况:文章详情的content中不输入数据也能通过校验!———— 所以必须判断一下content中是否有输入!
  26. if request.POST.get('content'):
  27. # 文章详情
  28. detail_form = forms.ArticleDetailForm(request.POST)
  29. if detail_form.is_valid():
  30. detail_form.save()
  31. # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
  32. # QueryDict需要copy()一下才能修改!
  33. qd = request.POST.copy()
  34. # 注意!键是Article表做外键关联的那个属性值;值是对应的ArticleDetail中的对应的对象的pk值~先用instance取出文章详情的对象
  35. qd['detail'] = detail_form.instance.pk
  36. print(qd)
  37. # 文章表中有数据和文件,文件需要用request.FILES获取
  38. # 注意~这里的数据应该是qd了!!!
  39. form_obj = forms.ArticleForm(data=qd,files=request.FILES)
  40. if form_obj.is_valid():
  41. # print(111111111111111111111111)
  42. form_obj.save()
  43. return redirect('backend:article_list')
  44.  
  45. # 这种情况是:文章详情添加成功但是文章添加失败了~需要把文章详情添加的数据删除了
  46. # 文章与文章详情的数据保持一致
  47. elif detail_form.is_valid() and detail_form.instance:
  48. detail_form.instance.delete()
  49. # print(222222222222222222)
  50.  
  51. return render(request, 'article/article_form.html',
  52. {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})
  53. else:
  54. return render(request, 'article/article_form.html',
  55. {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})
  56.  
  57. # 编辑文章——如果用户上传了新的图片,应该把之前的图片删掉
  58. def article_edit(request,pk):
  59. # 一个页面显示两个表单
  60. # 文章对象
  61. article_obj = models.Article.objects.filter(pk=pk).first()
  62. # 提前将原文章中的图片对象取出来
  63. img_obj = article_obj.img
  64.  
  65. form_obj = forms.ArticleForm(instance=article_obj)
  66. # 基于对象的跨表查询
  67. detail_form = forms.ArticleDetailForm(instance=article_obj.detail)
  68. title = '编辑文章'
  69. if request.method == 'GET':
  70. return render(request,'article/article_form.html',{'title':title,'form_obj':form_obj,'detail_form':detail_form})
  71.  
  72. elif request.method == 'POST':
  73. # 文章详情
  74. detail_form =forms.ArticleDetailForm(data=request.POST,instance=article_obj.detail)
  75. if detail_form.is_valid():
  76. detail_form.save()
  77. # 文章的处理
  78. # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
  79. # QueryDict需要copy()一下才能修改!
  80. qd = request.POST.copy()
  81. qd['detail'] =detail_form.instance.pk
  82. # 注意:data应该是qd了!!!~~还得加上article_obj~否则成了添加了!
  83. form_obj = forms.ArticleForm(data=qd,files=request.FILES,instance=article_obj)
  84. if form_obj.is_valid():
  85. # 判断一下新传入的图片对象跟之前的是否相等
  86. # 如果没有传入图片就继续
  87. if not form_obj.cleaned_data.get('img'):
  88. pass
  89. if form_obj.cleaned_data.get('img') != img_obj:
  90. try:
  91. # 将之前的图片删除了
  92. os.remove(img_obj.path)
  93. except Exception as e:
  94. pass
  95. form_obj.save()
  96. return redirect('backend:article_list')
  97. return render(request, 'article/article_form.html',
  98. {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})
  99.  
  100. # 删除文章~~
  101. # 把Article中的图片以及文章详情的content中的图片同时也删掉~~
  102. def article_del(request,pk):
  103. # 这里注意:一对一的关系属性加在了Article类中
  104. # “级联删除”的功能应该是:删除ArticleDetail表中的数据后Article表中的数据跟着删除
  105. # 但是删除Article中的数据的话ArticleDetail中对应的数据不会跟着删除
  106. # 先找到Article对象,注意删除的是ArticleDetail的对象!
  107. article_obj = models.Article.objects.filter(pk=pk).first()
  108. # 基于对象的跨表查询
  109. detail_obj = article_obj.detail
  110. # 找到图片对象
  111. img_file_obj = article_obj.img
  112. # path方法找到文件的绝对路径
  113. img_file_path = img_file_obj.path
  114.  
  115. content = detail_obj.content
  116. print(content)
  117. # 如果有图片~把文章中的图片也删除了
  118. if 'img' in content:
  119. # 异常情况是:服务器中没有图片文件而数据库中有记录(人为误删导致)
  120. try:
  121. # 匹配多个格式的图片文件~~~注意取消分组优先!!!
  122. result = re.findall(r'src=".+\.(?:jpg|png|jpeg|PNG|JPG|JPEG)"',content)
  123. print(result) #['src="/media/ckeditor/2019/07/23/itachi3.PNG"', 'src="/media/ckeditor/2019/07/23/default.jpg"']
  124. for src in result:
  125. # 去掉前面的前缀(前面那个斜杠也去掉!)以及后面的那个引号
  126. path = src[6:-1]
  127. file_img_path = os.path.join(settings.BASE_DIR,path)
  128. # # 删除这张图片
  129. os.remove(file_img_path)
  130. except Exception as e:
  131. pass
  132.  
  133. # 删除Article中的图片文件
  134. os.remove(img_file_path)
  135. # 删除文章详情————有级联删除~文章表对应的记录也删了(字段是在文章表中定义的)
  136. detail_obj.delete()
  137. return redirect('backend:article_list')
  138.  
  139. # 文章表详情~前端模板中用safe渲染!
  140. def article_detail(request,pk):
  141. article_obj = models.Article.objects.filter(pk=pk).first()
  142.  
  143. title = article_obj.title
  144. content = article_obj.detail.content
  145. return render(request,'article/article_detail.html',{'content':content,'title':title})

项目的视图

项目的模板文件

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

  1. {% extends 'layout.html' %}
  2.  
  3. {# {% get_media_prefix %}方法需要先load static!!!#}
  4. {% load static %}
  5.  
  6. {% block content %}
  7.  
  8. <h2 class="text-danger">文章列表</h2>
  9.  
  10. <a href="{% url 'backend:article_add' %}" class="btn btn-success">添加文章</a>
  11. <table class="table table-condensed table-bordered" style="margin-top: 22px">
  12. <thead>
  13. <tr>
  14. <th>序号</th>
  15. <th>图像</th>
  16. <th>标题</th>
  17. <th>分类</th>
  18. <th>操作</th>
  19. </tr>
  20. </thead>
  21.  
  22. <tbody>
  23. {% for article in all_articles %}
  24. <tr>
  25. <td>{{ forloop.counter }}</td>
  26.  
  27. {# 不要用 {{ article.img/url }} 如果没有传图片的话会报错! #}
  28. <td><img src="{% get_media_prefix %}{{ article.img }}" alt="" width="52px" height="52px"></td>
  29. <td>{{ article.title }}</td>
  30. <td>{{ article.category }}</td>
  31. <td class="tpl-table-black-operation">
  32. <a href="{% url 'backend:article_edit' article.pk %}">
  33. <i class="am-icon-pencil"></i> 编辑
  34. </a>
  35. <a href="{% url 'backend:article_detail' article.pk %}"
  36. class="tpl-table-black-operation">
  37. <i class="am-icon-random"></i> 文章详情
  38. </a>
  39. <a href="{% url 'backend:article_del' article.pk %}"
  40. class="tpl-table-black-operation-del">
  41. <i class="am-icon-trash"></i> 删除
  42. </a>
  43. </td>
  44. </tr>
  45. {% endfor %}
  46. </tbody>
  47. </table>
  48.  
  49. {% endblock content %}

article_list.html

2、用于校验的页面:

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

  1. {% extends 'layout.html' %}
  2.  
  3. {% load staticfiles %}
  4.  
  5. {% block content %}
  6. <div class="panel panel-default">
  7. <div class="panel-heading">
  8. <h3 class="panel-title">{{ title }}</h3>
  9. </div>
  10.  
  11. <div class="panel-body">
  12. <form action="" class="form-horizontal" method="post" enctype="multipart/form-data" novalidate>
  13. {% csrf_token %}
  14.  
  15. {# 一个页面显示2个校验表单 #}
  16.  
  17. {# 添加文章的表单 #}
  18. {% for field in form_obj %}
  19. {# 注意这里不显示Article表的detail属性 #}
  20. {# field.name 标签对应的name属性的值 就是数据库中的字段的值 #}
  21. {% if field.name != 'detail' %}
  22. <div class="form-group {% if field.errors %}has-error{% endif %}">
  23. <label {% if not field.field.required %} style="color: #777777" {% endif %}
  24. for="{{ field.id_for_label }}"
  25. class="col-sm-2 control-label">{{ field.label }}</label>
  26. <div class="col-sm-8">
  27. {{ field }}
  28. <span class="help-block">{{ field.errors.0 }}</span>
  29. </div>
  30. </div>
  31. {% endif %}
  32. {% endfor %}
  33.  
  34. {# 添加文章详情的表单 #}
  35. {% for field in detail_form %}
  36.  
  37. <div class="form-group {% if field.errors %}has-error{% endif %}">
  38.  
  39. <div class="col-lg-offset-1 col-sm-10">
  40. {{ field }}
  41. <span class="help-block">{{ field.errors.0 }}</span>
  42. </div>
  43. </div>
  44. {% endfor %}
  45.  
  46. <button class="btn btn-success pull-right" style="margin-right: 244px">确认</button>
  47. </form>
  48. <div class="text-danger text-center"> {{ form_obj.non_field_errors.0 }} </div>
  49. <div class="text-danger text-center"> {{ detail_form.non_field_errors.0 }} </div>
  50.  
  51. </div>
  52.  
  53. </div>
  54.  
  55. {% endblock content %}
  56.  
  57. {% block js %}
  58. <script src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
  59. <script src="{% static 'ckeditor/ckeditor-init.js' %}"></script>
  60. {% endblock js %}

article_form.html

3、文章详情的页面:

  1. {% extends 'layout.html' %}
  2.  
  3. {% block content %}
  4.  
  5. <h2 class="text-danger">{{ title }}</h2>
  6.  
  7. <div>
  8. {# 加上safe #}
  9. {{ content|safe }}
  10. </div>
  11.  
  12. {% endblock content %}

article_detail.html

项目的forms校验的类

  1. # -*- coding:utf-8 -*-
  2. from django import forms
  3. # 存放Model的应用是repository
  4. from repository import models
  5.  
  6. class ArticleForm(forms.ModelForm):
  7. class Meta:
  8. model = models.Article
  9. fields = '__all__'
  10.  
  11. def __init__(self,*args,**kwargs):
  12. super(ArticleForm, self).__init__(*args,**kwargs)
  13.  
  14. for field in self.fields.values():
  15. field.widget.attrs['class'] = 'form-control'
  16.  
  17. class ArticleDetailForm(forms.ModelForm):
  18. class Meta:
  19. model = models.ArticleDetail
  20. fields = '__all__'

settings文件相关的配置

  1. STATIC_URL = '/static/'
  2. # 不写dirs,默认会去从名为“static”的文件夹中去找静态文件
  3. # STATICFILES_DIRS = [
  4. # os.path.join(BASE_DIR, 'web/static'),
  5. # ]
  6.  
  7. ### 媒体配置
  8. MEDIA_URL ='/media/'
  9. MEDIA_ROOT = os.path.join(BASE_DIR,'media')
  10.  
  11. ### 富文本编辑器的配置
  12. CKEDITOR_UPLOAD_PATH = 'ckeditor/'

media的配置及访问说明

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

之前的博客

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

本项目的具体配置

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

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

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

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

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

项目“总路由”里的配置

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

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

项目Model的配置

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

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

  7. category = models.ForeignKey(to=Category,blank=True,null=True,verbose_name='文章分类')
  8. # 如果设置成null=True,会出现“文章详情有数据但是其中的文章的这个字段为空”的情况
  9. 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

  1. <form action="" class="form-horizontal" method="post" enctype="multipart/form-data" novalidate>
  2. {% csrf_token %}
  3. {# 一个页面显示2个校验表单 #}
  4. {# 添加文章的表单 #}
  5. {% for field in form_obj %}
  6. {# 注意这里不显示Article表的detail属性 #}
  7. {# field.name 标签对应的name属性的值 就是数据库中的字段的值 #}
  8. {% if field.name != 'detail' %}
  9. <div class="form-group {% if field.errors %}has-error{% endif %}">
  10. <label {% if not field.field.required %} style="color: #777777" {% endif %}
  11. for="{{ field.id_for_label }}"
  12. class="col-sm-2 control-label">{{ field.label }}</label>
  13. <div class="col-sm-8">
  14. {{ field }}
  15. <span class="help-block">{{ field.errors.0 }}</span>
  16. </div>
  17. </div>
  18. {% endif %}
  19. {% endfor %}
  20.  
  21. {# 添加文章详情的表单 #}
  22. {% for field in detail_form %}
  23.  
  24. <div class="form-group {% if field.errors %}has-error{% endif %}">
  25.  
  26. <div class="col-lg-offset-1 col-sm-10">
  27. {{ field }}
  28. <span class="help-block">{{ field.errors.0 }}</span>
  29. </div>
  30. </div>
  31. {% endfor %}
  32.  
  33. <button class="btn btn-success pull-right" style="margin-right: 244px">确认</button>
  34. </form>

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

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

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

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

图片文件的访问

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

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

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

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

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

  1. {% for article in all_articles %}
  2. <tr>
  3. <td>{{ forloop.counter }}</td>

  4.        {# 谨慎使用用 {{ article.img.url }} 如果没有传图片的话会报错!但是在Model中给每个用户设置一个默认的图像也是可以的! #}
  5. <td><img src="{% get_media_prefix %}{{ article.img }}" alt="" width="52px" height="52px"></td>

  6.           <td>{{ article.title }}</td>
  7. <td>{{ article.category }}</td>
  8. <td class="tpl-table-black-operation">
  9. <a href="{% url 'backend:article_edit' article.pk %}">
  10. <i class="am-icon-pencil"></i> 编辑
  11. </a>
  12. <a href="{% url 'backend:article_detail' article.pk %}"
  13. class="tpl-table-black-operation">
  14. <i class="am-icon-random"></i> 文章详情
  15. </a>
  16. <a href="{% url 'backend:article_del' article.pk %}"
  17. class="tpl-table-black-operation-del">
  18. <i class="am-icon-trash"></i> 删除
  19. </a>
  20. </td>
  21. </tr>
  22. {% endfor %}

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

下载

  1. pip install django-ckeditor

在settings中注册

  1. INSTALLED_APPS = [
  2.  
  3. 'ckeditor',
  4. 'ckeditor_uploader',
  5. ]

在Model中使用字段

  1. from ckeditor_uploader.fields import RichTextUploadingField
  2. class ArticleDetail(models.Model):
  3. content = RichTextUploadingField(verbose_name='文章详情')

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

  1. from ckeditor_uploader import views
  2.  
  3. urlpatterns = [
  4.  
  5. # 上传文件
  6. url(r'^ckeditor/upload/', views.upload),
  7. url(r'^ckeditor/', include('ckeditor_uploader.urls')),
  8.  
  9. ]

数据库迁移

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

模板中的配置

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

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

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

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

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

  1. {{ field }} 富文本编辑框的字段
  2.  
  3. <script src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
  4. <script src="{% static 'ckeditor/ckeditor-init.js' %}"></script>

其他细节的说明

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

Model是这样设计的

  1. class ArticleDetail(models.Model):
  2. # 使用富文本编辑器
  3. content = RichTextUploadingField(verbose_name='文章详情')
  4.  
  5. class Article(models.Model):
  6. title = models.CharField(verbose_name='文章标题',max_length=56)
  7. summary = models.CharField(verbose_name='文章摘要',max_length=256)
  8. create_at = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
  9. # 相对路径
  10. img = models.ImageField(upload_to='img/article/',verbose_name='显示图片',blank=True,default='img/article/default.jpg')
  11.  
  12. category = models.ForeignKey(to=Category,blank=True,null=True,verbose_name='文章分类')
  13.  
  14. # 如果设置成null=True,会出现“文章详情有数据但是其中的文章的这个字段为空”的情况
  15. detail = models.OneToOneField(to=ArticleDetail,verbose_name='文章详情',null=True,blank=True)

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

  1. # -*- coding:utf-8 -*-
  2. from django import forms
  3.  
  4. from repository import models
  5.  
  6. class ArticleForm(forms.ModelForm):
  7. class Meta:
  8. model = models.Article
  9. fields = '__all__'
  10.  
  11. def __init__(self,*args,**kwargs):
  12. super(ArticleForm, self).__init__(*args,**kwargs)
  13.  
  14. for field in self.fields.values():
  15. field.widget.attrs['class'] = 'form-control'
  16.  
  17. class ArticleDetailForm(forms.ModelForm):
  18. class Meta:
  19. model = models.ArticleDetail
  20. fields = '__all__'

文章与文章详情的ModelForm校验

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

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

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

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

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

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

  1. {% extends 'layout.html' %}
  2.  
  3. {% load staticfiles %}
  4.  
  5. {% block content %}
  6. <div class="panel panel-default">
  7. <div class="panel-heading">
  8. <h3 class="panel-title">{{ title }}</h3>
  9. </div>
  10.  
  11. <div class="panel-body">
  12. <form action="" class="form-horizontal" method="post" enctype="multipart/form-data" novalidate>
  13. {% csrf_token %}
  14. {# 一个页面显示2个校验表单 #}
  15. {# 添加文章的表单 #}
  16. {% for field in form_obj %}
  17. {# 注意这里不显示Article表的detail属性 #}
  18. {# field.name 标签对应的name属性的值 就是数据库中的字段的值 #}
  19. {% if field.name != 'detail' %}
  20. <div class="form-group {% if field.errors %}has-error{% endif %}">
  21. <label {% if not field.field.required %} style="color: #777777" {% endif %}
  22. for="{{ field.id_for_label }}"
  23. class="col-sm-2 control-label">{{ field.label }}</label>
  24. <div class="col-sm-8">
  25. {{ field }}
  26. <span class="help-block">{{ field.errors.0 }}</span>
  27. </div>
  28. </div>
  29. {% endif %}
  30. {% endfor %}
  31.  
  32. {# 添加文章详情的表单 #}
  33. {% for field in detail_form %}
  34.  
  35. <div class="form-group {% if field.errors %}has-error{% endif %}">
  36.  
  37. <div class="col-lg-offset-1 col-sm-10">
  38. {{ field }}
  39. <span class="help-block">{{ field.errors.0 }}</span>
  40. </div>
  41. </div>
  42. {% endfor %}
  43.  
  44. <button class="btn btn-success pull-right" style="margin-right: 244px">确认</button>
  45. </form>
  46. <div class="text-danger text-center"> {{ form_obj.non_field_errors.0 }} </div>
  47. <div class="text-danger text-center"> {{ detail_form.non_field_errors.0 }} </div>
  48.  
  49. </div>
  50.  
  51. </div>
  52.  
  53. {% endblock content %}
  54.  
  55. {% block js %}
  56. <script src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
  57. <script src="{% static 'ckeditor/ckeditor-init.js' %}"></script>
  58. {% endblock js %}

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

一个坑

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

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

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

添加功能

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

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

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

  1. # 添加文章有一个异常情况:
  2. # 文章详情的content中不输入数据也能通过校验!———— 所以必须判断一下content中是否有输入!
  3. def article_add(request):
  4. # 一个页面加两个form校验表单:文章与文章详情
  5. form_obj = forms.ArticleForm()
  6. detail_form = forms.ArticleDetailForm()
  7. title = '新增文章'
  8. if request.method == 'GET':
  9. return render(request,'article/article_form.html',{'title':title,'form_obj':form_obj,'detail_form':detail_form})
  10. elif request.method == 'POST':
  11. # 这里有个异常情况:文章详情的content中不输入数据也能通过校验!———— 所以必须判断一下content中是否有输入!
  12. if request.POST.get('content'):
  13. # 文章详情
  14. detail_form = forms.ArticleDetailForm(request.POST)
  15. if detail_form.is_valid():
  16. detail_form.save()
  17. # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
  18. # QueryDict需要copy()一下才能修改!
  19. qd = request.POST.copy()
  20. # 注意!键是Article表做外键关联的那个属性值;值是对应的ArticleDetail中的对应的对象的pk值~先用instance取出文章详情的对象
  21. qd['detail'] = detail_form.instance.pk
  22. print(qd)
  23. # 文章表中有数据和文件,文件需要用request.FILES获取
  24. # 注意~这里的数据应该是qd了!!!
  25. form_obj = forms.ArticleForm(data=qd,files=request.FILES)
  26. if form_obj.is_valid():
  27. # print(111111111111111111111111)
  28. form_obj.save()
  29. return redirect('backend:article_list')
  30.  
  31. # 这种情况是:文章详情添加成功但是文章添加失败了~需要把文章详情添加的数据删除了
  32. # 文章与文章详情的数据保持一致
  33. elif detail_form.is_valid() and detail_form.instance:
  34. detail_form.instance.delete()
  35. # print(222222222222222222)
  36.  
  37. return render(request, 'article/article_form.html',
  38. {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})
  39. else:
  40. return render(request, 'article/article_form.html',
  41. {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})

编辑功能

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

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

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

  1. # 编辑文章——如果用户上传了新的图片,应该把之前的图片删掉
  2. def article_edit(request,pk):
  3. # 一个页面显示两个表单
  4. # 文章对象
  5. article_obj = models.Article.objects.filter(pk=pk).first()
  6. # 提前将原文章中的图片对象取出来
  7. img_obj = article_obj.img
  8.  
  9. form_obj = forms.ArticleForm(instance=article_obj)
  10. # 基于对象的跨表查询
  11. detail_form = forms.ArticleDetailForm(instance=article_obj.detail)
  12. title = '编辑文章'
  13. if request.method == 'GET':
  14. return render(request,'article/article_form.html',{'title':title,'form_obj':form_obj,'detail_form':detail_form})
  15.  
  16. elif request.method == 'POST':
  17. # 文章详情
  18. detail_form =forms.ArticleDetailForm(data=request.POST,instance=article_obj.detail)
  19. if detail_form.is_valid():
  20. detail_form.save()
  21. # 文章的处理
  22. # 需要给文章表中加入一个“文章详情”的键值对!~因为前端没有添加文章详情id
  23. # QueryDict需要copy()一下才能修改!
  24. qd = request.POST.copy()
  25. qd['detail'] =detail_form.instance.pk
  26. # 注意:data应该是qd了!!!~~还得加上article_obj~否则成了添加了!
  27. form_obj = forms.ArticleForm(data=qd,files=request.FILES,instance=article_obj)
  28. if form_obj.is_valid():
  29. # 判断一下新传入的图片对象跟之前的是否相等
  30. # 如果没有传入图片就继续
  31. if not form_obj.cleaned_data.get('img'):
  32. pass
  33. if form_obj.cleaned_data.get('img') != img_obj:
  34. try:
  35. # 将之前的图片删除了
  36. os.remove(img_obj.path)
  37. except Exception as e:
  38. pass
  39. form_obj.save()
  40. return redirect('backend:article_list')
  41. return render(request, 'article/article_form.html',
  42. {'title': title, 'form_obj': form_obj, 'detail_form': detail_form})

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

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

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

~

Django的media配置与富文本编辑器使用的实例的更多相关文章

  1. Django 插件之 Xadmin实现富文本编辑器

    此文为前一篇文章的续写: Django 插件之 Xadmin Ueditor 介绍 UEditor 是由百度 web 前端研发部开发所见即所得富文本 web 编辑器,具有轻量,可定制,注重用户体验等特 ...

  2. Django学习---py3下的富文本编辑器的使用

    背景说明: Ueditor HTML编辑器是百度开源的HTML编辑器,但是在Python3下调用报错,找不到widgets模块,经查发现,DjangoUeditor是基于Python 2.7的,对Py ...

  3. django项目中使用KindEditor富文本编辑器。

    先从官网下载插件,放在static文件下 前端引入 <script type="text/javascript" src="/static/back/kindedi ...

  4. django项目中使用KindEditor富文本编辑器

    先从官网下载插件,放在static文件下 前端引入 <script type="text/javascript" src="/static/back/kindedi ...

  5. 配置KindEditor富文本编辑器

    第一步:首先我们要到KindEditor官网下载资源包-点击进入官网下载KindEditor资源包 第二部:在下载完了KindEditor的资源包后解压结构如下图所示: 里面包括集中语言的文件上传后台 ...

  6. django配置Ueditor富文本编辑器

    1.https://github.com/twz915/DjangoUeditor3下载包,进入包文件夹,找到DjangoUeditor包拷贝到项目下,和xadmin同级目录 2.找到项目的setti ...

  7. Django Admin后台使用tinymc 富文本编辑器

    1.CDN地址 <script src="//cdn.tinymce.com/4/tinymce.min.js"></script> 2.修改base.ht ...

  8. django2集成DjangoUeditor富文本编辑器

    富文本编辑器,在web开发中可以说是不可缺少的.django并没有自带富文本编辑器,因此我们需要自己集成,在这里推荐大家使用DjangoUeditor,因为DjangoUeditor封装了我们需要的一 ...

  9. MVC 使用 Ueditor富文本编辑器

    一.Ueditor 1.下载Ueditor富文本编辑器 官方下载地址: http://ueditor.baidu.com/website/download.html 建议下载开发版,此处我下载的是 . ...

随机推荐

  1. Widget代码讲解

    参考:https://zhuanlan.zhihu.com/p/28225011 QT版本为5.12.4 1.main.cpp #include "widget.h" #inclu ...

  2. P1162填涂颜色

    这还是一个搜索题,难度较低,但我提交第三次才AC.. 观察0地图左上角的上面和左面都是一,所以先把他找粗来,然后设成start,然后dfs找到与他联通的块,涂成2即可.再说一下自己犯的低级错误:1.当 ...

  3. php读取excel文件并导入数据库(表头任意设定)

    最近收到一个很奇葩的需求,要求上传excel员工工资表,表格表头不固定,导入后字段名为表头的拼音,每月导入一次,当月重复导入则覆盖现有的当月表头,并且可以按照在界面上按照月份筛选显示,我写的代码主要包 ...

  4. django后台集成富文本编辑器Tinymce的使用

    富文本编辑器Tinymce是使用步骤: 1.首先去python的模块包的网站下载一个django-tinymce的包 2.下载上图的安装包,然后解压,进入文件夹,执行: (pychrm直接运行命令pi ...

  5. web前后端数据交互

    前后端数据交互是每一名web程序员必须熟悉的过程,前后端的数据交互重点在于前端是如何获取后端返回的数据,毕竟后端一般情况下只需要将数据封装到一个jsonMap,然后return就完了.下面通过一个li ...

  6. ASP.NET的面包屑导航控件、树形导航控件、菜单控件

    原文:http://blog.csdn.net/pan_junbiao/article/details/8579293 ASP.NET的面包屑导航控件.树形导航控件.菜单控件. 1. 面包屑导航控件— ...

  7. 自我笔记,Rides介绍

    Redis是一个key-value存储系统,和Memccached类似,支持存储的value类型相对更多,很大程度上补偿memcached这类key-value存储的不足 他提供了Java,c/c++ ...

  8. vimdiff 可视化比较工具

    1.命令功能 vimdiff调用vim打开文件,可以同时打开2~4个文件,最多4个文件,且会以不同的颜色来区分文件的差异. 2.语法格式 vimdiff file1 file2 3.使用范例 [roo ...

  9. wepy-数据双向绑定input

    初入wepy,发现wepy和vue神似,但还是有不一样的地方,例如v-model数据双向绑定 场景: 一个input搜索框,用户输入内容,点击“叉叉”按钮,输入的内容全部清空,这是一个很常见的场景 j ...

  10. 《SaltStack技术入门与实践》—— Event和Reactor系统

    Event和Reactor系统 本章节参考<SaltStack技术入门与实践>,感谢该书作者: 刘继伟.沈灿.赵舜东 Event是SaltStack里面的对每个事件的一个记录,它相比job ...