ORM简介

  • MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库
  • ORM是“对象-关系-映射”的简称,主要任务是:
    • 根据对象的类型生成表结构
    • 将对象、列表的操作,转换为sql语句
    • 将sql查询到的结果转换为对象、列表
  • 这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
  • Django中的模型包含存储数据的字段和约束,对应着数据库中唯一的表

使用MySql数据库

  • 在虚拟环境中安装mysql包
  1. pip install mysql-python
  • 在mysql中创建数据库
  1. create databases test2 charset=utf8
  • 打开settings.py文件,修改DATABASES项
  1. DATABASES = {
  2. 'default': {
  3. 'ENGINE': 'django.db.backends.mysql',
  4. 'NAME': 'test2',
  5. 'USER': '用户名',
  6. 'PASSWORD': '密码',
  7. 'HOST': '数据库服务器ip,本地可以使用localhost',
  8. 'PORT': '端口,默认为3306',
  9. }
  10. }

开发流程

  1. 在models.py中定义模型类,要求继承自models.Model
  2. 把应用加入settings.py文件的installed_app项
  3. 生成迁移文件
  4. 执行迁移生成表
  5. 使用模型类进行crud操作

使用数据库生成模型类

  1. python manage.py inspectdb > booktest/models.py

1. 定义模型

定义模型

  • 在模型中定义属性,会生成表中的字段
  • django根据属性的类型确定以下信息:
    • 当前选择的数据库支持字段的类型
    • 渲染管理表单时使用的默认html控件
    • 在管理站点最低限度的验证
  • django会为表增加自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后,则django不会再生成默认的主键列
  • 属性命名限制
    • 不能是python的保留关键字
    • 由于django的查询方式,不允许使用连续的下划线

定义属性

  • 定义属性时,需要字段类型
  • 字段类型被定义在django.db.models.fields目录下,为了方便使用,被导入到django.db.models中
  • 使用方式
    1. 导入from django.db import models
    2. 通过models.Field创建字段类型的对象,赋值给属性
  • 对于重要数据都做逻辑删除,不做物理删除,实现方法是定义isDelete属性,类型为BooleanField,默认值为False

字段类型

  • AutoField:一个根据实际ID自动增长的IntegerField,通常不指定

    • 如果不指定,一个主键字段将自动添加到模型中
  • BooleanField:true/false 字段,此字段的默认表单控制是CheckboxInput
  • NullBooleanField:支持null、true、false三种值
  • CharField(max_length=字符长度):字符串,默认的表单样式是 TextInput
  • TextField:大文本字段,一般超过4000使用,默认的表单控件是Textarea
  • IntegerField:整数
  • DecimalField(max_digits=None, decimal_places=None):使用python的Decimal实例表示的十进制浮点数
    • DecimalField.max_digits:位数总数
    • DecimalField.decimal_places:小数点后的数字位数
  • FloatField:用Python的float实例来表示的浮点数
  • DateField[auto_now=False, auto_now_add=False]):使用Python的datetime.date实例表示的日期
    • 参数DateField.auto_now:每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false
    • 参数DateField.auto_now_add:当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false
    • 该字段默认对应的表单控件是一个TextInput. 在管理员站点添加了一个JavaScript写的日历控件,和一个“Today"的快捷按钮,包含了一个额外的invalid_date错误消息键
    • auto_now_add, auto_now, and default 这些设置是相互排斥的,他们之间的任何组合将会发生错误的结果
  • TimeField:使用Python的datetime.time实例表示的时间,参数同DateField
  • DateTimeField:使用Python的datetime.datetime实例表示的日期和时间,参数同DateField
  • FileField:一个上传文件的字段
  • ImageField:继承了FileField的所有属性和方法,但对上传的对象进行校验,确保它是个有效的image

字段选项

  • 通过字段选项,可以实现对字段的约束
  • 在字段对象时通过关键字参数指定
  • null:如果为True,Django 将空值以NULL 存储到数据库中,默认值是 False
  • blank:如果为True,则该字段允许为空白,默认值是 False
  • 对比:null是数据库范畴的概念,blank是表单验证证范畴的
  • db_column:字段的名称,如果未指定,则使用属性的名称
  • db_index:若值为 True, 则在表中会为此字段创建索引
  • default:默认值
  • primary_key:若为 True, 则该字段会成为模型的主键字段
  • unique:如果为 True, 这个字段在表中必须有唯一值

