Django 4 笔记 - 4.访问数据库

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run ‘python manage.py migrate’ to apply them.

前面开箱介绍时我忽略了上面这段提示。产生这段提示的原因是 django 自带一些需要使用数据库的应用,例如 auth、admin、sessions 等等,在配置数据库并完成初始化之前,这些应用不能正确工作。

Django 支持的数据库版本

  • MySQL 5.7 及以上
  • MariaDB 10.3 及以上
  • PostgreSQL 11 及以上

配置数据库连接并初始化

  1. 使用 MySQL,提前建好库和用户,配置好用户权限。
  2. 修改 mygarage/settings.py 中的 DATABASES 配置:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'garagedb',
        'USER': 'garageusr',
        'PASSWORD': 'garagepwd',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}
  1. 安装 mysqlclient。
pip install mysqlclient
  1. 库表初始化
(.djenv) PS C:\django-projects\mygarage> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK        
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK 
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK   
  Applying auth.0005_alter_user_last_login_null... OK 
  Applying auth.0006_require_contenttypes_0002... OK       
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK     
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

创建一个使用数据库的应用

  1. 创建应用 cars
python manage.py startapp cars
  1. 修改 cars/models.py
from django.db import models


class Car(models.Model):
    class Classification(models.IntegerChoices):
        SEDAN = 1
        SUV = 2
        MPV = 3
        OFFROADER = 4
        SPORTS = 5
        OTHERS = 6

    brand = models.CharField(max_length=20)
    model = models.CharField(max_length=20)
    classification = models.PositiveSmallIntegerField(choices=Classification.choices)

    class Meta:
        # 唯一索引
        unique_together = ['brand', 'model']

        # 排序
        ordering = ['brand', 'model']

    # 对象显示名称
    def __str__(self):
        return self.brand + ' ' + self.model
  1. 修改 cars/views.py
from django.http import JsonResponse, QueryDict
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from .models import Car


@method_decorator(csrf_exempt, name='dispatch')
class MyCars(View):
    def get(self, request):
        '''
        获取所有车辆列表
        '''
        cars = Car.objects.all()

        data = []
        for car in cars:
            data.append({
                'id': car.pk,
                'brand': car.brand,
                'model': car.model,
                'class': car.classification
            })

        return JsonResponse({'cars': data})
    

    def post(self, request):
        '''
        添加车辆
        '''
        car = Car()

        # 未检查参数完整性与合法性
        car.brand = request.POST['brand']
        car.model = request.POST['model']
        car.classification = request.POST['class']

        try:
            # 检查参数合法性
            car.full_clean()
        except ValidationError as e:
            return JsonResponse({'error': e.message_dict})

        car.save()

        data = {
            'id': car.pk,
            'brand': car.brand,
            'model': car.model,
            'class': car.classification
        }
        return JsonResponse({'car': data})


class MyCar(View):
    def get(self, request, id):
        '''
        按ID查询车辆
        '''
        try:
            car = Car.objects.get(pk=id)
        except Car.DoesNotExist:
            return JsonResponse({'car': None})

        data = {
            'id': car.pk,
            'brand': car.brand,
            'model': car.model,
            'class': car.classification
        }
        return JsonResponse({'car': data})


    def delete(self, request, id):
        '''
        按ID删除车辆
        '''
        Car.objects.filter(pk=id).delete()
        return JsonResponse({'message': 'done'})
  1. 创建 cars/urls.py,修改 mygarage/urls.py
# cars/urls.py
from django.urls import path
from .views import MyCars, MyCar

urlpatterns = [
    path('', MyCars.as_view()),
    path('<int:id>/', MyCar.as_view()),
]
# mygarage/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('bicycle/', include('bicycle.urls')),
    path('cars/', include('cars.urls'))
]
  1. 修改 garage/settings.py ,安装 cars
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bicycle',
    'cars',
]
  1. 生成数据库脚本并创建库表:
(.djenv) PS C:\django-projects\mygarage> python manage.py makemigrations cars
Migrations for 'cars':
  cars\migrations\0001_initial.py
    - Create model Car
(.djenv) PS C:\django-projects\mygarage> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, cars, contenttypes, sessions
Running migrations:
  Applying cars.0001_initial... OK
  1. 运行服务,调用情况如下:
