提供有关 Web 应用程序的建模和设计的一些很好的见解。 Web 开发和一般的软件开发不仅仅是编码。
                 爱校码
实体是将要创建的模型,它与 Django 应用程序将处理的数据密切相关。考虑到城市景点的项目案例,至少需要实现以下模型:City、Spot和 User。

思考模型如何相互关联很重要。实线告诉我们的是,在一个城市中,需要一个字段来识别它。同样,景点需要一个字段来表示它属于哪个城市,以便我们可以列出在特定城市中创建的景点。最后,我们将需要城市中的字段来知道谁创建了城市和景点中的字段,以便我们可以识别用户操作。
还可以与 City、Spot和 User 模型建立关联,这样就可以确定谁创建了给定的 City与Spot。
现在有了基本的类表示,必须考虑每个模型将携带什么样的信息。开始开发所需的信息。稍后,我们可以使用迁移改进模型。
花时间思考模型如何相互关联也很重要。 实线中,在一个City中,需要字段来识别它。 类似地,Spot需要字段来识别,这将是我们模型字段的基本表示:

这个类图强调模型之间的关系。 这些关系线条最终将转换为字段。
对于 City 模型,将从两个字段开始:名称和描述。名称字段必须是唯一的,以避免重复的城市名。该描述只是为了进一步说明其信息内容。
Spot模型也由两个字段组成,景点将用于定义一个名为city的字段,用于定义特定景点属于哪个城市。
最后是User模型。在类图中,只提到了用户名、密码、电子邮件和超级用户标志字段,因为这几乎就是将要使用的全部内容。需要注意的是,这里不需要创建 User 模型,因为 Django 在 contrib 包中已经内置了 User 模型。我们将要使用它。
关于类图中的多重性(数字 1、0..* 等),可以这样解读:
类图中的城市和景点关联,一个景点必须与一个城市相关联(这意味着它不能为空),并且一个城市可以与许多景点相关联(1..*)。
一个城市必须有一个且只有一个用户关联: 一个用户可以创建很多城市(*)。同样,一个景点必须有一个用户关联:由用户创建。 一个用户可能创建多个景点(*)。 Cityt和 User 之间的关联以及Spot和 User 之间的关联是直接关联,这意味着针对 User 编辑给定 Spot 或者City的关系,它将是更新的字段。 多重性表示 (1),这意味着字段最多可能仅与一个用户相关联。
模型基本上代表了应用程序的数据库布局。 现在要做的是创建前面类图建模的类表示:City、Spot。 User 模型已经在名为 auth 的内置应用程序中定义,该应用程序在命名空间 django.contrib.auth 下的 INSTALLED_APPS配置中列出。同时需要在MIDDLEWARE中列出相关的中间件:
settings.configure(
   ...
   INSTALLED_APPS = [
       'django.contrib.admin',
       'django.contrib.auth',
       'django.contrib.contenttypes',
       'django.contrib.sessions',
       'django.contrib.messages',
       'django.contrib.staticfiles',
       'widget_tweaks',
       'cityspot',
   ],
   MIDDLEWARE=[
       'django.middleware.security.SecurityMiddleware',
       'django.contrib.sessions.middleware.SessionMiddleware',
       'django.middleware.common.CommonMiddleware',
       'django.middleware.csrf.CsrfViewMiddleware',
       'django.contrib.auth.middleware.AuthenticationMiddleware',
       'django.contrib.messages.middleware.MessageMiddleware',
       'django.middleware.clickjacking.XFrameOptionsMiddleware',
   ],
   ...
)
在 cityspot/models.py文件中完成所有工作。 下面为类图设计程序代码 :
cityspot/models.py
from django.db import models
from django.contrib.auth.models import User
class City(models.Model):
    name = models.CharField(max_length=30, unique=True)
    description = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(null=True)
    created_by = models.ForeignKey(User, related_name='citys',on_delete=models.CASCADE)
    updated_by = models.ForeignKey(User, null=True, related_name='+',on_delete=models.DO_NOTHING)
    def __str__(self):
        return self.name
    def to_json(self):
        json_city = {
            'id': self.id,
            'name': self.name,
            'description': self.description
        }
        return json_city
