Как вставить с SQLAlchemy ORM при работе с отношениями один ко многим? - PullRequest
0 голосов
/ 28 апреля 2019

У меня есть простая структура таблицы с родительской таблицей (категория) и дочерней таблицей (передача). Я думаю, что я настроил модели правильно, следуя инструкциям SQLAlchemy ORM (отношение super_category_id может быть неправильным, я еще не дошел до этого).

TLDR в следующих двух точках:

  • при выполнении запроса POST с Json, представляющим объект Transfer с вложенным объектом Category, механизм ORM SQLAlchemy пытается снова вставить Category, потерпев неудачу, поскольку она уже существует, и, следовательно, нарушая уникальное ограничение.

  • при выполнении запроса POST с Json, представляющим объект Transfer. БЕЗ вложенного объекта Category. Я получаю нулевую ошибку ограничения, поскольку кажется, что внешний ключ (который не обнуляется) теряется при переводе в запрос

Я впервые использую какой-либо движок ORM, поэтому я публикую весь релевантный код, так как меня не удивит, если в нем будет несколько явных ошибок.

Категория

class BaseModel(db.Model):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True)
def get_id(model):
    return model.id

class Category(BaseModel):
__tablename__ = 'category'
name = db.Column('category_name', db.String, unique=True, nullable=False)
super_category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
transfers = db.relationship("Transfer", back_populates="category")

def to_dict(self):
    return {
        "id": self.id,
        "name": self.name,
        "super_category_id": self.super_category_id,
        "transfers": json.dumps(self.transfers[0].to_dict() if len(self.transfers) > 0 else {})
    }

@staticmethod
def from_dict(data):
    c = Category()
    c.id = data.get("id")
    c.name = data["name"]
    c.super_category_id = data.get("super_category_id")
    return c

Передача

class Transfer(BaseModel):
__tablename__ = 'transfer'
date = db.Column('transfer_date', db.Date, nullable=False)
amount = db.Column(db.Numeric(8, 2))
comment = db.Column(db.String)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False)
category = db.relationship('Category', back_populates='transfers')

def to_dict(self):
    return {
        "id": self.id,
        "date": self.date.strftime("%Y-%m-%d"),
        "amount": str(self.amount),
        "comment": self.comment,
        "category_id": self.category_id,
        "category_name": self.category.name
    }

@staticmethod
def from_dict(data):
    t = Transfer()
    t.id = data.get("id")
    t.date = data["date"]
    t.amount = data["amount"]
    t.comment = data.get("comment")
    t.category_id = data["category_id"]
    t.category = Category.from_dict(json.loads(json.dumps(data.get("category")))) if data.get("category") is not None else None
    return t 

Конечная точка API

@bp.route("/transfers", methods=["POST"])
def add_transfer():
    data = request.get_json() or {}
    if 'amount' not in data or 'category_id' not in data:
        return bad_request('must include amount and category_id')
    t = Transfer.from_dict(data)
    print("ADDING %s" % t)
    db.session.add(t)
    try:
        db.session.commit()
        resp = jsonify(t.to_dict())
        resp.status_code = 201
    except IntegrityError as e:
        db.session.rollback()
        resp = bad_request(str(e))

    resp.headers['Location'] = url_for('core.index')
    return resp

Первый сценарий Разноска запроса с вложенным объектом категории

{
    "amount": "60.00",
    "category_id": 1,
    "comment": null,
    "date": "2019-04-18",
    "category": {
        "id": 1,
        "name": "bolletta"
    }
}

print (конечная точка API, строка 7) выдает это сообщение: ADDING Transfer{id:None, date:2019-04-18, amount:60.00, category_id:1, category:Category{id:1, name:bolletta, super_category_id:None}}

и ошибка psycopg2.errors.UniqueViolation) duplicate key value violates unique constraint \"category_pkey\"\nDETAIL: Key (id)=(1) already exists.

Второй сценарий Опубликовать запрос без вложенного объекта категории

{
    "amount": "60.00",
    "category_id": 1,
    "comment": null,
    "date": "2019-04-18"
}

print (конечная точка API, строка 7) выдает следующее сообщение: ADDING Transfer{id:None, date:2019-04-18, amount:60.00, category_id:1, category:None}

и ошибка (psycopg2.errors.NotNullViolation) null value in column \"category_id\" violates not-null constraint\nDETAIL: Failing row contains (8, 2019-04-18, 60.00, null, null).\n\n[SQL: INSERT INTO transfer (transfer_date, amount, comment, category_id) VALUES (%(transfer_date)s, %(amount)s, %(comment)s, %(category_id)s) RETURNING transfer.id]\n[parameters: {'transfer_date': '2019-04-18', 'amount': '60.00', 'comment': None, 'category_id': None}]

...