关系

  • 关系的类型包括

    • ForeignKey:一对多,将字段定义在多的端中
    • ManyToManyField:多对多,将字段定义在两端中
    • OneToOneField:一对一,将字段定义在任意一端中
  • 可以维护递归的关联关系,使用'self'指定,详见“自关联”
  • 用一访问多:对象.模型类小写_set
  1. bookinfo.heroinfo_set
  • 用一访问一:对象.模型类小写
  1. heroinfo.bookinfo
  • 访问id:对象.属性_id
  1. heroinfo.book_id

元选项

  • 在模型类中定义类Meta,用于设置元信息
  • 元信息db_table:定义数据表名称,推荐使用小写字母,数据表的默认名称
  1. <app_name>_<model_name>
  • ordering:对象的默认排序字段,获取对象的列表时使用,接收属性构成的列表
  1. class BookInfo(models.Model):
  2. ...
  3. class Meta():
  4. ordering = ['id']
  • 字符串前加-表示倒序,不加-表示正序
  1. class BookInfo(models.Model):
  2. ...
  3. class Meta():
  4. ordering = ['-id']
  • 排序会增加数据库的开销

示例演示

  • 创建test2项目,并创建booktest应用,使用mysql数据库
  • 定义图书模型
  1. class BookInfo(models.Model):
  2. btitle = models.CharField(max_length=20)
  3. bpub_date = models.DateTimeField()
  4. bread = models.IntegerField(default=0)
  5. bcommet = models.IntegerField(default=0)
  6. isDelete = models.BooleanField(default=False)
  • 英雄模型
  1. class HeroInfo(models.Model):
  2. hname = models.CharField(max_length=20)
  3. hgender = models.BooleanField(default=True)
  4. isDelete = models.BooleanField(default=False)
  5. hcontent = models.CharField(max_length=100)
  6. hbook = models.ForeignKey('BookInfo')
  • 定义index、detail视图
  • index.html、detail.html模板
  • 配置url,能够完成图书及英雄的展示

测试数据

  • 模型BookInfo的测试数据
  1. insert into booktest_bookinfo(btitle,bpub_date,bread,bcommet,isDelete) values
  2. ('射雕英雄传','1980-5-1',12,34,0),
  3. ('天龙八部','1986-7-24',36,40,0),
  4. ('笑傲江湖','1995-12-24',20,80,0),
  5. ('雪山飞狐','1987-11-11',58,24,0)
  • 模型HeroInfo的测试数据
  1. insert into booktest_heroinfo(hname,hgender,hbook_id,hcontent,isDelete) values
  2. ('郭靖',1,1,'降龙十八掌',0),
  3. ('黄蓉',0,1,'打狗棍法',0),
  4. ('黄药师',1,1,'弹指神通',0),
  5. ('欧阳锋',1,1,'蛤蟆功',0),
  6. ('梅超风',0,1,'九阴白骨爪',0),
  7. ('乔峰',1,2,'降龙十八掌',0),
  8. ('段誉',1,2,'六脉神剑',0),
  9. ('虚竹',1,2,'天山六阳掌',0),
  10. ('王语嫣',0,2,'神仙姐姐',0),
  11. ('令狐冲',1,3,'独孤九剑',0),
  12. ('任盈盈',0,3,'弹琴',0),
  13. ('岳不群',1,3,'华山剑法',0),
  14. ('东方不败',0,3,'葵花宝典',0),
  15. ('胡斐',1,4,'胡家刀法',0),
  16. ('苗若兰',0,4,'黄衣',0),
  17. ('程灵素',0,4,'医术',0),
  18. ('袁紫衣',0,4,'六合拳',0)
  1.  

2. 模型成员

类的属性

  • objects:是Manager类型的对象,用于与数据库进行交互
  • 当定义模型类时没有指定管理器,则Django会为模型类提供一个名为objects的管理器
  • 支持明确指定模型类的管理器
  1. class BookInfo(models.Model):
  2. ...
  3. books = models.Manager()
  • 当为模型类指定管理器后,django不再为模型类生成名为objects的默认管理器