class Spot(models.Model):
    name = models.CharField(max_length=30, unique=True)
    description = models.CharField(max_length=100)  
    city = models.ForeignKey(City, related_name='spots',on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(null=True)
    created_by = models.ForeignKey(User, related_name='spots',on_delete=models.CASCADE)
    updated_by = models.ForeignKey(User, null=True, related_name='+',on_delete=models.DO_NOTHING)
    def __str__(self):
        return self.name
    def to_json(self):
        json_spot = {
            'id': self.id,
            'name': self.name,
            'description': self.description,
            'city': self.city.to_json()
        }
        return json_spot
所有模型都是  django.db.models.Model 类的子类。 每个类都将转换为数据库表。 每个字段由 django.db.models.Field 子类(内置 Django 核心)的实例表示,并将被转换为数据库字段。
CharField、DateTimeField 等字段都是 django.db.models.Field 的子类,它们包含在 Django 核心中随时准备可用。
这里只使用 CharField、DateTimeField 和 ForeignKey 字段来定义我们的模型。 但是 Django 提供了广泛的选项来表示不同类型的数据, 将根据需要引用它们。
某些字段具有必需的参数,例如 CharField。 应该始终设置一个 max_length。 此信息将用于创建数据库列。 Django 需要知道数据库列需要多大。 Django Forms API 也将使用 max_length 参数来验证用户输入。
在 City模型定义中,更具体地说,在 name 字段中,还设置了参数 unique=True,顾名思义,它将在数据库级别强制该字段的唯一性。
在 City和Spot 模型中, created_at 字段有一个可选参数, auto_now_add 设置为 True。 这将指示 Django 在创建 City或Spot对象时设置当前日期和时间。
在模型之间创建关系的一种方法是使用 ForeignKey 字段。 它将在模型之间创建链接并在数据库级别创建适当主-外键的关系。ForeignKey字段需要一个位置参数,其中包含对其相关模型的引用。
例如,在 Spot模型中,city 字段是 City 模型的 ForeignKey。 它告诉 Django 一个 Spot 实例只与一个 City实例相关。 related_name 参数将用于创建反向关系,其中 City实例将有权访问属于它的 Spot 实例列表。
Django 自动创建这种反向关系——related_name 是可选的。 但是如果我们不为它设置名称,Django 将使用名称生成它:(class_name)_set。 例如,在 City 模型中,Spot 实例将在 spot_set 属性下可用,作为代替,我们只是将其重命名为spots,以使其感觉更自然。
在 Spot 模型中,updated_by 字段设置了related_name='+'。 这告诉 Django 我们不需要这种反向关系,所以会忽略它。
在以上的模型设计中,我们并没有为模型指定主键,Django 会自动为我们生成它。
下一步是告诉 Django 创建数据库表,方便我们使用它。
打开并激活虚拟环境,进入城市景点项目案例所在的文件夹,运行以下命令:
python scenic_spot.py makemigrations
作为输出,将得到如下内容:
Migrations for 'cityspot':
  cityspot/migrations/0001_initial.py
    - Create model City
    - Create model Spot
此时,Django 在 cityspot/migrations 目录中创建了一个名为 0001_initial.py的文件。 它代表我们应用程序模型的当前状态。 在下一步中,Django 将使用这个文件来创建表和列。
迁移文件被翻译成 SQL 语句,所有工作都将使用 Django ORM 完成,这是一个与数据库通信的抽象层。现在下一步是将生成的迁移应用到数据库:
python scenic_spot.py migrate
输出应该是下列这样的内容:
Operations to perform:
  Apply all migrations: admin, auth, cityspot, 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 cityspot.0001_initial... OK
  Applying sessions.0001_initial... OK
如果这是第一次迁移数据库,所以 migrate 命令还应用了来自 Django contrib 应用程序的现有迁移文件,在 INSTALLED_APPS中列出。 这是意料之中的。
Applying cityspot.0001_initial... OK 这行是我们在前一步中生成的迁移。现在,数据库已经可以使用了。在配置中,数据库是sqlite3:
settings.configure(
    ...
    DATABASES = {
        'default': {
              'ENGINE': 'django.db.backends.sqlite3',
              'NAME': 'db.sqlite3',
         }
    },
    ...
)
在当前的项目路径scenic_spot下,使用sqlite3命令操作数据库:
sqlite3 db.sqlite3
执行命令后,进入了sqlite3客户端的交互控制台(Console):
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite>
使用.tables查看数据库表,显示了迁移执行后生成的数据库表:
sqlite> .tables
auth_group                  cityspot_city             
auth_group_permissions      cityspot_spot             
auth_permission             django_admin_log          
auth_user                   django_content_type       
auth_user_groups            django_migrations         
auth_user_user_permissions  django_session
Django Admin当我们开始时,已经在配置了的 INSTALLED_APPS中列出了相关 Django Admin的内置应用'django.contrib.admin'。
Django Admin 是一个强大的工具,但我们需要知道何时使用它,其旨在用作内容驱动网站的后台应用程序,而不打算由网站的访问者使用。现在,我们将配置 Django Admin来维护我们应用程序。
让我们从创建管理员帐户开始:
python scenic_spot.py createsuperuser
按照提示说明进行操作:
Username (leave blank to use 'zhaocj'): admin
Email address: 3261524748@qq.com
Password: 
Password (again): 
Superuser created successfully.
现在,启动服务器:
python scenic_spot.py runserver
观察服务器启动后的信息:
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
May 08, 2022 - 07:05:42
Django version 4.0.3, using settings None
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
然后在项目根urls中增加admin的映射:
from django.urls import path, include
from django.contrib import admin
urlpatterns = [
     path('admin/', admin.site.urls),
     path('cityspot/',include(('cityspot.urls','cityspot'),namespace='cityspot')),
]
接着在网络浏览器中打开 URL:http://localhost:8000/admin/

在登录页面,输入用户名和密码,点击[LOGIN]按钮,成功登录后进入管理页面:

它已经具有了一些功能。 在这里我们可以添加用户和组来管理权限。点击Users进入到用户信息管理的界面,可以看到已创建的管理员用户信息。

这里使用管理员登录账号admin,以创建City模型的实例为例,需要对视图控制类CityView进行一个微调:
class CityView(View):
    def get(self, request):
        form  =  CityForm()
        return render(request, 'new_city.html', {'form': form })
    def post(self, request):
        form = CityForm(request.POST)
        if form.is_valid():
           city = form.save(commit=False)
           city.created_by = request.user              #  <--- 这里添加已登录用户的设置
           city.save()
           return redirect('cityspot:home')
        else:
           return render(request, 'new_city.html', {'form': form })
然后,启动Web服务器:
$ python scenic_spot.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
June 08, 2022 - 00:13:13
Django version 4.0.3, using settings None
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
在网络浏览器中打开 URL:http://localhost:8000/admin/, 进行管理员账号的登录。接下来新启浏览器窗口,并在地址栏打开URL: http://localhost:8000/cityspot/addcity,添加新的城市实例:

在发布一个新增城市的实例后,在当前的项目scenic_spot的目录下,使用sqlite3的管理命令查看数据库情况:
$ sqlite3 db.sqlite3
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite>
查看数据库表:
sqlite>.tables
auth_group                  cityspot_city             
auth_group_permissions      cityspot_spot             
auth_permission             django_admin_log          
auth_user                   django_content_type       
auth_user_groups            django_migrations         
auth_user_user_permissions  django_session
查看auth_user表:
sqlite> select * from auth_user;
1|pbkdf2_sha256$320000$4noFZjeq5LuqAEXiHHjFJ9$TMp7xXOgCA/GDUDZdQ/tUJSdyIErpOS45oZNc4cbYJQ=|2022-05-08 
08:40:18.137345|1|admin||3261524748@qq.com|1|1|2022-05-08 06:56:05.542979|
查看cityspot_city表:
sqlite> select * from cityspot_city;
1|广州|五羊城|2022-05-08 08:40:57.420875||1|
           
       
   博文最后更新时间: