From 9982c3b5291ff5afb7439facd6acfe1434498f51 Mon Sep 17 00:00:00 2001 From: VladislavOstapov Date: Tue, 4 Apr 2023 23:35:51 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D1=80=D1=82=D1=84=D0=BE=D0=BB?= =?UTF-8?q?=D0=B8=D0=BE=20=D0=B2=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=B2=D0=B8=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/admin.py | 8 +++++ api/api_errors.py | 12 +++---- api/api_methods.py | 82 ++++++++++++++++++++++++++++++++++++++++++++-- api/models.py | 33 +++++++++++++++++-- 4 files changed, 122 insertions(+), 13 deletions(-) diff --git a/api/admin.py b/api/admin.py index b8f62c9..fa6c036 100755 --- a/api/admin.py +++ b/api/admin.py @@ -37,6 +37,14 @@ class AccessTokenAdmin(admin.ModelAdmin): class OrderAdmin(admin.ModelAdmin): list_display = ['owner', 'phone', 'name', 'create_time', 'moderated', 'published'] readonly_fields = ['create_time'] + + +@admin.register(Portfolio) +class PortfolioAdmin(admin.ModelAdmin): + list_display = ['id', 'account', 'title', 'actual_date', 'actual_price'] + readonly_fields = ['actual_date', 'actual_price'] + + # # # @admin.register(OrderImage) diff --git a/api/api_errors.py b/api/api_errors.py index 393d948..228cb1a 100755 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -74,16 +74,14 @@ def __make_error(ex: Exception): def make_error_object(ex: Exception | list): try: data = { - "status": "error" - } - if type(ex) == list: - data["error"] = { + "status": "error", + "error": { "code": API_ERROR_MULTIPLY_ERRORS[0], "message": API_ERROR_MULTIPLY_ERRORS[1], - } + } if type(ex) == list else __make_error(ex) + } + if type(ex) == list: data["related"] = [__make_error(e) for e in ex] - else: - data["error"] = __make_error(ex) return data except BaseException as err: diff --git a/api/api_methods.py b/api/api_methods.py index 1b6f8db..aa7934c 100755 --- a/api/api_methods.py +++ b/api/api_methods.py @@ -1,4 +1,8 @@ +import random import time +from datetime import date as dt +import traceback + from .api_media_utils import * from .api_utils import * from .models import * @@ -39,7 +43,7 @@ class ApiAccount: return True @staticmethod - def __make_user_json(user: Account, self_using=False): + def make_user_json(user: Account, self_using=False): obj = { "id": user.id, "name": user.name, @@ -165,7 +169,7 @@ class ApiAccount: if user is None: return make_error_object(Exception(API_ERROR_NOT_FOUND, {"user": user_id})) - return api_make_response(ApiAccount.__make_user_json(user)) + return api_make_response(ApiAccount.make_user_json(user)) @staticmethod @api_method("account.edit", @@ -281,7 +285,7 @@ class ApiAccount: await sync_to_async(user.executoraccount.save)() - return api_make_response(ApiAccount.__make_user_json(user)) + return api_make_response(ApiAccount.make_user_json(user)) @staticmethod @api_method("account.changePhone", @@ -801,6 +805,78 @@ class ApiMedia: return api_make_response(res) +class ApiPortfolio: + # portfel.get(user_id=None, portfel_id=None, count=20, offset=0, order_by={date,-date, .... }) + # portfel.create(поля портфолио) + # portfel.delete(portfel_id) + @staticmethod + @api_method("portfolio.create", + doc="Создания объекта портфолио", + params=[ + ApiParamAccessToken(), + ApiParamStr(name="title", min_length=5, max_length=200, + description="Название выполненного заказа, 5-200 символов"), + ApiParamInt(name="date", required=False, + description="Дата в формате UNIX time, потом будет нормальный объект даты," + "если не передать то будет вставлена текущая дата"), + ApiParamFloat(name="price", description="Цена заказа, актуальная на момент выполнения") + ], returns="id созданного объекта") + async def create(access_token, title, date, price): + # проверка на роль, нужна сразу + if access_token.user.role != Account.ROLE_EXECUTOR: + raise Exception(API_ERROR_NOT_ALLOWED, "you must have executor role") + + try: + p = await Portfolio.objects.acreate(account=access_token.user, title=title, actual_price=price, + actual_date=(dt.fromtimestamp(date) if date is not None else None)) + return api_make_response({"portfolio_id": p.id}) + except Exception: + traceback.print_exc() + raise Exception(API_ERROR_INTERNAL_ERROR) + + @staticmethod + @api_method("portfolio.get", + doc="Получение портфолио или ленты портфолио", + params=[ + ApiParamAccessToken(), + ApiParamInt(name="owner_id", required=False, value_min=0, default=None, + description="ID пользователя, портфолио которого нужно вернуть."), + ApiParamInt(name="portfolio_id", required=False, value_min=0, default=None, + description="ID портфолио, которое нужно вернуть."), + ApiParamInt(name="count", required=False, value_min=1, value_max=100, default=20, + description="Количество объектов, по умолчанию 20"), + ApiParamInt(name="offset", required=False, value_min=0, default=0, + description="Количество объектов") + ], returns="") + async def get(access_token, owner_id, portfolio_id, count, offset): + res = Portfolio.objects.order_by('actual_date') + res = res.select_related('account', 'account__accountavatar', 'account__accountavatar__photo', + 'account__accountavatar__profile_background', + 'account__executoraccount') + + if owner_id is not None: + res = res.filter(account_id=owner_id) + + if portfolio_id is not None: + res = res.filter(id=portfolio_id) + + res = res[offset:offset + count] + + # выполняем fetch + objects = [{ + "owner": ApiAccount.make_user_json(item.account), + "id": item.id, + "title": item.title, + "actual_date": int(time.mktime(item.actual_date.timetuple())), + "actual_price": item.actual_price, + "photos": [random.randint(1, 10) for _ in range(random.randint(1, 5))] + } async for item in res] + + return api_make_response(objects) + + + + class DatabaseApi: # TODO переместить сюда форму заказа, список городов # def get_order_form diff --git a/api/models.py b/api/models.py index 7c9b060..0e0cd07 100755 --- a/api/models.py +++ b/api/models.py @@ -300,9 +300,9 @@ class Order(models.Model): default=CHOICE_UNDEFINED, verbose_name="Тип помещения") number_of_rooms = models.SmallIntegerField(verbose_name='Количество комнат, -1 = студия', validators=[ - MinValueValidator(-1), - MaxValueValidator(100) - ]) + MinValueValidator(-1), + MaxValueValidator(100) + ]) is_balcony = models.BooleanField(default=False, verbose_name="Балкон") is_loggia = models.BooleanField(default=False, verbose_name="Лоджия") @@ -402,6 +402,33 @@ class Order(models.Model): else: return q[0] + +class Portfolio(models.Model): + account = models.ForeignKey(Account, on_delete=models.CASCADE, verbose_name="Аккаунт") + + title = models.CharField(max_length=200, verbose_name="Название") + actual_date = models.DateField(verbose_name="Дата выполнения", default=datetime.now) + actual_price = models.DecimalField(max_digits=12, decimal_places=2, blank=False, verbose_name="Цена") + + def __str__(self): + return f"{self.id}: \"{self.title}\"" + + +class PortfolioPhoto(models.Model): + portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE, verbose_name="Портфолио") + photo = models.ForeignKey(Media, on_delete=models.SET_NULL, null=True, verbose_name="Аватар") + is_preview = models.BooleanField(verbose_name="Это главная фотография") + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=['portfolio', 'photo'], name='unique_portfoliophoto_photo' + ) + ] + + # TODO добавить проверку того, чтобы нельзя было приложить медиа другого юзера + + # def _upload_image_filename(instance, filename): # name, ext = os.path.splitext(filename) # fn = sha256((str(datetime.now()) + name).encode('utf-8')).hexdigest() + ext