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

问题描述 投票:9回答:3

Description of the problem

分类学是在共同特征的基础上定义和命名生物有机体群的科学。生物被组合成分类群(单数:分类单元),这些组被赋予分类等级。现代使用的主要等级是领域,王国,门,阶级,秩序,家庭,属和物种。有关维基百科中TaxonomyTaxonomic ranks的更多信息。

按照维基百科文章分类排名中的红狐狸的例子,我需要创建一个这样的JSON输出:

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

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

What I have tried

在Django中创建的模型类看起来像这样:

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

Question

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

python django python-3.x django-rest-framework bioinformatics
3个回答
4
投票

你可以像下面这样做

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")

如下面的答案所述

https://stackoverflow.com/a/47717441/2830850


5
投票

生物信息学领域的其他软件开发人员可能对这个问题的解决方案感兴趣,所以我在这里发布我的方法,如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之间的冲突。 我希望这也可以帮助其他人。


2
投票

您可以在重载版本的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
© www.soinside.com 2019 - 2024. All rights reserved.