英文:
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 ManyToManyField
s (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 ('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)
But I get:
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: 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 ('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
Here, order_by('?')
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 ('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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论