管理器Manager

  • 管理器是Django的模型进行数据库的查询操作的接口,Django应用的每个模型都拥有至少一个管理器
  • 自定义管理器类主要用于两种情况
  • 情况一:向管理器类中添加额外的方法:见下面“创建对象”中的方式二
  • 情况二:修改管理器返回的原始查询集:重写get_queryset()方法
  1. class BookInfoManager(models.Manager):
  2. def get_queryset(self):
  3. return super(BookInfoManager, self).get_queryset().filter(isDelete=False)
  4. class BookInfo(models.Model):
  5. ...
  6. books = BookInfoManager()

创建对象

  • 当创建对象时,django不会对数据库进行读写操作
  • 调用save()方法才与数据库交互,将对象保存到数据库中
  • 使用关键字参数构造模型对象很麻烦,推荐使用下面的两种之式
  • 说明: _init _方法已经在基类models.Model中使用,在自定义模型中无法使用,
  • 方式一:在模型类中增加一个类方法
  1. class BookInfo(models.Model):
  2. ...
  3. @classmethod
  4. def create(cls, title, pub_date):
  5. book = cls(btitle=title, bpub_date=pub_date)
  6. book.bread=0
  7. book.bcommet=0
  8. book.isDelete = False
  9. return book
  10. 引入时间包:from datetime import *
  11. 调用:book=BookInfo.create("hello",datetime(1980,10,11));
  12. 保存:book.save()
  • 方式二:在自定义管理器中添加一个方法
  • 在管理器的方法中,可以通过self.model来得到它所属的模型类
  1. class BookInfoManager(models.Manager):
  2. def create_book(self, title, pub_date):
  3. book = self.model()
  4. book.btitle = title
  5. book.bpub_date = pub_date
  6. book.bread=0
  7. book.bcommet=0
  8. book.isDelete = False
  9. return book
  10. class BookInfo(models.Model):
  11. ...
  12. books = BookInfoManager()
  13. 调用:book=BookInfo.books.create_book("abc",datetime(1980,1,1))
  14. 保存:book.save()
  • 在方式二中,可以调用self.create()创建并保存对象,不需要再手动save()
  1. class BookInfoManager(models.Manager):
  2. def create_book(self, title, pub_date):
  3. book = self.create(btitle = title,bpub_date = pub_date,bread=0,bcommet=0,isDelete = False)
  4. return book
  5. class BookInfo(models.Model):
  6. ...
  7. books = BookInfoManager()
  8. 调用:book=Book.books.create_book("abc",datetime(1980,1,1))
  9. 查看:book.pk

实例的属性

  • DoesNotExist:在进行单个查询时,模型的对象不存在时会引发此异常,结合try/except使用

实例的方法

  • str (self):重写object方法,此方法在将对象转换成字符串时会被调用
  • save():将模型对象保存到数据表中,用于新增和更新
  • delete():将模型对象从数据表中删除

3. 模型查询

简介

  • 查询集表示从数据库中获取的对象集合
  • 查询集可以含有零个、一个或多个过滤器
  • 过滤器基于所给的参数限制查询的结果
  • 从Sql的角度,查询集和select语句等价,过滤器像where和limit子句
  • 接下来主要讨论如下知识点
    • 查询集
    • 字段查询:比较运算符,F对象,Q对象

查询集

  • 在管理器上调用过滤器方法会返回查询集
  • 查询集经过过滤器筛选后返回新的查询集,因此可以写成链式过滤
  • 惰性执行:创建查询集不会带来任何数据库的访问,直到调用数据时,才会访问数据库
  • 何时对查询集求值:迭代,序列化,与if合用
  • 返回查询集的方法,称为过滤器
    • all()
    • filter()
    • exclude()
    • order_by()
    • values():一个对象构成一个字典,然后构成一个列表返回
  • 写法:
  1. filter(键1=值1,键2=值2)
  2. 等价于
  3. filter(键1=值1).filter(键2=值2)
  • 返回单个值的方法

    • get():返回单个满足条件的对象

      • 如果未找到会引发"模型类.DoesNotExist"异常
      • 如果多条被返回,会引发"模型类.MultipleObjectsReturned"异常
    • count():返回当前查询的总条数
    • first():返回第一个对象
    • last():返回最后一个对象
    • exists():判断查询集中是否有数据,如果有则返回True

