如何在Django中使用具有ManyToManyFields的模型对象填充测试数据库?

huangapple go评论71阅读模式
英文:

How to fill test DB in Django with objects of models with ManyToManyFields?

问题

我想测试我的项目的速度,所以我需要用测试数据填充我的数据库。

我有一个带有许多ManyToManyField的模型(稍后我会创建更多模型):

class Deciduous(PlantBasicCharacteristics):
    usda_zone = models.ManyToManyField(UsdaZone)
    soil_moisture = models.ManyToManyField(SoilMoisture)
    soil_fertility = models.ManyToManyField(SoilFertility)
    soil_ph = models.ManyToManyField(SoilPh)

我正在尝试创建一个实用程序来填充数据库测试数据:

from random import randint

from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.db.models.fields import CharField, DecimalField
from django.db.models.fields.related import ManyToManyField
from django.shortcuts import get_object_or_404

from plants.models import Deciduous
from plants.web_page_filter_fields import get_plant_class_fields

PLANT_CLASSES = [Deciduous, ]
TEST_DATA_AMOUNT = 3

class Command(BaseCommand):

    def handle(self, *args, **options):
        for plant_class in PLANT_CLASSES:
            fields_and_names = get_plant_class_fields(plant_class)
            data = []
            for _ in range(TEST_DATA_AMOUNT):
                data.append(self.create_data(fields_and_names))
            print(data)
            plant_class.objects.bulk_create([
                plant_class(**values) for values in data
            ])

    def create_data(self, fields_and_names):
        data = {}
        for field_name, field_model in fields_and_names.items():
            if ('ptr' not in field_name
                    and 'synonym' not in field_name
                    and field_name != 'id'):
                if isinstance(field_model, CharField):
                    data[field_name] = self.get_string_random()
                elif isinstance(field_model, DecimalField):
                    data[field_name] = self.get_number_random()
                elif isinstance(field_model, ManyToManyField):
                    data[field_name] = [self.get_choice_random(field_model)]
        return data

    def get_string_random(self):
        letters = [chr(randint(97, 122)) for _ in range(randint(5, 20)]
        return ''.join(letters).capitalize()

    def get_number_random(self):
        return randint(10, 15000) / 100

    def get_choice_random(self, model):
        field_model = model.related_model
        field_choices = field_model._meta.get_field('name').choices
        choice_number = randint(0, len(field_choices) - 1)
        choice = field_choices[choice_number][0]
        return get_object_or_404(field_model, name=choice)

但我得到了以下错误:

  File "manage.py", line 22, in <module>
main()
File "manage.py", line 18, in main
execute_from_command_line(sys.argv)
File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
utility.execute()
File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 413, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 354, in run_from_argv
self.execute(*args, **cmd_options)
File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 398, in execute
output = self.handle(*args, **options)
File "/app/plants/management/commands/add_test_data.py", line 30, in handle
plant_class(**values) for values in data
File "/app/plants/management/commands/add_test_data.py", line 30, in <listcomp>
plant_class(**values) for values in data
File "/usr/local/lib/python3.7/site-packages/django/db/models/base.py", line 498, in __init__
_setattr(self, prop, kwargs[prop])
File "/usr/local/lib/python3.7/site-packages/django/db/models/fields/related_descriptors.py", line 547, in __set__
% self._get_set_deprecation_msg_params(),
TypeError: 直接赋值给多对多关系的前向侧是禁止的。请使用usda_zone.set()来代替。

是否可能使用for循环和set()来创建对象,而不必为每个ManyToManyField编写代码?

英文:

I want to test speed of my project, so I need to fill my DB with test data.

I have a model with lots of ManyToManyFields (later I will create more models):

class Deciduous(PlantBasicCharacteristics):
    usda_zone = models.ManyToManyField(UsdaZone)
    soil_moisture = models.ManyToManyField(SoilMoisture)
    soil_fertility = models.ManyToManyField(SoilFertility)
    soil_ph = models.ManyToManyField(SoilPh)

And I am trying to create utility to fill DB with test data:

from random import randint

from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.db.models.fields import CharField, DecimalField
from django.db.models.fields.related import ManyToManyField
from django.shortcuts import get_object_or_404

from plants.models import Deciduous
from plants.web_page_filter_fields import get_plant_class_fields

PLANT_CLASSES = [Deciduous, ]
TEST_DATA_AMOUNT = 3


class Command(BaseCommand):

    def handle(self, *args, **options):
        for plant_class in PLANT_CLASSES:
            fields_and_names = get_plant_class_fields(plant_class)
            data = []
            for _ in range(TEST_DATA_AMOUNT):
                data.append(self.create_data(fields_and_names))
            print(data)
            plant_class.objects.bulk_create([
                plant_class(**values) for values in data
            ])

    def create_data(self, fields_and_names):
        data = {}
        for field_name, field_model in fields_and_names.items():
            if (&#39;ptr&#39; not in field_name
                    and &#39;synonym&#39; not in field_name
                    and field_name != &#39;id&#39;):
                if isinstance(field_model, CharField):
                    data[field_name] = self.get_string_random()
                elif isinstance(field_model, DecimalField):
                    data[field_name] = self.get_number_random()
                elif isinstance(field_model, ManyToManyField):
                    data[field_name] = [self.get_choice_random(field_model)]
        return data

    def get_string_random(self):
        letters = [chr(randint(97, 122)) for _ in range(randint(5, 20))]
        return &#39;&#39;.join(letters).capitalize()

    def get_number_random(self):
        return randint(10, 15000) / 100

    def get_choice_random(self, model):
        field_model = model.related_model
        field_choices = field_model._meta.get_field(&#39;name&#39;).choices
        choice_number = randint(0, len(field_choices) - 1)
        choice = field_choices[choice_number][0]
        return get_object_or_404(field_model, name=choice)

But I get:

  File &quot;manage.py&quot;, line 22, in &lt;module&gt;
main()
File &quot;manage.py&quot;, line 18, in main
execute_from_command_line(sys.argv)
File &quot;/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py&quot;, line 419, in execute_from_command_line
utility.execute()
File &quot;/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py&quot;, line 413, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File &quot;/usr/local/lib/python3.7/site-packages/django/core/management/base.py&quot;, line 354, in run_from_argv
self.execute(*args, **cmd_options)
File &quot;/usr/local/lib/python3.7/site-packages/django/core/management/base.py&quot;, line 398, in execute
output = self.handle(*args, **options)
File &quot;/app/plants/management/commands/add_test_data.py&quot;, line 30, in handle
plant_class(**values) for values in data
File &quot;/app/plants/management/commands/add_test_data.py&quot;, line 30, in &lt;listcomp&gt;
plant_class(**values) for values in data
File &quot;/usr/local/lib/python3.7/site-packages/django/db/models/base.py&quot;, line 498, in __init__
_setattr(self, prop, kwargs[prop])
File &quot;/usr/local/lib/python3.7/site-packages/django/db/models/fields/related_descriptors.py&quot;, line 547, in __set__
% self._get_set_deprecation_msg_params(),
TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use usda_zone.set() instead.

Is it possible to create objects using a for loop with set() and not to write code for every ManyToManyField?

答案1

得分: 1

使用set()方法创建ManyToManyFields的对象,如下所示:

def create_data(self, fields_and_names):
    data = {}
    for field_name, field_model in fields_and_names.items():
        if ('ptr' not in field_name
                and 'synonym' not in field_name
                and field_name != 'id'):
            if isinstance(field_model, CharField):
                data[field_name] = self.get_string_random()
            elif isinstance(field_model, DecimalField):
                data[field_name] = self.get_number_random()
            elif isinstance(field_model, ManyToManyField):
                choices = field_model.related_model.objects.all()
                data[field_name] = set(choices.order_by('?')[:randint(1, len(choices))])
    return data

在这里,order_by('?')用于随机排序选择并使用切片选择随机数量的选择项。

编辑

尝试使用以下方法:

from random import sample

# ...

if isinstance(field_model, ManyToManyField):
    choices = field_model.related_model.objects.all()
    count = randint(1, len(choices))
    data[field_name] = set(sample(choices, count))

编辑 2

尝试首先创建没有多对多关系的对象,然后使用set方法添加相关对象,如下所示:

def create_data(self, fields_and_names):
    data = {}
    many_to_many_fields = []
    for field_name, field_model in fields_and_names.items():
        if ('ptr' not in field_name
                and 'synonym' not in field_name
                and field_name != 'id'):
            if isinstance(field_model, CharField):
                data[field_name] = self.get_string_random()
            elif isinstance(field_model, DecimalField):
                data[field_name] = self.get_number_random()
            elif isinstance(field_model, ManyToManyField):
                many_to_many_fields.append(field_name)
    obj = Deciduous.objects.create(**data)
    for field_name in many_to_many_fields:
        choices = [self.get_choice_random(Deciduous._meta.get_field(field_name)) for _ in range(randint(1, 3))]
        getattr(obj, field_name).set(choices)
    return data
英文:

Try to use the set() method to create objects for ManyToManyFields so:

def create_data(self, fields_and_names):
    data = {}
    for field_name, field_model in fields_and_names.items():
        if (&#39;ptr&#39; not in field_name
                and &#39;synonym&#39; not in field_name
                and field_name != &#39;id&#39;):
            if isinstance(field_model, CharField):
                data[field_name] = self.get_string_random()
            elif isinstance(field_model, DecimalField):
                data[field_name] = self.get_number_random()
            elif isinstance(field_model, ManyToManyField):
                choices = field_model.related_model.objects.all()
                data[field_name] = set(choices.order_by(&#39;?&#39;)[:randint(1, len(choices))])
    return data

Here, order_by(&#39;?&#39;) is used to randomly order the choices and select a random number of choices using slicing.

Edit

Try this:

from random import sample

# ...

if isinstance(field_model, ManyToManyField):
    choices = field_model.related_model.objects.all()
    count = randint(1, len(choices))
    data[field_name] = set(sample(choices, count))

Edit 2

Try to first create the object without the many-to-many relationships, then add the related objects using the set method so:


def create_data(self, fields_and_names):
    data = {}
    many_to_many_fields = []
    for field_name, field_model in fields_and_names.items():
        if (&#39;ptr&#39; not in field_name
                and &#39;synonym&#39; not in field_name
                and field_name != &#39;id&#39;):
            if isinstance(field_model, CharField):
                data[field_name] = self.get_string_random()
            elif isinstance(field_model, DecimalField):
                data[field_name] = self.get_number_random()
            elif isinstance(field_model, ManyToManyField):
                many_to_many_fields.append(field_name)
    obj = Deciduous.objects.create(**data)
    for field_name in many_to_many_fields:
        choices = [self.get_choice_random(Deciduous._meta.get_field(field_name)) for _ in range(randint(1, 3))]
        getattr(obj, field_name).set(choices)
    return data

huangapple
  • 本文由 发表于 2023年2月16日 16:33:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75469605.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定