|
1 | 1 | from flask.views import MethodView |
2 | 2 | from flask_jwt_extended import jwt_required |
3 | 3 | from flask_smorest import Blueprint, abort |
4 | | -from sqlalchemy import exists |
| 4 | +from psycopg2.errors import UniqueViolation |
| 5 | +from sqlalchemy import UniqueConstraint, exists |
| 6 | +from sqlalchemy.exc import IntegrityError |
5 | 7 |
|
6 | 8 | from app import db |
7 | 9 | from app.models import ( |
8 | 10 | Category, |
9 | 11 | Product, |
10 | 12 | Subcategory, |
11 | 13 | category_subcategory, |
12 | | - subcategory_product, |
13 | 14 | ) |
14 | 15 | from app.schemas import ( |
15 | 16 | CategoriesOut, |
|
27 | 28 | class CategoryCollection(MethodView): |
28 | 29 | init_every_request = False |
29 | 30 |
|
| 31 | + @staticmethod |
| 32 | + def _get_name_unique_constraint(): |
| 33 | + name_col = Category.__table__.c.name |
| 34 | + return next( |
| 35 | + con |
| 36 | + for con in Category.__table__.constraints |
| 37 | + if isinstance(con, UniqueConstraint) |
| 38 | + and len(con.columns) == 1 |
| 39 | + and con.columns.contains_column(name_col) |
| 40 | + ) |
| 41 | + |
| 42 | + _NAME_UNIQUE_CONSTRAINT = _get_name_unique_constraint() |
| 43 | + |
30 | 44 | @bp.response(200, CategoriesOut) |
31 | 45 | def get(self): |
32 | 46 | """ |
@@ -88,8 +102,18 @@ def post(self, data): |
88 | 102 |
|
89 | 103 | category.subcategories = subcategories |
90 | 104 |
|
91 | | - db.session.add(category) |
92 | | - db.session.commit() |
| 105 | + try: |
| 106 | + db.session.add(category) |
| 107 | + db.session.commit() |
| 108 | + except IntegrityError as ie: |
| 109 | + db.session.rollback() |
| 110 | + if ( |
| 111 | + isinstance(ie.orig, UniqueViolation) |
| 112 | + and ie.orig.diag.constraint_name |
| 113 | + == CategoryCollection._NAME_UNIQUE_CONSTRAINT.name |
| 114 | + ): |
| 115 | + abort(409, message="Category with this name already exists") |
| 116 | + raise |
93 | 117 |
|
94 | 118 | return category |
95 | 119 |
|
@@ -176,7 +200,18 @@ def put(self, data, id): |
176 | 200 |
|
177 | 201 | category.subcategories.extend(subcategories) |
178 | 202 |
|
179 | | - db.session.commit() |
| 203 | + try: |
| 204 | + db.session.commit() |
| 205 | + except IntegrityError as ie: |
| 206 | + db.session.rollback() |
| 207 | + if ( |
| 208 | + isinstance(ie.orig, UniqueViolation) |
| 209 | + and ie.orig.diag.constraint_name |
| 210 | + == category_subcategory.primary_key.name |
| 211 | + ): |
| 212 | + abort(409, message="Category and subcategory already linked") |
| 213 | + raise |
| 214 | + |
180 | 215 | return category |
181 | 216 |
|
182 | 217 | @jwt_required() |
@@ -277,14 +312,9 @@ def get(self, id, page): |
277 | 312 | abort(404) |
278 | 313 |
|
279 | 314 | products = ( |
280 | | - Product.query.join(subcategory_product) |
281 | | - .join( |
282 | | - category_subcategory, |
283 | | - onclause=subcategory_product.c.subcategory_id |
284 | | - == category_subcategory.c.subcategory_id, |
| 315 | + Product.query.filter( |
| 316 | + Product.subcategories.any(Subcategory.categories.any(id=id)) |
285 | 317 | ) |
286 | | - .filter(category_subcategory.c.category_id == id) |
287 | | - .distinct() |
288 | 318 | .order_by(Product.id.asc()) |
289 | 319 | .paginate(page=page, per_page=CategoryProducts._PER_PAGE, error_out=False) |
290 | 320 | ) |
|
0 commit comments