Добавление тегов в портфолио, добавление вадилатора почты, добавление параметров телеофн и почта в создание заказа
This commit is contained in:
parent
451a8b9bc3
commit
82a957f659
@ -52,6 +52,12 @@ class PortfolioAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ['actual_date', 'actual_price']
|
||||
|
||||
|
||||
@admin.register(PortfolioPhoto)
|
||||
class PortfolioAdmin(admin.ModelAdmin):
|
||||
# list_display = ['id', 'account', 'title', 'actual_date', 'actual_price']
|
||||
# readonly_fields = ['actual_date', 'actual_price']
|
||||
pass
|
||||
|
||||
#
|
||||
#
|
||||
# @admin.register(OrderImage)
|
||||
|
@ -1,9 +1,6 @@
|
||||
import random
|
||||
import time
|
||||
from datetime import date as dt
|
||||
import traceback
|
||||
|
||||
from django.http import HttpResponseNotFound
|
||||
|
||||
from .api_media_utils import *
|
||||
from .api_utils import *
|
||||
@ -194,9 +191,11 @@ class ApiAccount:
|
||||
ApiParamEnum(name="city", description="Город, в котором находится ползователь: {choices}",
|
||||
required=False, choices=CITIES_CHOICES),
|
||||
ApiParamInt(name="photo", required=False, default=None,
|
||||
description="ID медиа, которое будет использоваться в качестве фото профиля"),
|
||||
description="ID медиа, которое будет использоваться в качестве фото профиля, "
|
||||
"-1 для сброса"),
|
||||
ApiParamInt(name="profile_background", required=False, default=None,
|
||||
description="ID медиа, которое будет использоваться в качестве банера профиля")
|
||||
description="ID медиа, которое будет использоваться в качестве банера профиля, "
|
||||
"-1 для сброса")
|
||||
],
|
||||
returns="Вернет основную информацию о пользователе, иначе ошибки")
|
||||
async def edit(access_token, name, surname, about, executor_type, executor_inn, city, photo, profile_background):
|
||||
@ -220,9 +219,12 @@ class ApiAccount:
|
||||
need_save = True
|
||||
|
||||
if photo is not None:
|
||||
p = await Media.objects.filter(pk=photo).select_related('owner').afirst()
|
||||
if p is None:
|
||||
raise Exception(API_ERROR_NOT_FOUND, 'field "photo" not found')
|
||||
if photo != -1:
|
||||
p = await Media.objects.filter(pk=photo).select_related('owner').afirst()
|
||||
if p is None:
|
||||
raise Exception(API_ERROR_NOT_FOUND, 'field "photo" not found')
|
||||
else:
|
||||
p = None
|
||||
|
||||
if p.owner.id == user.id and p.extension in Media.PHOTO_EXTENSIONS:
|
||||
if not hasattr(user, 'accountavatar'):
|
||||
@ -233,9 +235,12 @@ class ApiAccount:
|
||||
raise Exception(API_ERROR_NOT_FOUND, 'field "photo" not correct')
|
||||
|
||||
if profile_background is not None:
|
||||
p = await Media.objects.filter(pk=profile_background).select_related('owner').afirst()
|
||||
if p is None:
|
||||
raise Exception(API_ERROR_NOT_FOUND, 'field "profile_background" not found')
|
||||
if profile_background != -1:
|
||||
p = await Media.objects.filter(pk=profile_background).select_related('owner').afirst()
|
||||
if p is None:
|
||||
raise Exception(API_ERROR_NOT_FOUND, 'field "profile_background" not found')
|
||||
else:
|
||||
p = None
|
||||
|
||||
if p.owner.id == user.id and p.extension in Media.PHOTO_EXTENSIONS:
|
||||
if not hasattr(user, 'accountavatar'):
|
||||
@ -528,9 +533,8 @@ class ApiOrder:
|
||||
required=False, default=""),
|
||||
ApiParamStr(name='video_link', max_length=160, description="Ссылка на видео",
|
||||
required=False, default=""),
|
||||
|
||||
# email = models.EmailField(null=True, blank=True, verbose_name="Email")
|
||||
# phone = models.CharField(null=True, blank=True, max_length=16, verbose_name="Телефон")
|
||||
ApiParamPhone(max_length=60, required=False, default=None),
|
||||
ApiParamEmail(required=False, default=None)
|
||||
],
|
||||
returns="ID созданного заказа, иначе одну из ошибок")
|
||||
async def create(**kwargs):
|
||||
@ -541,7 +545,7 @@ class ApiOrder:
|
||||
order = await Order.objects.acreate(owner=access_token.user, **kwargs)
|
||||
return api_make_response({"order_id": order.id})
|
||||
except ValidationError as ve:
|
||||
return _make_model_validation_errors(ve, API_ERROR_USER_MODIFY)
|
||||
return _make_model_validation_errors(ve, API_ERROR_OBJECT_VALIDATION)
|
||||
|
||||
@staticmethod
|
||||
@api_method("order.setPublished",
|
||||
@ -689,15 +693,15 @@ class ApiMedia:
|
||||
@api_method("media.get",
|
||||
doc="Получение медиа",
|
||||
params=[
|
||||
ApiRequestParam(),
|
||||
ApiParamAccessToken(),
|
||||
ApiParamInt(name="media_id", description="ID медиа",
|
||||
value_min=0, value_max=1000000000),
|
||||
], returns="<code>медиа</code>, в противном случае ошибку")
|
||||
async def get(request, access_token, media_id):
|
||||
async def get(access_token, media_id):
|
||||
m = await Media.objects.filter(Q(owner=access_token.user) |
|
||||
Q(owner__accountavatar__photo=media_id) |
|
||||
Q(owner__accountavatar__profile_background_id=media_id)).filter(pk=media_id).afirst()
|
||||
Q(accountavatar__photo=media_id) |
|
||||
Q(accountavatar__profile_background=media_id) |
|
||||
Q(portfoliophoto=media_id)).filter(pk=media_id).afirst()
|
||||
|
||||
if m is not None:
|
||||
try:
|
||||
@ -753,8 +757,9 @@ class ApiPortfolio:
|
||||
ApiParamFloat(name="price", description="Цена заказа, актуальная на момент выполнения"),
|
||||
ApiParamFloat(name='square', value_max=99999.99, value_min=1.0,
|
||||
description='Площадь в м²'),
|
||||
ApiParamTags(name='tags', tags=Portfolio.TAGS_NAMES, required=False, default=[])
|
||||
], returns="<code>id</code> созданного объекта")
|
||||
async def create(access_token, title, date, price, square):
|
||||
async def create(access_token, title, date, price, square, tags):
|
||||
# проверка на роль, нужна сразу
|
||||
if access_token.user.role != Account.ROLE_EXECUTOR:
|
||||
raise Exception(API_ERROR_NOT_ALLOWED, "you must have executor role")
|
||||
@ -762,7 +767,7 @@ class ApiPortfolio:
|
||||
try:
|
||||
p = await Portfolio.objects.acreate(account=access_token.user, actual_price=price, square=square,
|
||||
actual_date=(dt.fromtimestamp(date) if date is not None else None),
|
||||
title=title,)
|
||||
title=title, attributes={"tags": tags})
|
||||
return api_make_response({"portfolio_id": p.id})
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
@ -780,9 +785,10 @@ class ApiPortfolio:
|
||||
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="Количество объектов")
|
||||
description="Количество объектов"),
|
||||
ApiParamTags(name='tags', tags=Portfolio.TAGS_NAMES, required=False, default=None)
|
||||
], returns="")
|
||||
async def get(access_token, owner_id, portfolio_id, count, offset):
|
||||
async def get(access_token, owner_id, portfolio_id, tags, count, offset):
|
||||
res = Portfolio.objects.order_by('actual_date')
|
||||
res = res.select_related('account', 'account__accountavatar', 'account__accountavatar__photo',
|
||||
'account__accountavatar__profile_background',
|
||||
@ -794,6 +800,9 @@ class ApiPortfolio:
|
||||
if portfolio_id is not None:
|
||||
res = res.filter(id=portfolio_id)
|
||||
|
||||
if tags is not None:
|
||||
res = res.filter(attributes__tags__contains=tags)
|
||||
|
||||
res = res[offset:offset + count]
|
||||
|
||||
# выполняем fetch
|
||||
@ -805,7 +814,8 @@ class ApiPortfolio:
|
||||
"publish_date": int(time.mktime(item.publish_date.timetuple())),
|
||||
"actual_price": float(item.actual_price),
|
||||
"square": float(item.square),
|
||||
"photos": [random.randint(4, 28) for _ in range(random.randint(1, 5))]
|
||||
"photos": [random.randint(4, 28) for _ in range(random.randint(1, 5))],
|
||||
"attributes": item.attributes
|
||||
} async for item in res]
|
||||
|
||||
return api_make_response(objects)
|
||||
|
@ -288,3 +288,45 @@ class ApiParamVerifyCode(ApiParamInt):
|
||||
description="Код верификации (требуется если клиенту будет отправлена "
|
||||
"одна из ошибок <i>верификации</i>)", **kwargs):
|
||||
super().__init__(name=name, required=False, value_min=0, value_max=9999, description=description, **kwargs)
|
||||
|
||||
|
||||
class ApiParamEmail(ApiParamStr):
|
||||
def __init__(self, name="email",
|
||||
description="Почта", **kwargs):
|
||||
super().__init__(name=name, description=description,
|
||||
regex="^[\\w\\-.]+@([\\w\\-]+\\.)+[\\w\\-]{2,4}$", **kwargs)
|
||||
|
||||
|
||||
class ApiParamTags(ApiParamStr):
|
||||
def __init__(self, tags: list[list[str, str] | tuple[str, str]], name="tags", default=None,
|
||||
description="Один или несколько тегов из списка: {tags}"
|
||||
" Теги перечисляются через запятую, без кавычек", **kwargs):
|
||||
super().__init__(name=name, description=description,
|
||||
regex="^[\\w\\_]+(,[\\w\\_]+)*$", default=None, **kwargs)
|
||||
self.tags = tags
|
||||
self.__tags_names = [i[0] for i in tags]
|
||||
self.__default_tags = default
|
||||
|
||||
def validate(self, value):
|
||||
items = super(ApiParamTags, self).validate(value)
|
||||
if items is not None:
|
||||
items = value.split(',')
|
||||
# проверка того, что параметры входят в список
|
||||
for i in items:
|
||||
if i not in self.__tags_names:
|
||||
_make_invalid_argument_value_error(self.name, value, "unexpected",
|
||||
f"expected items in {self.__tags_names}")
|
||||
else:
|
||||
items = self.__default_tags
|
||||
|
||||
return items
|
||||
|
||||
def get_doc(self):
|
||||
doc = super().get_doc()
|
||||
ch = "<ul>"
|
||||
for c in self.tags:
|
||||
if c[0] != '':
|
||||
ch += f"<li>{c[0]} - {c[1]}</li>"
|
||||
ch += "</ul>"
|
||||
return doc.replace('{tags}', ch)
|
||||
|
||||
|
@ -407,6 +407,10 @@ class Order(models.Model):
|
||||
return q[0]
|
||||
|
||||
|
||||
def _portfolio_default_attrs():
|
||||
return {"tags": []}
|
||||
|
||||
|
||||
class Portfolio(models.Model):
|
||||
account = models.ForeignKey(Account, on_delete=models.CASCADE, verbose_name="Аккаунт")
|
||||
|
||||
@ -416,13 +420,26 @@ class Portfolio(models.Model):
|
||||
actual_price = models.DecimalField(max_digits=12, decimal_places=2, blank=False, verbose_name="Цена")
|
||||
square = models.DecimalField(max_digits=7, decimal_places=2, blank=False, verbose_name="Площадь в м²")
|
||||
|
||||
TAGS_NAMES = [
|
||||
("housings", "Квартиры"),
|
||||
("private_houses", "Частные дома"),
|
||||
("country_houses", "Дачные дома"),
|
||||
("penthouses", "Пентхаусы"),
|
||||
("apartments", "Апартаменты"),
|
||||
("rooms", "Комнаты"),
|
||||
("kitchens", "Кухни"),
|
||||
("bathrooms", "Ванные комнаты"),
|
||||
("child_rooms", "Детские комнаты")
|
||||
]
|
||||
attributes = models.JSONField(verbose_name="Атрибуты", default=_portfolio_default_attrs)
|
||||
|
||||
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="Аватар")
|
||||
photo = models.ForeignKey(Media, on_delete=models.CASCADE, verbose_name="Фотография")
|
||||
is_preview = models.BooleanField(verbose_name="Это главная фотография")
|
||||
|
||||
class Meta:
|
||||
|
@ -1,5 +1,5 @@
|
||||
psycopg2
|
||||
psycopg2-binary==2.9.6
|
||||
django==4.1.7
|
||||
requests==2.28.2
|
||||
python-dotenv
|
||||
boto3
|
||||
boto3==1.26.120
|
||||
|
Reference in New Issue
Block a user