Django中间件之process_view

  • 执行顺序: 在 urls.py 的对应关系之后,在执行视图函数之前执行

  • 如果返回 None,则继续执行后面的中间件的 process_view 函数

  • 如果返回 HttpResponse,则不执行后续的 process_view 函数,直接跳到第一个 process_response 函数执行

### 中间件.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
 
 
class Test(MiddlewareMixin):
    def process_request(self, request):
        print("这是一个中间件 --> test")
 
    def process_response(self, request, response):
        print("这里是 Test 的 HttpResponse")
        return HttpResponse("这是 Test 返回的 HttpResponse")
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        '''
        :param request: 浏览器发来的 request 请求对象
        :param view_func: 将要执行的视图函数的名字
        :param view_args: 将要执行的视图函数的位置参数
        :param view_kwargs: 将要执行的视图函数的关键字参数
        :return:
        '''
        print("这里是 Test 的 process_view 函数")
        print(view_func, type(view_func))


### views
from django.shortcuts import HttpResponse
def index(request):
    print("这里是 index 页面")
    return HttpResponse("这里是主页面 index")


- 执行顺序: 

    1.首先执行 process_request 函数
    2.然后在执行视图函数之前执行 process_view 函数,
    3.然后执行视图函数
    4.最后执行 process_response 函数

- 上述代码执行结果:

    "这是一个中间件 --> test" (process_request)
    "这里是 Test 的 process_view 函数" (process_view)
    "这里是 index 页面"(view视图)
    "这里是 Test 的 HttpResponse"(process_response)

  • 其他说明: 参考网址
    https://www.cnblogs.com/iiiiiher/p/13864054.html
- process_request 只返回 None,都执行完之后,就匹配路由,找到要执行的视图函数
  在执行视图函数之前先执行中间件的 process_view 函数

- 如果 process_view 返回 None,就继续执行后续的中间件的 process_view 方法
  执行完所有的 process_view 函数之后就执行视图函数

- 如果其中有个 process_view 返回了 HttpResponse,就不执行后续的 process_view 函数
  会跳到第一个 process_response 函数,并继续往下执行
 - 如果有 process_request 函数返回了 HttpResponse 对象
   就不执行后续的 process_request 函数,也不执行 process_view 函数
   直接跳转到相应的 process_response 函数执行

拆分Django的settings.py配置文件,使线下开发和线上部署更方便

参考网址: https://zhuanlan.zhihu.com/p/67984033#:~:text=4.%E7%9B%B8%E5%85%B3%E9%85%8D%E7%BD%AE%201%204.1%20%E4%BF%AE%E6%94%B9manage.py%20manage.py%20%E5%BA%94%E8%AF%A5%E6%98%AF%E6%88%91%E4%BB%AC%E5%88%9D%E5%AD%A6%20django%20%E6%97%B6%E5%B0%B1%E4%BC%9A%E4%BD%BF%E7%94%A8%E5%88%B0%E7%9A%84%E4%B8%80%E4%B8%AA%E5%B7%A5%E5%85%B7%E6%96%87%E4%BB%B6%EF%BC%8C%E4%BD%BF%E7%94%A8%E5%AE%83%E6%88%91%E4%BB%AC%E5%8F%AF%E4%BB%A5%E5%90%AF%E5%8A%A8%E9%A1%B9%E7%9B%AE%EF%BC%8C%E5%88%9B%E5%BB%BA,4.4%20%E4%BF%AE%E6%94%B9Pycharm%20%E5%A6%82%E6%9E%9C%E4%BD%A0%E7%94%A8%E7%9A%84%E6%98%AF%20Pycharm%20%E8%BF%9B%E8%A1%8C%20Django%20%E5%BC%80%E5%8F%91%E7%9A%84%E8%AF%9D%E9%9C%80%E8%A6%81%E8%BF%9B%E8%A1%8C%E6%AD%A4%E9%A1%B9%E4%BF%AE%E6%94%B9%E3%80%82%20

  • 为什么要拆分

    • 就像本文标题一样,对Django的配置文件Settings.py进行拆分的主要目的就是为了使线下开发和线上开发配合得更加方便
      以我的个人网站(www.eastnotes.com)举例,在本地进行开发时的一些配置,比如数据库配置信息,和远程部署服务器上的配置是不一样的
      另外,本地开发所单独需要加载的某些APP(比如django-debug-toolbar),在部署服务器上就不需要加载

    • 在拆分之前,我的解决方案是将djangoblog/settings.py文件写入.gitignore配置文件中
      这样在版本控制的时候,Git就会忽略对这个文件的跟踪,从而保持本地配置和远程配置的不一样
      但这样做的缺点就是,每当我需要更改配置文件的时候,就需要改两次,非常麻烦。
      通过对配置文件的拆分,就能大大减轻这种不便,接下来看具体操作

  • 拆分后的效果