C:\>curl -X POST http://127.0.0.1:8000/cars/ -d "brand=Volvo&model=XC90&class=2"
{"car": {"id": 1, "brand": "Volvo", "model": "XC90", "class": 2}}
C:\>curl -X POST http://127.0.0.1:8000/cars/ -d "brand=Volvo&model=S60&class=1"
{"car": {"id": 2, "brand": "Volvo", "model": "S60", "class": 1}}
C:\>curl -X POST http://127.0.0.1:8000/cars/ -d "brand=Benz&model=CLS&class=1"
{"car": {"id": 3, "brand": "Benz", "model": "CLS", "class": 1}}
C:\>curl -X POST http://127.0.0.1:8000/cars/ -d "brand=Audi&model=TT&class=5"
{"car": {"id": 4, "brand": "Audi", "model": "TT", "class": 5}}
C:\>curl -X POST http://127.0.0.1:8000/cars/ -d "brand=Volvo&model=XC90&class=8"
{"error": {"classification": ["Value 8 is not a valid choice."], "__all__": ["Car with this Brand and Model already exists."]}}
C:\>curl -X GET http://127.0.0.1:8000/cars/
{"cars": [{"id": 4, "brand": "Audi", "model": "TT", "class": 5}, {"id": 3, "brand": "Benz", "model": "CLS", "class": 1}, {"id": 2, "brand": "Volvo", "model": "S60", "class": 1}, {"id": 1, "brand": "Volvo", "model": "XC90", "class": 2}]}
C:\>curl -X GET http://127.0.0.1:8000/cars/3/
{"car": {"id": 3, "brand": "Benz", "model": "CLS", "class": 1}}
C:\>curl -X GET http://127.0.0.1:8000/cars/9/
{"car": null}
C:\>curl -X DELETE http://127.0.0.1:8000/cars/3/
{"message": "done"}
C:\>curl -X GET http://127.0.0.1:8000/cars/
{"cars": [{"id": 4, "brand": "Audi", "model": "TT", "class": 5}, {"id": 2, "brand": "Volvo", "model": "S60", "class": 1}, {"id": 1, "brand": "Volvo", "model": "XC90", "class": 2}]}

修改数据模型

  1. 修改 cars/models.py,增加 color、created_time、updated_time 字段。
from django.db import models


class Car(models.Model):
    class Classification(models.IntegerChoices):
        SEDAN = 1
        SUV = 2
        MPV = 3
        OFFROADER = 4
        SPORTS = 5
        OTHERS = 6

    brand = models.CharField(max_length=20)
    model = models.CharField(max_length=20)
    classification = models.PositiveSmallIntegerField(choices=Classification.choices)
    color = models.CharField(max_length=20, blank=True)
    created_time = models.DateTimeField(auto_now_add=True, db_index=True)
    updated_time = models.DateTimeField(auto_now=True)

    class Meta:
        # 唯一索引
        unique_together = ['brand', 'model']

        # 排序
        ordering = ['brand', 'model']

    # 对象显示名称
    def __str__(self):
        return self.brand + ' ' + self.model
  1. 更新数据库脚本,并向数据库同步。
    由于新增的 created_time 没有默认值,所以要给数据库中已经存在的记录指定默认值,选 1 然后回车,使用 Python 的 timezone.now 即可。
(.djenv) PS C:\django-projects\mygarage> python manage.py makemigrations cars
It is impossible to add the field 'created_time' with 'auto_now_add=True' to car without providing a default. This is because the database needs something to populate existing rows.
 1) Provide a one-off default now which will be set on all existing rows
 2) Quit and manually define a default value in models.py.
Please enter the default value as valid Python.
Accept the default 'timezone.now' by pressing 'Enter' or provide another value.
The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
[default: timezone.now] >>>
Migrations for 'cars':
  cars\migrations\0002_car_color_car_created_time_car_updated_time.py
    - Add field color to car
    - Add field created_time to car
    - Add field updated_time to car
(.djenv) PS C:\django-projects\mygarage> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, cars, contenttypes, sessions
Running migrations:
  Applying cars.0002_car_color_car_created_time_car_updated_time... OK
  1. 修改 cars/views.py,增加修改车辆颜色的接口。
...

@method_decorator(csrf_exempt, name='dispatch')
class MyCars(View):
    def get(self, request):
        ...    

    def post(self, request):
        ...


@method_decorator(csrf_exempt, name='dispatch')
class MyCar(View):
    def get(self, request, id):
        ...    

    def delete(self, request, id):
        ...    

    def put(self, request, id):
        '''
        按ID修改车辆颜色
        '''
        try:
            car = Car.objects.get(pk=id)
        except Car.DoesNotExist:
            return JsonResponse({'car': None})

        car.color = QueryDict(request.body)['color']

        try:
            car.full_clean()
        except ValidationError as e:
            return JsonResponse({'error': e.messages})

        car.save()

        data = {
            'id': car.pk,
            'brand': car.brand,
            'model': car.model,
            'class': car.classification,
            'color': car.color
        }
        return JsonResponse({'car': data})
  1. 运行服务,调用情况如下:
