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