通过 modelforms 将非规范化数据导入 Django 模型

Importing Denormalized data into django models via modelforms

场景:

我有一些数据看起来有点像这样:

Person   | Favorite Color | Favorite Fruit
------------------------------------------
Bobby    | RED            | BANANA
Jared    | YELLOW         | RASPBERRY
Milly    | BLACK          | PEACH
Shawn    | ORANGE         | ORANGE

假设它是一个平面文件,或者 python 字典,或者其他一些非 sql 格式。

编辑:假设我已经在一个 Python 结构中得到它,如下所示:

data = [
    {"name": "Bobby", "favorite_color": "RED", "favorite_fruit": "BANANA"},
    {"name": "Jared", "favorite_color": "YELLOW", "favorite_fruit": "RASPBERRY"},
    # etc....
 ]

我有如下所示的 Django 模型:

class Person(models.Model):
    COLORS = (
                 ('R', 'RED'),
                 ('O', 'ORANGE'),
                 ('Y', 'YELLOW'),
                 ('G', 'GREEN'),
                 ('B', 'BLUE'),
                 ('P', 'PURPLE'),
                 ('L', 'BLACK'),
                 ('W', 'WHITE')
              )
    name = CharField(max_length=256)
    favorite_color = CharField(max_length=1, choices=COLORS)
    favorite_fruit = ForeignKey(Fruit)

class Fruit(models.Model):
    name = CharField(max_length=256)
    fructose_content = PositiveIntegerField()

编辑:假设我的 Fruit 模型已经填充了所有可能的水果。

任务:

我想使用 ModelForms 将我的数据从原始源导入我的 Django 模型,以利用适当的验证和数据库抽象。

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person
        fields = '__all__'

有没有办法 ModelForm 可以将非规范化数据转换为可以保存在模型中的数据? ModelForm这里用错了吗?

试试下面的代码:

insert_data = []

with open('data.txt') as f:
    state = 'HEADER'
    headers = []
    for line in f.readlines():
        if state == 'HEADER':
            headers = [header.lower().strip().replace(' ', '_') for header in line.split('|')]
            state = 'IGNORE'
        elif state == 'IGNORE':
            state = 'DATA'
        elif state == 'DATA':
            data_values = map(str.strip, line.split('|'))
            insert_entry = {}
            for key, data in zip(headers, data_values):
                insert_entry[key] = data
            insert_data.append(insert_entry)

for row in insert_data:
    form = PersonForm(row)

    if form.is_valid():
        form.save()
    else:
        print form.errors()

第一步是读取文件(假设文件名为 data.txt),我建议您使用 json 或其他一些结构化文本以避免输入错误,因为您可以先检查是否文件使用众所周知的库进行了良好格式化。

要使此脚本正常工作,您还需要对表单中的字段使用一些技巧,我认为将 PERSON 字段称为 NAME 就足够了。

第二步,我们为每个要插入的条目创建表单实例,验证它们,如果一切正常,我们将它们保存到数据库中。

希望对您有所帮助,

我想出了一个部分解决方案,至少对于涉及选择的问题。我想通过一些修补它也可以用于 ForeignKey 字段。

首先,我定义了一个函数 get_choice_by_name,它遍历一个选择元组并按值查找键。

然后我将 TypedChoiceField 子类化并覆盖其 clean() 方法来转换数据。此方法似乎在任何验证之前被调用。

代码如下:

def get_choice_by_name(name, choices, case_sensitive=False):
    try:
        if name is None:
            return ''
        elif name and not case_sensitive:
            return next(k for k, n in choices
                        if n.lower() == name.lower())
        else:
            return next(k for k, n in choices if n == name)
    except StopIteration:
        raise ValueError(
            "Invalid choice: {}, not found in {}".format(name, choices)
        )

class DenormalizedChoiceField(TypedChoiceField):

    def clean(self, value):
        if not value:
            return self.empty_value
        try:
            value = get_choice_by_name(value, self.choices)
        except ValueError as e:
            raise ValidationError(str(e))

        value = super(DenormalizedChoiceField, self).clean(value)
        return value

我的 ModelForm 现在只需要将有问题的字段重新定义为 DenormalizedChoiceField。不过,我需要明确指定选项,但出于某种原因,如果您覆盖该字段,它不会从模型中选择它。

class PersonForm(forms.ModelForm):
    favorite_color = DenormalizedChoiceField(choices=Person.COLORS)
    class Meta:
        model = Person
        fields = '__all__'