├── settings
│   ├── __init__.py
│   ├── base.py
│   ├── local.py
│   └── production.py

- 我将原始settings.py文件拆分成了3个文件,base.py存放在本地开发和线上部署所共同的配置信息
  local.py存放本地开发所特有的数据库配置等信息,production.py存放线上部署所特有的数据库配置等信息
  __init__.py是一个空文件,作用是使seetings文件夹成为一个包文件。以便使用import语句。
  • 拆分步骤

    • 创建文件

      • 在原始settings.py配置文件的同级项目下创建一个settings文件夹,然后在该文件夹下创建四个.py文件,命名参考上面一段内容
        注意,一定要创建一个空的__init__.py文件.否则后期进行导入操作的时候一定会遇到如下的报错:
ModuleNotFoundError: No module named 'djangoblog.settings.local'; 'djangoblog.settings' is not a package
- 内容拆分

    - 内容的拆分需要你从settings.py文件中进行筛选,将线上线下所共同需要的配置放入base.py中
      将线下独有的配置放入到local.py中,将线上所独有的配置放入production.py中
  • 内容导入

    • 要想使local.py和production.py都能使用到base.py文件中的配置,我们需要在这两个文件的顶部写入import语句:
from .base import *
  • 以上就完成了内容上面的拆分,但仅仅拆分完是不够的,此时django还不能自动识别并使用这些配置文件,我们还需要接下来的相关配置

  • 相关配置

    • 修改manage.py,manage.py应该是我们初学django时就会使用到的一个工具文件,使用它我们可以启动项目,创建APP,创建超级用户等操作。
      当我们使用它启动项目的时候,它会自动加载文件中的一个配置:DJANGO_SETTINGS_MODULE,该配置指定了配置文件的所在位置
      由于我们更改了配置文件,因此也一定要更改它才能正常启动Django,你需要将djangoblog换成你的项目名即可
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoblog.settings.local')
  • 修改BASE_DIR

    • 在base.py文件中有一个配置文件路径的选项,拆分之前的代码如下:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- 这样设置的BASE_DIR的值是/Users/reborn/PycharmProjects/djangoblog,也就是说指向的是项目的根目录
  但是拆分之后,该值所指向的是/Users/reborn/PycharmProjects/djangoblog/djangoblog
  如果不修改,django会找不到使用了这个路径的文件,比如模板文件,会报出下面的错误:
django.template.exceptions.TemplateDoesNotExist: base.html
- 因此,我们只需要将此目录指向上一级目录就可以了,使用join完成:
BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "..")
  • 修改wsgi.py

    • 修改这个文件的需求是我在部署时才发现的,在完成上面的步骤之后,我就通过git更新了服务器上的配置文件,
      重启nginx和uwsgi之后,网站依然打不开。使用python manage.py runserver之后,项目能正常启动,但是网站依然打不开
      于是我突然想到应该是uwsgi找不到我的配置文件,因为线上部署的话是通过uwsgi来启动我们的项目的。
      打开djangoblog下的wsgi.py文件后,不出我所料,这里面同样有一个有关DJANGO_SETTINGS_MODULE的配置
      因此我将它指向我的production.py配置文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoblog.settings.production')
