YAML для создания тестовых данных

Сегодня в качестве эксперимента я пощупал формат описания данных YAML и успешно заюзал его для наполнения проекта тестовыми данными. В начале я хотел просто хранить множество данных в YAML-файле, затем скриптом доставать известные заранее ключи и записывать их в нужные модели, но затем как всегда душе захотелось большего и я решил хранить в YAML также информацию о названиях моделей и полей. То есть теперь одним скриптом я могу парсить разные YAML-файлы.

Конечно, хотелось бы это всё красочно расписать, да больно неохото палец сосать, так что выкладываю код и баста!

Вот это YAML-файл, по которому генерятся данные для различных моделей. Обратите внимание, это не просто модели в себе, они связаны друг с другом отношениями.


meta:
order: [Agency, Client, User, Compaign]
defaults:
User:
is_active: True

Agency:
- name: Суперстар Агенство
ref: star
- name: Unlimited Success
ref: success

User:
# Agency managers
- username: mars
agency: {Agency: star}
is_staff: True
is_superuser: True
- username: boss
agency: {Agency: success}
is_staff: True
is_superuser: True

# Client managers
- username: balt_manager
client: {Client: baltika}
- username: blat_manager
client: {Client: gazprom}

Client:
- name: Балтика
ref: baltika
- name: Газпром
ref: gazprom

Compaign:
- name: Пей, пока жив
client: {Client: baltika}
- name: Наше пиво не токсично
client: {Client: baltika}
- name: А у нас на кухне газ
client: {Client: gazprom}


А вот собственно скрипт для парсинга:


#!/usr/bin/env python
"""
This module fill project database with objects described in YAML file.
"""

import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

import yaml
import logging
from itertools import chain

from agency.models import Agency
from django.contrib.auth.models import User
from task.models import Compaign
from client.models import Client

def parse():
conf = yaml.load(open('init.ya').read())
meta = conf.pop('meta')
objects = {}

for model_name in meta['order']:
model = globals()[model_name]

logging.debug('Deleting all %s instances' % model_name)
model.objects.all().delete()

defaults = meta['defaults'].get(model_name, {})

for item in conf[model_name]:
if 'ref' in item:
ref = item.pop('ref')
else:
ref = None

obj = model()
m2m = {}

for key, value in chain(item.iteritems(), defaults.iteritems()):
if isinstance(value, dict):
fk_model, fk_ref = value.items()[0]
setattr(obj, key, objects[fk_model][fk_ref])
elif isinstance(value, (dict, list)):
for m2m_value in value:
fk_model, fk_ref = m2m_value.items()[0]
m2m.setdefault(key, []).append(objects[fk_model][fk_ref])
else:
setattr(obj, key, value)
obj.save()

for key, values in m2m.iteritems():
setattr(obj, key, values)

logging.debug(u'New %s instance: %s' % (model_name, obj))

if ref:
objects.setdefault(model_name, {})[ref] = obj


if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
parse()



Ну как, это какой-то велосипед аццкий или здравое зерно всё таки есть? :-)

UPD: Подправил скрипт, начальная версия оказывается неправильно обрабатывала ForeignKey. Теперь правильно + умеет many to many отношения )
Add post to:   Google Slashdot Yahoo Digg Technorati Delicious Bobrdobr.ru Newsland.ru Smi2.ru Rumarkz.ru Vaau.ru Memori.ru Rucity.com Moemesto.ru News2.ru Mister-Wong.ru Yandex.ru Myscoop.ru 100zakladok.ru
Make comment

Comments

да, это лучше выглядит по сравнению со стандартной django-вской сериализацией в YAML и обратно. понравилось использование ref'ов. для ручками заданных тестовых данных это плюс. зачем в meta нужен order, я не понял.

Потому что мне было лень писать логику вычисляющую этот порядок: в начале надо создать объекты, от которые зависят другие объекты. Я решил это в лоб сделать, хотя, конечно, это не совсем красиво и по хорошему надо переписать )

А обратная задача, из моделей вылить вот в такой вот YAML? Чтобы тестовые данные можно было просто в админке и создавать.

Django'вская сериализация хороша тем что работает в обе стороны. А JSON не так уж ужасен.

Ну, обратная задача вполне решается средствами ./manage.py: dumpdata, а затем loaddata.

Required. 30 chars of fewer.

Required.

captcha image Please, enter symbols, which you see on the image