限制查询集

  • 查询集返回列表,可以使用下标的方式进行限制,等同于sql中的limit和offset子句
  • 注意:不支持负数索引
  • 使用下标后返回一个新的查询集,不会立即执行查询
  • 如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()引发DoesNotExist异常

查询集的缓存

  • 每个查询集都包含一个缓存来最小化对数据库的访问
  • 在新建的查询集中,缓存为空,首次对查询集求值时,会发生数据库查询,django会将查询的结果存在查询集的缓存中,并返回请求的结果,接下来对查询集求值将重用缓存的结果
  • 情况一:这构成了两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载
  1. print([e.title for e in Entry.objects.all()])
  2. print([e.title for e in Entry.objects.all()])
  • 情况二:两次循环使用同一个查询集,第二次使用缓存中的数据
  1. querylist=Entry.objects.all()
  2. print([e.title for e in querylist])
  3. print([e.title for e in querylist])
  • 何时查询集不会被缓存:当只对查询集的部分进行求值时会检查缓存,但是如果这部分不在缓存中,那么接下来查询返回的记录将不会被缓存,这意味着使用索引来限制查询集将不会填充缓存,如果这部分数据已经被缓存,则直接使用缓存中的数据

字段查询

  • 实现where子名,作为方法filter()、exclude()、get()的参数
  • 语法:属性名称__比较运算符=值
  • 表示两个下划线,左侧是属性名称,右侧是比较类型
  • 对于外键,使用“属性名_id”表示外键的原始值
  • 转义:like语句中使用了%与,匹配数据中的%与,在过滤器中直接写,例如:filter(title__contains="%")=>where title like '%\%%',表示查找标题中包含%的

比较运算符

  • exact:表示判等,大小写敏感;如果没有写“ 比较运算符”,表示判等
  1. filter(isDelete=False)
  • contains:是否包含,大小写敏感
  1. exclude(btitle__contains='传')
  • startswith、endswith:以value开头或结尾,大小写敏感
  1. exclude(btitle__endswith='传')
  • isnull、isnotnull:是否为null
  1. filter(btitle__isnull=False)
  • 在前面加个i表示不区分大小写,如iexact、icontains、istarswith、iendswith
  • in:是否包含在范围内
  1. filter(pk__in=[1, 2, 3, 4, 5])
  • gt、gte、lt、lte:大于、大于等于、小于、小于等于
  1. filter(id__gt=3)
  • year、month、day、week_day、hour、minute、second:对日期间类型的属性进行运算
  1. filter(bpub_date__year=1980)
  2. filter(bpub_date__gt=date(1980, 12, 31))
  • 跨关联关系的查询:处理join查询

    • 语法:模型类名 <属性名> <比较>
    • 注:可以没有__<比较>部分,表示等于,结果同inner join
    • 可返向使用,即在关联的两个模型中都可以使用
  1. filter(heroinfo_ _hcontent_ _contains='八')
  • 查询的快捷方式:pk,pk表示primary key,默认的主键是id
  1. filter(pk__lt=6)

聚合函数

  • 使用aggregate()函数返回聚合函数的值
  • 函数:Avg,Count,Max,Min,Sum
  1. from django.db.models import Max
  2. maxDate = list.aggregate(Max('bpub_date'))
  • count的一般用法:
  1. count = list.count()

F对象

  • 可以使用模型的字段A与字段B进行比较,如果A写在了等号的左边,则B出现在等号的右边,需要通过F对象构造
  1. list.filter(bread__gte=F('bcommet'))
  • django支持对F()对象使用算数运算
  1. list.filter(bread__gte=F('bcommet') * 2)
  • F()对象中还可以写作“模型类__列名”进行关联查询
  1. list.filter(isDelete=F('heroinfo__isDelete'))
  • 对于date/time字段,可与timedelta()进行运算
  1. list.filter(bpub_date__lt=F('bpub_date') + timedelta(days=1))