- 此时,再打开网站就没有问题了
  • 修改Pycharm

  • 如果你用的是Pycharm进行Django开发的话需要进行此项修改。每次使用Pycharm运行项目的时候,它都会默认加载一个自带的配置
    这个配置在Pycharm运行按钮的旁边,下拉菜单中有一个Edit Configurations,点击之后修改里面的Environment variables
    目的也是让Pycharm能够找到我们的本地配置文件:

PYTHONUNBUFFERED=1;DJANGO_SETTINGS_MODULE=djangoblog.settings.local
- 至此,对Django配置文件的拆分工作已经完成,你可以删除原来的settings.py文件了

跨域,只针对浏览器才有这种问题

  • 比如,我们调用短信接口的时候,为什么不会有跨域问题?因为不需要浏览器...

  • CORS(跨域资源共享),django配置

- pip install django-cors-headers

INSTALLED_APPS = [
    ......
    'corsheaders', # 位置任意
]


MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # 一定要放在最上面(每个响应都会涉及到'跨域')
    'django.middleware.security.SecurityMiddleware',
    ......
]

# CORS  白名单
CORS_ORIGIN_WHITELIST = (
    'http://127.0.0.1:8080',
    'http://localhost:8080',
    'http://www.meiduo.site:8080',
    'http://www.meiduo.site:8000'
)
CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie

django自带admin后台,展示页显示'缩略图'

  • 现有需求,有个 CharField 字段存储的是 url地址
    在admin后台展示为url地址,并不是图片,用户当然期望是图片.
    如果想显示图片,可以这么搞
# models
class WXUserInfo(models.Model):
    nick_name = models.CharField(max_length=64, verbose_name='昵称')
    avatar_url = models.CharField(max_length=256, verbose_name='头像')
    token = models.CharField(max_length=128, null=True, blank=True, verbose_name='认证')
    create_time = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name='创建时间')

    def __str__(self):
        return self.nick_name

    class Meta:
        verbose_name = '微信用户信息'
        verbose_name_plural = '微信用户信息'

    def avatar_url_data(self): # 自定义展示字段(不会被写入db)
        if self.avatar_url:
            return format_html( # 返回img标签即可
                '<img src="{}" width="156px" height="98px" />',
                self.avatar_url
            )

    avatar_url_data.short_description = '用户头像' # 字段名称
......

from django.contrib import admin
from .models import WXUserInfo


@admin.register(WXUserInfo)
class WXUserInfoAdmin(admin.ModelAdmin): # 展示自定义字段
    list_display = ('id','nick_name', 'avatar_url_data', 'token', 'create_time')
    ......

django admin模糊查询db错误处理

  • 异常
MySQLdb._exceptions.OperationalError: (1271, "Illegal mix of collations for operation 'like'")
  • 参考网址

https://blog.csdn.net/qq_24036403/article/details/96489189

- 找到python目录下的 django 安装路径:在该路径下打开 base.py

    - D:\Python\django\projects\izone\venv\Lib\site-packages\django\db\backends\mysql

    - 修改源码如下:

        operators = {
            ......
            # 'icontains': 'LIKE %s', # 中文模糊查询会报错
            'icontains':  'LIKE BINARY %s',
           ......
        }

导包路径引发的异常

......declare an explicit app_label and isn't in an application in INSTALLED_APPS
  • 参考网址

https://www.cnblogs.com/lewangchen/p/15049778.html

  • 比较隐蔽的错误点(对调试试,一般就不会再报错了)
......
class UserpageConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'userPage' # 出错点在这里

class UserpageConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'Apps.userPage' # 出错点在这里

返回json注意事项

  • 参考网址

https://www.cnblogs.com/songhaixing/p/14583239.html

  • 中文乱码问题
import json

def test(resquest):
    user_info = {"name":"shawn","age":23,"sex":"male","hobby":"吃饭"}
    # 添加 ensure_ascii=False 参数是为了让中文保持正常显示, 不然会转换成uncode格式
    data = json.dumps(user_info,ensure_ascii=False)
    return HttpResponse(data)
  • 使用 JsonResponse
from django.http import JsonResponse
def test1(request):
    ll = [1,2,3,43,4]
    # 加入 safe=False 参数, 让其允许非 dict 对象被序列化
    return JsonResponse(ll, safe=False,json_dumps_params={'ensure_ascii':False})

