Is there a way in a seed_data.yaml file to autogenerate models on which first model is dependent upon?

北战南征 提交于 2021-01-27 21:36:16

问题


I'm using Django 2.0, Python 3.7, and MySql 5. I have the following two models, the second dependent on the first ...

class CoopType(models.Model):
    name = models.CharField(max_length=200, null=False)

    class Meta:
        unique_together = ("name",)


class Coop(models.Model):
    name = models.CharField(max_length=250, null=False)
    type = models.ForeignKey(CoopType, on_delete=None)
    address = AddressField(on_delete=models.CASCADE)
    enabled = models.BooleanField(default=True, null=False)
    phone = PhoneNumberField(null=True)
    email = models.EmailField(null=True)
    web_site = models.TextField()

I'm creating some seed data for the second model. I was wondering if there was a way to auto-generate data for the first model from within the second model. I tried this ...

- model: maps.coop
  pk: 1
  fields:
    name: "1871"
    type:
      pk: null
      name: Coworking Space
    address:
      street_number: 222
      route: 1212
      raw: 222 W. Merchandise Mart Plaza, Suite 1212
      formatted: 222 W. Merchandise Mart Plaza, Suite 1212
      locality:
        name: Chicago
        postal_code: 60654
        state:
          code: IL
    enabled: True
    phone:
    email:
    web_site: "http://www.1871.com/"
    latitude: 41.88802611
    longitude: -87.63612199

but I get this error when running the seed data ...

(env) localhost:maps davea$ python scripts/parse_coop_csv.py ~/Downloads/chicommons_prep.xlsx\ -\ Mapping\ Sheet.csv > maps/fixtures/seed_data.yaml
(env) localhost:maps davea$ python manage.py loaddata maps/fixtures/seed_data.yaml
Traceback (most recent call last):
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/db/models/fields/__init__.py", line 923, in to_python
    return int(value)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'dict'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/serializers/python.py", line 157, in Deserializer
    data[field.attname] = model._meta.get_field(field_name).to_python(field_value)
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/db/models/fields/__init__.py", line 928, in to_python
    params={'value': value},
django.core.exceptions.ValidationError: ["'{'pk': None, 'name': 'Coworking Space'}' value must be an integer."]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/management/__init__.py", line 371, in execute_from_command_line
    utility.execute()
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/management/__init__.py", line 365, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/management/base.py", line 335, in execute
    output = self.handle(*args, **options)
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/management/commands/loaddata.py", line 72, in handle
    self.loaddata(fixture_labels)
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/management/commands/loaddata.py", line 113, in loaddata
    self.load_label(fixture_label)
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/management/commands/loaddata.py", line 168, in load_label
    for obj in objects:
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/serializers/pyyaml.py", line 73, in Deserializer
    yield from PythonDeserializer(yaml.load(stream, Loader=SafeLoader), **options)
  File "/Users/davea/Documents/workspace/chicommons/maps/env/lib/python3.7/site-packages/django/core/serializers/python.py", line 159, in Deserializer
    raise base.DeserializationError.WithData(e, d['model'], d.get('pk'), field_value)
django.core.serializers.base.DeserializationError: Problem installing fixture '/Users/davea/Documents/workspace/chicommons/maps/maps/maps/fixtures/seed_data.yaml': ["'{'pk': None, 'name': 'Coworking Space'}' value must be an integer."]: (maps.coop:pk=1) field_value was '{'pk': None, 'name': 'Coworking Space'}'

Is there any way to do this elegantly in the seed_data.yaml file?


回答1:


Solution: Update your fixtures as per the following

- model: test_app.coop
  pk: 1
  fields:
    name: '1871'
    type:
    - Coworking Space
    enabled: true
    email: null
    web_site: ''
- model: test_app.coop
  pk: 2
  fields:
    name: '18715'
    type:
    - Coworking Space 2
    enabled: true
    email: null
    web_site: ''
- model: test_app.coop
  pk: 3
  fields:
    name: '187156'
    type:
    - Coworking Space 3
    enabled: true
    email: null
    web_site: ''

And add a custom manager in your CoopType model

class CoopManager(models.Manager):

    def get_by_natural_key(self, name):
        return self.get_or_create(name=name)[0]


class CoopType(models.Model):
    name = models.CharField(max_length=200)

    objects = CoopManager()

    class Meta:
        unique_together = ("name",)

Now when you load the fixtures it will create the CoopType objects if not already exists and then load Coop model

Explanation: This is the Django code responsible for deserializing the objects

# Handle FK fields
elif field.remote_field and isinstance(field.remote_field, models.ManyToOneRel):
    model = field.remote_field.model
    if field_value is not None:
        try:
            default_manager = model._default_manager
            field_name = field.remote_field.field_name
            if hasattr(default_manager, 'get_by_natural_key'):
                if hasattr(field_value, '__iter__') and not isinstance(field_value, six.text_type):
                    obj = default_manager.db_manager(db).get_by_natural_key(*field_value)
                    value = getattr(obj, field.remote_field.field_name)
                    # If this is a natural foreign key to an object that
                    # has a FK/O2O as the foreign key, use the FK value
                    if model._meta.pk.remote_field:
                        value = value.pk
                else:
                    value = model._meta.get_field(field_name).to_python(field_value)
                data[field.attname] = value
            else:
                data[field.attname] = model._meta.get_field(field_name).to_python(field_value)
        except Exception as e:
            raise base.DeserializationError.WithData(e, d['model'], d.get('pk'), field_value)
    else:
        data[field.attname] = None

Source: django.core.serializers.python line 152 onwards

As you can see if get_by_natural_key function is given in model and fixtures has nested data then it calls get_by_natural_key function to get the object. So in get_by_natural_key function we can use get_or_create and return the instance.

Please check that nested data in the Coop model has only args and not the kwargs because Django code pass args in this function.

PS: I am not sure how correct it is to use get_or_create in get_by_natural_key function but This is the only working solution for now.



来源:https://stackoverflow.com/questions/59940160/is-there-a-way-in-a-seed-data-yaml-file-to-autogenerate-models-on-which-first-mo

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!