C:\>curl -X PUT http://127.0.0.1:8000/cars/1/ -d "color=black"
{"car": {"id": 1, "brand": "Volvo", "model": "XC90", "class": 2, "color": "black"}}

撤销数据库修改

每一次模型修改都会产生新的迁移文件(保存在 migrations 目录中,文件名以数字序号开头),可以执行 migrate 命令将数据库回溯到某次迁移的状态,或撤销所有迁移。

  • cars 应用的数据库回溯到 0001 号迁移完成时的状态
python manage.py migrate cars 0001
  • 撤销 cars 应用的所有迁移
python manage.py migrate cars zero

重新生成全部数据库脚本和库表

  • 有时我们想要完全重新生成某个应用的数据库脚本和库表(通常在首个版本的开发阶段):
  1. python manage.py migrate <app> zero (或者删除 django_migrations 表里面的相关记录)
  2. 删除 <app>/migrations 目录下除 __init__.py 的其他文件
  3. python manage.py makemigrations <app>
  4. python manage.py migrate <app>

  • 如果想全部重来:
  1. drop database
  2. 删除所有应用 <app>/migrations 目录下除 __init__.py 的其他文件
  3. python manage.py makemigrations
  4. python manage.py migrate

ModelForm

为了聚焦于 Model 的使用,前面的例子没有使用表单对视图接收到的请求参数进行验证,但更好的方法是使用 ModelForm 对参数进行验证。

  1. 创建 cars/forms.py

强烈建议您使用 fields 属性来显式设置所有应在表单中编辑的字段。如果不这样做,当一张表单不慎允许用户设置某些字段,尤其是在将新字段添加到模型中时,很容易导致安全问题。

from django.forms import ModelForm
from .models import Car

class CarForm(ModelForm):
    class Meta:
        model = Car
        fields = ['brand', 'model', 'classification', 'color']
  1. 修改 cars/views.py 中的 MyCars.post 方法(类似的,也应该对 MyCar.put 进行修改) :
...
from .forms import CarForm


@method_decorator(csrf_exempt, name='dispatch')
class MyCars(View):
    def get(self, request):
        ...    

    def post(self, request):
        '''
        添加车辆
        '''
        form = CarForm(request.POST)

        # is_valid() 将对表单中包含的所有字段调用模型验证
        if form.is_valid():
            newcar = form.save()
            data = {
                'id': newcar.pk,
                'brand': newcar.brand,
                'model': newcar.model,
                'class': newcar.classification,
                'color': newcar.color
            }
            return JsonResponse({'car': data})
        
        else:
            return JsonResponse({'error': form.errors})

...

代理模型

一般不会用到代理模型,不过还是在这里记录一下。完整文档在这里

使用 多表继承 时,每个子类模型都会创建一张新表。这一般是期望的行为,因为子类需要一个地方存储基类中不存在的额外数据字段。不过,有时候你只想修改模型的 Python 级行为——可能是修改默认管理器,或添加一个方法。

代理模型就像普通模型一样申明。你需要告诉 Django 这是一个代理模型,通过将 Meta 类的 proxy 属性设置为 True

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

MyPerson 类与父类 Person 操作 同一张数据表。特别提醒, Person 的实例能通过 MyPerson 访问,反之亦然。

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

可以用代理模型定义模型的另一种不同的默认排序方法。普通的 Person 查询结果不会被排序,但 OrderdPerson 查询接轨会按 last_name 排序。

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

模型层文档

模型层非常重要,通常不用编写 SQL 访问数据库,不过 Django 也提供了编写 raw SQL 的方式。阅读下面内容了解更多:

模型: 模型介绍 | 字段类型 | 索引 | Meta 选项 | Model 类
QuerySet: 执行查询 | QuerySet 方法参考 | 查询表达式
模型实例: 实例方法 | 访问关联的对象
迁移: 迁移概述 | 操作参考 | SchemaEditor | 编写迁移
高级: 管理器 | 原始 SQL | 事务 | 聚合 | 搜索 | 自定义字段 | 多个数据库 | 自定义查询 | 查询表达式 | 条件表达式 | 数据库函数
其它: 支持的数据库 | 旧数据库 | 提供初始化数据 | 优化数据库访问 | PostgreSQL 的特有功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值