# Python Django近期总结 - 2021-06-06

> 作者：zning（https://ning.ae） · 发布：2021/06/06 20:16 下午 (UTC+8) · 原文：https://zning.me/16229817791761.html

周五公司给买的键盘到货了，毕竟我自己不太会买这么贵的键盘……不过这个键盘的布局由于键数缩小到60，键位改变了不少。为了熟悉一下键盘，整理一下近期的一些经验，以飨读者。

## 使用Django REST Framework 提示`Could not resolve URL for hyperlinked relationship using view name "user-detail"`

经过查询，由于我在app里的`urls.py`里设定了namespace名称，因此在Django设置序列化的文件`serializers.py`中，需要在`serializers.HyperlinkedIdentityField`中指定`view_name`的名称，例如：

```python
class UserSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(view_name="myapp:user-detail")

    class Meta:
    model = User
    fields = ('url', 'username')
```

至此，该问题解决。

## Django Models 处理Select形式字段经验总结

其实还是挺简单的，由于我的需求只需要在Django后台页面显示，所以只需要在Models那里处理一下就好了。由于我需要的字段比较多，因此在app目录下新建了一个文件`cons.py`专门存储字段。

以下是模型文件`models.py`：

```python
# -*- coding: UTF-8 -*-
from django.db import models
from .cons import SALCATEGORIES
from .cons import YEAR
from .cons import MONTH
from .cons import ORG

# Create your models here.
class Salary(models.Model):
    month = models.CharField('月',max_length=50, choices=MONTH)
    year = models.CharField('年',max_length=10, choices=YEAR)
    org = models.CharField('组织名',max_length=100, choices=ORG)
    create_timestamp = models.DateTimeField('创建时间',auto_now_add=True)
    change_timestamp = models.DateTimeField('修改时间',auto_now=True)
    def __str__(self):
        return dict(YEAR)[self.year]+dict(MONTH)[self.month]+'_'+dict(ORG)[self.org]

    class Meta:
        db_table = 'salary'
```

`cons.py`文件引入的内容如下：

```python
# -*- coding: UTF-8 -*-
SALCATEGORIES = (
    ('shubas','应发 - 基本工资'),
    ('shupos','应发 - 岗位工资'),
    ('shupls','应发 - 绩效工资'),
    ('shuotr','应发 - 其他'),
    ('wihuem','扣缴 - 失业保险'),
    ('wihmed','扣缴 - 医疗保险'),
    ('wihold','扣缴 - 养老保险'),
    ('wihgjj','扣缴 - 公积金'),
    ('wihbgj','扣缴 - 补充住房公积金'),
    ('wihotr','扣缴 - 其他'),
    ('relsal','实发 - 实发工资'),
    ('relotr','实发 - 其他'),
)

YEAR = (
    ('2020','2020年'),
    ('2021','2021年'),
    ('2022','2022年'),
    ('2023','2023年'),
    ('2024','2024年'),
    ('2025','2025年'),
    ('2026','2026年'),
    ('2027','2027年'),
    ('2028','2028年'),
)

MONTH = (
    ('01','01月'),
    ('02','02月'),
    ('03','03月'),
    ('04','04月'),
    ('05','05月'),
    ('06','06月'),
    ('07','07月'),
    ('08','08月'),
    ('09','09月'),
    ('10','10月'),
    ('11','11月'),
    ('12','12月'),
)

ORG = (
    ('abc','中国abc公司'),
    ('cde','上海cde公司'),
)
```

这个键值对，存储的是前面的键，显示的是后面的值

上面的这个形式，对于该变量的类型也是一个挺有趣的东西。众所周知`( )`是个元组的初始化标志，正常来说元组是不能直接转换为字典的，但是上面这样的变量是可以正常转换的，请见如下测试代码和输出：

```python
print(type(ORG))
for i in ORG:
    print(i[0]+"_"+i[1])
    print(type(i))

print(type(dict(ORG)))
print(dict(ORG))
for i in dict(ORG):
    print(i)
    print(type(i))
```

输出:

```shell
<class 'tuple'>
abc_中国abc公司
<class 'tuple'>
cde_上海cde公司
<class 'tuple'>
<class 'dict'>
{'abc': '中国abc公司', 'cde': '上海cde公司'}
abc
<class 'str'>
cde
<class 'str'>
```

回到实现上来。最终我需要的后台显示情况如下：

![](media/16229817791761/16229839637075.jpg)

![](media/16229817791761/16229839876474.jpg)

数据库表存储情况如下：

![Pasted Screenshot 2021-06-06 20-54-18](media/16229817791761/Pasted%20Screenshot%202021-06-06%2020-54-18.png)

