Обновлен список городов - теперь он не в базе данных, а в статических объектах. Обновлена работа с медиа - теперь их можно полноценно загружать и скачивать с сервера.

This commit is contained in:
2023-03-28 14:10:28 +03:00
parent d4158ae1c0
commit a9d3a34c0b
8 changed files with 207 additions and 86 deletions

View File

@@ -1,8 +1,11 @@
import random
from django.core.exceptions import ValidationError
from django.http import HttpResponse, HttpResponseBadRequest
from .api_utils import *
from .api_params import *
from .models import *
from .api_media_utils import *
import time
@@ -41,7 +44,7 @@ class ApiAccount:
return True
@staticmethod
def __make_user_json(user: Account):
def __make_user_json(user: Account, self_using=False):
obj = {
"id": user.id,
"name": user.name,
@@ -49,10 +52,19 @@ class ApiAccount:
"phone": user.phone,
"email": user.email,
"about": user.about,
"city": {"code": user.city.code, "name": user.city.name} if user.city is not None else None,
"city": {"code": user.city, "name": CITIES_CHOICES[user.city]} if user.city is not None else None,
"register_datetime": int(time.mktime(user.register_datetime.timetuple())),
"role": user.role,
}
if hasattr(user, 'accountavatar'):
obj["avatar"] = user.accountavatar.photo.id if user.accountavatar.photo is not None else None
obj["profile_background"] = \
user.accountavatar.profile_background.id if user.accountavatar.photo is not None else None
else:
obj["avatar"] = None
obj["profile_background"] = None
if user.role == Account.ROLE_EXECUTOR:
obj |= {
"executor_type": user.executoraccount.executor_type,
@@ -135,7 +147,7 @@ class ApiAccount:
params=[ApiParamAccessToken()],
returns="Стандартный ответ успеха, в случае успеха")
async def delete(access_token):
user = access_token.user
user = access_token.owner
await sync_to_async(user.delete)()
return api_make_response({})
@@ -151,9 +163,9 @@ class ApiAccount:
returns="Поля пользователя (name, surname, email, phone и прочие).")
async def get(access_token, user_id):
if user_id is None:
user = access_token.user
user = access_token.owner
else:
user = await access_token.user.get_by_id(user_id)
user = await access_token.owner.get_by_id(user_id)
if user is None:
return make_error_object(Exception(API_ERROR_NOT_FOUND, {"user": user_id}))
@@ -177,11 +189,11 @@ class ApiAccount:
description="ИНН исполнителя (только для роли исполнитель): "
"12 цифр если исполнитель - физ.лицо и 10 цифр если это юр. лицо"),
ApiParamEnum(name="city", description="Город, в котором находится ползователь: {choices}",
required=False, choices=City.to_choices)
required=False, choices=CITIES_CHOICES)
],
returns="Вернет основную информацию о пользователе, иначе ошибки")
async def edit(access_token, name, surname, about, executor_type, executor_inn, city):
user = access_token.user
user = access_token.owner
executor_need_save, need_save = False, False
if name is not None:
@@ -256,7 +268,7 @@ class ApiAccount:
],
returns="Вернет стандартный объект успеха")
async def change_phone(access_token, password, phone, code):
user = access_token.user
user = access_token.owner
if not user.check_password(password):
raise Exception(API_ERROR_INVALID_PASSWORD)
@@ -310,7 +322,7 @@ class ApiAccount:
class ApiSecurity:
@staticmethod
@api_method("security.listSessions",
doc="Получение сиписка сессий (кроме текущей)",
doc="Получение списка сессий (кроме текущей)",
params=[
ApiParamAccessToken(),
ApiParamPassword(description="Пароль пользователя, нужен для подтверждения личности")
@@ -319,7 +331,7 @@ class ApiSecurity:
async def list_sessions(access_token, password):
sessions = await access_token.list_sessions()
if not access_token.user.check_password(password):
if not access_token.owner.check_password(password):
raise Exception(API_ERROR_INVALID_PASSWORD)
return api_make_response({
@@ -338,14 +350,14 @@ class ApiSecurity:
@staticmethod
@api_method("security.removeOtherSessions",
doc="Получение сиписка сессий (кроме текущей)",
doc="Получение списка сессий (кроме текущей)",
params=[
ApiParamAccessToken(),
ApiParamPassword(description="Пароль пользователя, нужен для подтверждения личности")
],
returns="Вернет sessions: [{id: int, name: str, created: unix_timestamp}]")
async def remove_other_sessions(access_token, password):
if not access_token.user.check_password(password):
if not access_token.owner.check_password(password):
raise Exception(API_ERROR_INVALID_PASSWORD)
sessions = await access_token.list_sessions()
@@ -377,7 +389,7 @@ class ApiSecurity:
],
returns="Вернет стандартный отъект в случае успеха")
async def remove_session(access_token, password, session):
if not access_token.user.check_password(password):
if not access_token.owner.check_password(password):
raise Exception(API_ERROR_INVALID_PASSWORD)
await access_token.delete_session(session)
@@ -394,7 +406,7 @@ class ApiSecurity:
],
returns="Вернет стандартный объект успеха")
async def change_password(access_token, old_password, password):
user = access_token.user
user = access_token.owner
if not user.check_password(old_password):
raise Exception(API_ERROR_INVALID_PASSWORD, "old_password")
@@ -538,7 +550,7 @@ class ApiOrder:
# date_start = models.DateField(null=True, blank=True, default=None, verbose_name="Дата начала")
# date_end = models.DateField(null=True, blank=True, default=None, verbose_name="Дата окончания")
ApiParamEnum(name="address_city", description="Город: {choices}", choices=City.to_choices),
ApiParamEnum(name="address_city", description="Город: {choices}", choices=CITIES_CHOICES),
ApiParamStr(name='address_text', max_length=70, description="Улица, дом",
required=False, default="")
@@ -550,13 +562,11 @@ class ApiOrder:
access_token = kwargs.pop('access_token')
ApiOrder._check_write_permissions(access_token)
city = await City.get_by_code(kwargs.pop('address_city'))
try:
order = await Order.objects.acreate(owner=access_token.user, address_city=city, **kwargs)
order = await Order.objects.acreate(owner=access_token.owner, **kwargs)
return api_make_response({"order_id": order.id})
except ValidationError as ve:
return _make_model_validation_errors(ve, API_ERROR_USER_MODIFY)
return api_make_response({"order_id": order.id})
@staticmethod
@api_method("order.setPublished",
@@ -571,7 +581,7 @@ class ApiOrder:
ApiOrder._check_write_permissions(access_token)
query = Order.objects.filter(id=order_id)
order = await query.afirst()
if order.owner_id != access_token.user.id:
if order.owner_id != access_token.owner.id:
raise Exception(API_ERROR_ACCESS_DENIED, 'edit operation allowed only for owner')
await query.aupdate(published=value)
@@ -609,13 +619,13 @@ class ApiOrder:
if order_id is not None:
res = await query.aget(id=order_id)
if user_id is not None:
if access_token.user.id == res.owner_id or (res.published and res.moderated):
if access_token.owner.id == res.owner_id or (res.published and res.moderated):
return api_make_response([ApiOrder._order_to_json(res)])
else:
raise Exception(API_ERROR_NOT_ALLOWED, 'attempt access to closed order')
if user_id is not None:
user = await access_token.user.get_by_id(user_id)
user = await access_token.owner.get_by_id(user_id)
if user is None:
raise Exception(API_ERROR_NOT_FOUND, 'user')
if user.role != Account.ROLE_CUSTOMER:
@@ -629,29 +639,106 @@ class ApiOrder:
@staticmethod
def _check_write_permissions(access_token):
if not access_token.user.is_completed():
if not access_token.owner.is_completed():
raise Exception(API_ERROR_NEED_COMPLETED_ACCOUNT)
if access_token.user.role != Account.ROLE_CUSTOMER:
if access_token.owner.role != Account.ROLE_CUSTOMER:
raise Exception(API_ERROR_NOT_ALLOWED, 'you must be a customer')
class ApiMedia:
# поскольку media.upload это не совсем стандартная функция, обернем фейковый метод чтоб была документация
@staticmethod
def __filename_to_ext(filename: str):
formats = {
".jpeg": "jpeg",
".png": "png",
".pdf": "pdf"
}
for k in formats:
if filename.endswith(k):
return formats[k]
return None
@staticmethod
def __ext_to_content_type(ext: str):
formats = {
"jpeg": "image/jpeg",
"png": "image/png",
"pdf": "application/pdf"
}
if ext in formats:
return formats[ext]
return "application/binary"
@staticmethod
@api_method("media.upload",
doc="Загрузка медиа на сервер. Вызывать методом POST.",
doc="Загрузка медиа на сервер. Вызывать методом POST. Обязательно должен быть прикреплен файл"
" с именем поля 'file'",
params=[
ApiRequestParam(),
ApiParamAccessToken(),
ApiParamStr(name="filename", description="Название файла",
min_length=5, max_length=60),
], returns="<code>id</code> медиа, в противном случае ошибку")
def upload(request, access_token, filename):
# ну шож, метод фейковый, все проверки нужно сделать руками
async def upload(request, access_token):
if request.method != "POST":
return make_error_object(Exception(API_ERROR_INVALID_REQUEST, "method must be executed http POST method"))
return HttpResponse("Да пошел ты нах со своим аплоадом")
if 'file' not in request.FILES:
return make_error_object(Exception(API_ERROR_INVALID_REQUEST,
"you must attach file with field-name 'file'"))
filename = request.FILES['file'].name
print(filename, type(filename))
# if not access_token.user.is_completed():
# return make_error_object(Exception(API_ERROR_NEED_COMPLETED_ACCOUNT))
ext = ApiMedia.__filename_to_ext(filename)
if ext is None:
return make_error_object(Exception(API_ERROR_INVALID_REQUEST, "unsupported file extension"))
try:
storage_name = Media.generate_storage_name(filename, datetime.now(), access_token.owner)
await sync_to_async(s3_upload_from_buffer)(storage_name, request.FILES['file'].read())
m = await Media.objects.acreate(user=access_token.owner, original_name=filename,
extension=ext, storage_name=storage_name)
return api_make_response({'media_id': m.id})
except Exception:
traceback.print_exc()
return make_error_object(Exception(API_ERROR_INTERNAL_ERROR, "try to upload file after 5 minutes"))
@staticmethod
@api_method("media.get",
doc="Получение медиа",
params=[
ApiRequestParam(),
ApiParamAccessToken(),
ApiParamInt(name="m_id", description="ID медиа",
value_min=0, value_max=1000000000),
], returns="<code>медиа</code>, в противном случае ошибку")
async def get(request, access_token, m_id):
if request.method != "GET":
return make_error_object(Exception(API_ERROR_INVALID_REQUEST, "method must be executed http GET method"))
m = await Media.objects.filter(owner=access_token.user, pk=m_id).afirst()
if m is not None:
try:
# Media.get_by_id(access_token.user, m_id)
res = HttpResponse(content=s3_get(m.storage_name))
res.headers["Content-type"] = "image/jpg"
return res
except Exception:
traceback.print_exc()
return make_error_object(Exception(API_ERROR_NOT_FOUND, "object in storage not found"))
class DatabaseApi:
# TODO переместить сюда форму заказа, список городов
# def get_order_form
# def get_citys
pass
async def api_call_method(request, method_name, params: dict):