У меня есть простая структура таблицы с родительской таблицей (категория) и дочерней таблицей (передача).
Я думаю, что я настроил модели правильно, следуя инструкциям 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}]