表单上传文件

  • form表单上传文件注意事项

    • method 必须指定 post 提交
    • enctype 指定属性值 : multipart/form-data
### 前端
<body>
    <div class="container">
        <div class="col-md-8 col-md-offset-2">
            <!--指定post,enctype-->
            <form action="" method="post" enctype="multipart/form-data">
                <input type="file" class="form-control" name="myfile">
                <input type="submit" class="btn btn-warning btn-block" value="提交">
            </form>
        </div>
    </div>
</body>

### views
def form_test(request):
    if request.method == "POST":
        # 从文件对象字典中获取名为"myfile"的文件对象
        file_obj = request.FILES.get('myfile')  
        file_name = file_obj.name
        # 保存方式一:
        with open(f'./{file_name}',"wb")as f:
            for line in file_obj:
                f.write(line)
        # 保存方式二:(官方推荐)
        with open(f'./{file_name}',"wb")as f:
            for line in file_obj.chunks():
                f.write(line)

    return render(request,'test.html')

class Meta嵌套类

  • 作用: 做为嵌套类,主要目的是给上级类添加一些功能,或指定一些标准
    比如第一个abstract=True这个东东,是为了继承,将该基类定义为抽象类,即不必生成数据库表单,只作为一个可以继承的基类,把一些子类必须的代码放在基类,避免重复代码也避免重复录入数据库。大概是这么个意思吧?

   再比如db_table='xxxx'这个东东更简单些,其实就是指定该类的数据库表单名字。当然如果不指定也没关系,Django会自动默认的按照一定规则生成数据模型对应的数据库表名。至于合不合你的意那就得看缘分了,所以自己指定往往比较好。

    又比如ordering=‘xxxxx’,是表示按照指定的字段进行数据库的排序。主要是为了好看好查找。你可以指定任意的表单名称或内容,数据库生成之后就会按照指定的列进行排序。还可以升序降序随机,唉反正挺复杂的。

    其实以上三种Django中model嵌套类的元数据定义,都涉及不少的知识点且有多种选择和用法。甚至这种嵌套类中可以有十几种不同的元数据定义方法,真要研究下去晕都晕死了。还好没有一个选项是必需的. 是否添加class Meta 到你的 model 完全是可选的。以后用到什么再查吧,太复杂了。我学习的不好,尽量遇到什么就搞懂什么吧
————————————————

原文链接:https://blog.csdn.net/qq_41856814/article/details/89002456

django数据模型中关于on_delete的使用

  • 参考网址
https://blog.csdn.net/kuangshp128/article/details/78946316
  • on_delete属性介绍
- CASCADE:这就是默认的选项,级联删除,你无需显性指定它

- PROTECT: 保护模式,如果采用该选项,删除的时候,会抛出ProtectedError错误

- SET_NULL: 置空模式,删除的时候,外键字段被设置为空,前提就是blank=True, null=True,定义该字段的时候,允许为空

- SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值,所以定义外键的时候注意加上一个默认值

- SET(): 自定义一个值,该值当然只能是对应的实体了

  • 补充说明:关于SET()的使用
# 官方案例
def get_sentinel_user():
    return get_user_model().objects.get_or_create(username='deleted')[0]

class MyModel(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET(get_sentinel_user),
    )

Meta配置项介绍

  • 参考网址: https://www.cnblogs.com/hunterxiong/p/17255793.html
  • get_latest_by: 指定字段 field_name,系统会返回 TestModel 按照字段名为 field_name 排序的最新的一条数据
    • 等价于: TestModel.objects.order_by('name').last()
class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()
    
    class Meta:
        get_latest_by = "name" # 以 name 字段进行排序

- 使用: Blog.objects.latest() # 返回以name排序的最新一条数据
  • managed: 配置model 的每次更改时,使用 makemigrations 的时候是否检测到
- 默认为True: 对 model 的每次更改,都会在 makemigrations 的时候被检测到
- False: 表示 Django 在 makemigrations 的时候会忽略检测这张表,常用在仅用于系统查询的表
class Blog(models.Model):
	pass
	
	class Meta:
		managed = True # 默认值