在 Django 和 Django REST Framework 中使用保留字 "class" 作为字段名

Using the reserved word "class" as field name in Django and Django REST Framework

问题描述

分类学是根据共有特征定义和命名生物有机体组的科学。生物体被分组为分类单元(单数:分类单元),这些组被赋予分类等级。现代使用的主要等级是域、界、门、class、目、科、属和种。 有关维基百科 Taxonomy and Taxonomic ranks 的更多信息。

红狐维基百科文章分类等级中为例需要像这样创建一个 JSON 输出:

{
    "species": "vulpes",
    "genus": "Vulpes",
    "family": "Canidae",
    "order": "Carnivora",
    "<b>class</b>": "Mammalia",
    "phylum": "Chordata",
    "kingdom": "Animalia",
    "domain": "Eukarya"
}

由于 Django REST Framework 根据字段名称创建键,因此分类等级 class(示例中的粗体)出现了问题,因为它是Python 中的保留字,不能用作变量名。

我试过的

在 Django 中创建的模型 class 如下所示:

class Species(models.Model):
    species = models.CharField()
    genus = models.CharField()
    family = models.CharField()
    # class = models.CharField() - class is reserved word in Python
    # class_ = models.CharField() - Django doesn't allow field names
    # ending with underscore. That wouldn't be either a satisfying solution.
    # further fields

问题

有什么方法可以解决这个问题并生成所需的输出吗? 如果没有,解决此问题的最佳做法是什么?

生物信息学领域的其他软件开发人员可能对这个问题的解决方案感兴趣,所以我 post 这里我的方法是 Alasdair 所建议的。

我们的目标是为一个生物物种创建一个模型,为了简单起见,我们假设一个动物,并使用代表正确分类等级的 Django REST Framework 创建一个端点。

models.py

from django.db import models

class Animal(models.Model):
    canonical_name = models.CharField(max_length=100, unique=True)
    species = models.CharField(max_length=60, unique=True)
    genus = models.CharField(max_length=30)
    family = models.CharField(max_length=30)
    order = models.CharField(max_length=30)
    # we can't use class as field name
    class_name = models.CharField('Class', db_column='class', max_length=30)
    phylum = models.CharField(max_length=30)
    # we don't need to define kingdom and domain
    # it's clear that it is an animal and eukaryote

    def __str__(self):
        return '{} ({})'.format(self.canonical_name, self.species)

serializers.py

from collections import OrderedDict

from rest_framework import serializers

from .models import Species

class SpeciesSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Animal
        fields = ('url', 'id', 'canonical_name', 'species', 'genus',
            'subfamily', 'family', 'order', 'class_name', 'phylum')

    def to_representation(self, obj):
        # call the parent method and get an OrderedDict
        data = super(SpeciesSerializer, self).to_representation(obj)
        # generate a list of the keys and replace the key 'class_name'
        keys = list(data.keys())
        keys.insert(keys.index('class_name'), 'class')
        keys.remove('class_name')
        # remove 'class_name' and assign its value to a new key 'class'
        class_name = data.pop('class_name')
        data.update({'class': class_name})
        # create new OrderedDict with the order given by the keys
        response = OrderedDict((k, data[k]) for k in keys)
        return response

方法to_representation帮助我们操作输出。我在这里做了一些额外的工作,以获得所需顺序的分类等级。

因此 red fox 的输出如下所示:

赤狐 (狐狸)

{
    "url": "http://localhost:8000/animal/1",
    "id": 1,
    "canonical_name": "Red fox",
    "species": "Vulpes vulpes",
    "genus": "Vulpes",
    "family": "Canidae",
    "order": "Carnivora",
    "class": "Mammalia",
    "phylum": "Chordata"
}

这是一个简化的例子,实际上你会有更多的字段或者可能每个分类等级都有一个模型,但在某些地方你可能会遇到保留字 class 和分类等级之间的冲突class.
我希望这也能帮助其他人。

你可以像下面那样做

class SpeciesSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Species
        fields = (
            'url', 'id', 'canonical_name', 'slug',  'species', 'genus',
            'subfamily', 'family', 'order','class', 'phylum',
            'ncbi_id', 'ncbi_taxonomy',
        )
        read_only_fields = ('slug',)
        extra_kwargs = {
            'url': {'lookup_field': 'slug'}
        }

SpeciesSerializer._declared_fields["class"] = serializers.CharField(source="class_name")

如以下答案所述

您可以在 get_fields() 方法的重载版本中重命名字段

class MySerializer(serializers.Serializer):
    class_ = serializers.ReadOnlyField()

    def get_fields(self):
        result = super().get_fields()
        # Rename `class_` to `class`
        class_ = result.pop('class_')
        result['class'] = class_
        return result

您可以通过字符串设置 class 的 属性:

class SpeciesSerializer(serializers.Serializer):
    species = serializers.CharField()
    genus = serializers.CharField()
    family = serializers.CharField()
    vars()['class'] = serializers.CharField()