Q对象

  • 过滤器的方法中关键字参数查询,会合并为And进行
  • 需要进行or查询,使用Q()对象
  • Q对象(django.db.models.Q)用于封装一组关键字参数,这些关键字参数与“比较运算符”中的相同
  1. from django.db.models import Q
  2. list.filter(Q(pk_ _lt=6))
  • Q对象可以使用&(and)、|(or)操作符组合起来
  • 当操作符应用在两个Q对象时,会产生一个新的Q对象
  1. list.filter(pk_ _lt=6).filter(bcommet_ _gt=10)
  2. list.filter(Q(pk_ _lt=6) | Q(bcommet_ _gt=10))
  • 使用~(not)操作符在Q对象前表示取反
  1. list.filter(~Q(pk__lt=6))
  • 可以使用&|~结合括号进行分组,构造做生意复杂的Q对象
  • 过滤器函数可以传递一个或多个Q对象作为位置参数,如果有多个Q对象,这些参数的逻辑为and
  • 过滤器函数可以混合使用Q对象和关键字参数,所有参数都将and在一起,Q对象必须位于关键字参数的前面

自连接

  • 对于地区信息,属于一对多关系,使用一张表,存储所有的信息
  • 类似的表结构还应用于分类信息,可以实现无限级分类
  • 新建模型AreaInfo,生成迁移
  1. class AreaInfo(models.Model):
  2. atitle = models.CharField(max_length=20)
  3. aParent = models.ForeignKey('self', null=True, blank=True)
  • 访问关联对象
  1. 上级对象:area.aParent
  2. 下级对象:area.areainfo_set.all()
  • 加入测试数据(在workbench中,参见“省市区mysql.txt”)
  • 在booktest/views.py中定义视图area
  1. from models import AreaInfo
  2. def area(request):
  3. area = AreaInfo.objects.get(pk=130100)
  4. return render(request, 'booktest/area.html', {'area': area})
  • 定义模板area.html
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>地区</title>
  5. </head>
  6. <body>
  7. 当前地区:{{area.atitle}}
  8. <hr/>
  9. 上级地区:{{area.aParent.atitle}}
  10. <hr/>
  11. 下级地区:
  12. <ul>
  13. { %for a in area.areainfo_set.all%}
  14. <li>{{a.atitle}}</li>
  15. { %endfor%}
  16. </ul>
  17. </body>
  18. </html>
  • 在booktest/urls.py中配置一个新的urlconf
  1. urlpatterns = [
  2. url(r'^area/$', views.area, name='area')
  3. ]

遇到的问题——Win64无法安装mysql-python:

1.进入这个网站http://www.lfd.uci.edu/~gohlke/pythonlibs/下载

  1. MySQL_python1.2.5cp27nonewin_amd64.whl

ps:该网站内容较多,使用ctrl+f快捷键

2.进入windows cmd命令行安装wheel

  1. pip install wheel

ps:如果pip没有加入环境变量,建议先将pip加入环境变量。pip在python安装目录下的Scripts文件夹下。 
3.进入你下载MySQL_python‑1.2.5‑cp27‑none‑win_amd64.whl目录,然后使用

  1. pip install MySQL_python1.2.5cp27nonewin_amd64.whl

4.安装完成后验证

  1. import MySQLdb

如果不报错,就说明安装成功了。