这里我们介绍一下Django里模型`Field`的`choices`字段：

> 一个 [sequence](https://docs.python.org/3/glossary.html#term-sequence) 本身由正好两个项目的迭代项组成（例如 `[(A，B)，(A，B)...]` ），作为该字段的选择。如果给定了选择，它们会被 [模型验证](https://docs.djangoproject.com/zh-hans/3.2/ref/models/instances/#validating-objects) 强制执行，默认的表单部件将是一个带有这些选择的选择框，而不是标准的文本字段。

> 每个元组中的第一个元素是要在模型上设置的实际值，第二个元素是人可读的名称。例如：

> ```python
> YEAR_IN_SCHOOL_CHOICES = [
>     ('FR', 'Freshman'),
>     ('SO', 'Sophomore'),
>     ('JR', 'Junior'),
>     ('SR', 'Senior'),
>     ('GR', 'Graduate'),
> ]
> ```

> 一般来说，最好在模型类内部定义选择，并为每个值定义一个合适的名称的常量：

> ```python
> from django.db import models
> 
> class Student(models.Model):
>     FRESHMAN = 'FR'
>     SOPHOMORE = 'SO'
>     JUNIOR = 'JR'
>     SENIOR = 'SR'
>     GRADUATE = 'GR'
>     YEAR_IN_SCHOOL_CHOICES = [
>         (FRESHMAN, 'Freshman'),
>         (SOPHOMORE, 'Sophomore'),
>         (JUNIOR, 'Junior'),
>         (SENIOR, 'Senior'),
>         (GRADUATE, 'Graduate'),
>     ]
>     year_in_school = models.CharField(
>         max_length=2,
>         choices=YEAR_IN_SCHOOL_CHOICES,
>         default=FRESHMAN,
>     )
> 
>     def is_upperclass(self):
>         return self.year_in_school in {self.JUNIOR, self.SENIOR}
> ```

> 虽然你可以在模型类之外定义一个选择列表，然后引用它，但在模型类内定义选择和每个选择的名称，可以将所有这些信息保留在使用它的类中，并帮助引用这些选择（例如，`Student.SOPHOMORE` 将在导入 `Student` 模型的任何地方工作）。

> 你还可以将你的可用选择收集到可用于组织目的的命名组中：

> ```
> MEDIA_CHOICES = [
>     ('Audio', (
>             ('vinyl', 'Vinyl'),
>             ('cd', 'CD'),
>         )
>     ),
>     ('Video', (
>             ('vhs', 'VHS Tape'),
>             ('dvd', 'DVD'),
>         )
>     ),
>     ('unknown', 'Unknown'),
> ]
> ```

> 每个元组中的第一个元素是应用于该组的名称。第二个元素是一个二元元组的迭代，每个二元元组包含一个值和一个可读的选项名称。分组后的选项可与未分组的选项结合在一个单一的列表中（如本例中的 `'unknown'` 选项）。

> 对于每一个设置了 `choice` 的模型字段，Django 会添加一个方法来检索字段当前值的可读名称。参见数据库 API 文档中的 [`get_FOO_display()`](https://docs.djangoproject.com/zh-hans/3.2/ref/models/instances/#django.db.models.Model.get_FOO_display)。

> 请注意，选择可以是任何序列对象——不一定是列表或元组。这让你可以动态地构造选择。但是如果你发现自己把 `chips` 魔改成动态的，你可能最好使用一个合适的的带有 [`ForeignKey`](https://docs.djangoproject.com/zh-hans/3.2/ref/models/fields/#django.db.models.ForeignKey) 的数据库表。 `chips` 是用于静态数据的，如果有的话，不应该有太大的变化。

> 每当 `choices` 的顺序变动时将会创建新的迁移。

> 除非 [`blank=False`](https://docs.djangoproject.com/zh-hans/3.2/ref/models/fields/#django.db.models.Field.blank) 与 [`default`](https://docs.djangoproject.com/zh-hans/3.2/ref/models/fields/#django.db.models.Field.default) 一起设置在字段上，否则包含 `"---------"` 的标签将与选择框一起呈现。要覆盖这种行为，可以在 `choices` 中添加一个包含 `None` 的元组，例如 `(None, 'Your String For Display')` 。另外，你也可以在有意义的地方使用一个空字符串来代替 `None` ——比如在 [`CharField`](https://docs.djangoproject.com/zh-hans/3.2/ref/models/fields/#django.db.models.CharField)。

最后，新设备镇楼压轴。这可是近几年手头设备最贵的一年了。

![IMG_8731](media/16229817791761/IMG_8731.jpg)