[Django学习]模型的更多相关文章

  1. django学习の模型

    orm:对象数据库和模型的映射.如果想以简单的方式去操作数据库,例如用类的方式去操作,就像 p = Person.get(id = 1),那么就必须使得代码和数据库的结构具有映射关系,实现这种关系,你 ...

  2. 5.django学习模型

    ##Django Models ##编写models 1.在应用根目录下创建models.py并引入models模块,创建类,继承models.Model该类即是一张数据表 2.字段创建 ##映射数据 ...

  3. Django学习笔记(2)——模型,后台管理和视图的学习

    一:Web投票示例 本节我们首先从全局范围再复习一下Django的概念,让自己对Django的设计理念, 功能模块,体系架构,基本用法有初步的印象. Django初始的详细博客内容:请点击我 该应用包 ...

  4. Python Django 学习 (二) 【Django 模型】

    注: 由于自己排版确实很难看,本文开始使用markdown编辑,希望有所改善 官方定义 A model is the single, definitive source of information ...

  5. Django学习笔记(16)——扩展Django自带User模型,实现用户注册与登录

    一,项目题目:扩展Django自带User模型,实现用户注册与登录 我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统.此时我们需要实现包括用户注册,登录,用户认证,注销,修改密码等功能. ...

  6. django学习-数据库配置-创建模型

    数据库配置 在mysite/settings.py中,包含了django项目设置的python模块 通常,这个配置文件使用SQLite作为默认数据库.如果你不熟悉数据库,或者只是想尝试下django, ...

  7. Django学习笔记之数据库-模型的操作

    模型的操作 在ORM框架中,所有模型相关的操作,比如添加/删除等.其实都是映射到数据库中一条数据的操作.因此模型操作也就是数据库表中数据的操作. 添加模型 添加模型到数据库中.首先需要创建一个模型.创 ...

  8. Django 学习第六天——Django模型基础第一节

    一.Django 的 ORM 简介: Django的ORM系统的分析: 1.ORM 概念:对象关系映射(Object Relational Mapping,简称ORM) 2.ORM的优势:不用直接编写 ...

  9. django学习笔记(三)模型

    1.创建一个django app: python manage.py startapp books 2.validate 命令检查你的模型的语法和逻辑是否正确.一旦你觉得你的模型可能有问题,运行 py ...

随机推荐

  1. Tomcat配置+JSP页面模板修改UTF-8

    A.修改Tomcat端口号步骤:1.找到Tomcat目录下的conf文件夹2.进入conf文件夹里面找到server.xml文件3.打开server.xml文件4.在server.xml文件里面找到下 ...

  2. memset(&a, 0, sizeof(struct customer))函数

    memset(&a, 0, sizeof(struct customer))函数定义在memory.h中,用于给指定的内存区域赋值,在该语句中,&a指定待赋值的内存首地址,0是要赋的值 ...

  3. RHEL7 -- 通过gerp使用正则表达式

    正则表达式常会含有shell元字符(如S.*等),建议使用单引号('')来括起行令上的正则表达式 1.行定位符号 行首定位符号^和行尾定位符$ #找出以s开头的行: # grep '^s' /etc/ ...

  4. pimpl idiom

    pimpl idiom flyfish 2014-9-30 pimpl是Pointer to implementation的缩写 为什么要使用pimpl 1最小化编译依赖 2接口与实现分离 3可移植 ...

  5. Python 字典 update() 方法

    描述 Python 字典 update() 方法用于更新字典中的键/值对,可以修改存在的键对应的值,也可以添加新的键/值对到字典中. 用法与 Python dict() 函数相似. 语法 update ...

  6. 如何理解Latency和Throughput: 吞吐量和延迟

    Latency,中文译作延迟.Throughput,中文译作吞吐量.它们是衡量软件系统的最常见的两个指标. 延迟一般包括单向延迟(One-way Latency)和往返延迟(Round Trip La ...

  7. Android利用Fiddler进行网络数据抓包【怎么跟踪微信请求】

    主要介绍Android及IPhone手机上如何利用Fiddler进行网络数据抓包,比如我们想抓某个应用(微博.微信.墨迹天气)的网络通信请求就可以利用这个方法. Mac 下请使用 Charles 代替 ...

  8. C 指针使用误区

    /** *错误给指针赋常量 *知识点:指针存储内存地址 **/ #include <stdio.h>void main(){ //int *p_int = 123; //错误,不能直接给指 ...

  9. linux的fork()函数具体解释 子进程复制父进程什么

    #include<stdio.h>   #include<string.h>   #include<stdlib.h>   #include<unistd.h ...

  10. 在vps上安装中文环境

    现在vps默认都是安装的英文环境,其实变成中文环境也很简单.我记录以下在ubuntu下如何改变为中文环境. 1.安装中文环境包. sudo apt install language-pack-zh-h ...