Обновлен список городов - теперь он не в базе данных, а в статических объектах. Обновлена работа с медиа - теперь их можно полноценно загружать и скачивать с сервера.
This commit is contained in:
parent
d4158ae1c0
commit
a9d3a34c0b
@ -11,7 +11,7 @@ class AccountAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Media)
|
@admin.register(Media)
|
||||||
class MediaAdmin(admin.ModelAdmin):
|
class MediaAdmin(admin.ModelAdmin):
|
||||||
list_display = ['id', 'user', 'storage_name', 'original_name']
|
list_display = ['id', 'owner', 'storage_name', 'original_name']
|
||||||
readonly_fields = ['id', 'upload_datetime']
|
readonly_fields = ['id', 'upload_datetime']
|
||||||
|
|
||||||
@admin.register(ExecutorAccount)
|
@admin.register(ExecutorAccount)
|
||||||
@ -33,12 +33,6 @@ class AccessTokenAdmin(admin.ModelAdmin):
|
|||||||
return f"{obj.access_token[:8]}..."
|
return f"{obj.access_token[:8]}..."
|
||||||
|
|
||||||
|
|
||||||
@admin.register(City)
|
|
||||||
class CityAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['code', 'name']
|
|
||||||
ordering = ['name']
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Order)
|
@admin.register(Order)
|
||||||
class OrderAdmin(admin.ModelAdmin):
|
class OrderAdmin(admin.ModelAdmin):
|
||||||
list_display = ['owner', 'phone', 'name', 'create_time', 'moderated', 'published']
|
list_display = ['owner', 'phone', 'name', 'create_time', 'moderated', 'published']
|
||||||
|
41
api/api_media_utils.py
Normal file
41
api/api_media_utils.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import boto3
|
||||||
|
import os
|
||||||
|
|
||||||
|
print("[DEBUG] boto3 init...")
|
||||||
|
__session = boto3.session.Session()
|
||||||
|
__s3 = __session.client(
|
||||||
|
service_name='s3',
|
||||||
|
endpoint_url='https://storage.yandexcloud.net'
|
||||||
|
)
|
||||||
|
|
||||||
|
__bucket = os.getenv('AWS_DEFAULT_BUCKET')
|
||||||
|
|
||||||
|
|
||||||
|
def s3_upload_from_buffer(filename: str, data: bytes):
|
||||||
|
# Загрузить объекты в бакет
|
||||||
|
|
||||||
|
## Из строки
|
||||||
|
__s3.put_object(Bucket=__bucket, Key=filename, Body=data)
|
||||||
|
|
||||||
|
|
||||||
|
def s3_upload_from_file(s3_filename: str, target_file: str):
|
||||||
|
## Из файла
|
||||||
|
__s3.upload_file(target_file, __bucket, s3_filename)
|
||||||
|
|
||||||
|
|
||||||
|
# # Получить список объектов в бакете
|
||||||
|
# for key in __s3.list_objects(Bucket='bucket-name')['Contents']:
|
||||||
|
# print(key['Key'])
|
||||||
|
|
||||||
|
|
||||||
|
def s3_delete(files):
|
||||||
|
forDeletion = [{'Key': 'object_name'}, {'Key': 'script/py_script.py'}]
|
||||||
|
# Удалить несколько объектов
|
||||||
|
response = __s3.delete_objects(Bucket='bucket-name', Delete={'Objects': forDeletion})
|
||||||
|
|
||||||
|
|
||||||
|
def s3_get(file: str):
|
||||||
|
# Получить объект
|
||||||
|
return __s3.get_object(Bucket=__bucket, Key=file)['Body'].read()
|
||||||
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest
|
from django.http import HttpResponse, HttpResponseBadRequest
|
||||||
from .api_utils import *
|
from .api_utils import *
|
||||||
from .api_params import *
|
from .api_params import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from .api_media_utils import *
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +44,7 @@ class ApiAccount:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __make_user_json(user: Account):
|
def __make_user_json(user: Account, self_using=False):
|
||||||
obj = {
|
obj = {
|
||||||
"id": user.id,
|
"id": user.id,
|
||||||
"name": user.name,
|
"name": user.name,
|
||||||
@ -49,10 +52,19 @@ class ApiAccount:
|
|||||||
"phone": user.phone,
|
"phone": user.phone,
|
||||||
"email": user.email,
|
"email": user.email,
|
||||||
"about": user.about,
|
"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())),
|
"register_datetime": int(time.mktime(user.register_datetime.timetuple())),
|
||||||
"role": user.role,
|
"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:
|
if user.role == Account.ROLE_EXECUTOR:
|
||||||
obj |= {
|
obj |= {
|
||||||
"executor_type": user.executoraccount.executor_type,
|
"executor_type": user.executoraccount.executor_type,
|
||||||
@ -135,7 +147,7 @@ class ApiAccount:
|
|||||||
params=[ApiParamAccessToken()],
|
params=[ApiParamAccessToken()],
|
||||||
returns="Стандартный ответ успеха, в случае успеха")
|
returns="Стандартный ответ успеха, в случае успеха")
|
||||||
async def delete(access_token):
|
async def delete(access_token):
|
||||||
user = access_token.user
|
user = access_token.owner
|
||||||
await sync_to_async(user.delete)()
|
await sync_to_async(user.delete)()
|
||||||
return api_make_response({})
|
return api_make_response({})
|
||||||
|
|
||||||
@ -151,9 +163,9 @@ class ApiAccount:
|
|||||||
returns="Поля пользователя (name, surname, email, phone и прочие).")
|
returns="Поля пользователя (name, surname, email, phone и прочие).")
|
||||||
async def get(access_token, user_id):
|
async def get(access_token, user_id):
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
user = access_token.user
|
user = access_token.owner
|
||||||
else:
|
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:
|
if user is None:
|
||||||
return make_error_object(Exception(API_ERROR_NOT_FOUND, {"user": user_id}))
|
return make_error_object(Exception(API_ERROR_NOT_FOUND, {"user": user_id}))
|
||||||
|
|
||||||
@ -177,11 +189,11 @@ class ApiAccount:
|
|||||||
description="ИНН исполнителя (только для роли исполнитель): "
|
description="ИНН исполнителя (только для роли исполнитель): "
|
||||||
"12 цифр если исполнитель - физ.лицо и 10 цифр если это юр. лицо"),
|
"12 цифр если исполнитель - физ.лицо и 10 цифр если это юр. лицо"),
|
||||||
ApiParamEnum(name="city", description="Город, в котором находится ползователь: {choices}",
|
ApiParamEnum(name="city", description="Город, в котором находится ползователь: {choices}",
|
||||||
required=False, choices=City.to_choices)
|
required=False, choices=CITIES_CHOICES)
|
||||||
],
|
],
|
||||||
returns="Вернет основную информацию о пользователе, иначе ошибки")
|
returns="Вернет основную информацию о пользователе, иначе ошибки")
|
||||||
async def edit(access_token, name, surname, about, executor_type, executor_inn, city):
|
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
|
executor_need_save, need_save = False, False
|
||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
@ -256,7 +268,7 @@ class ApiAccount:
|
|||||||
],
|
],
|
||||||
returns="Вернет стандартный объект успеха")
|
returns="Вернет стандартный объект успеха")
|
||||||
async def change_phone(access_token, password, phone, code):
|
async def change_phone(access_token, password, phone, code):
|
||||||
user = access_token.user
|
user = access_token.owner
|
||||||
|
|
||||||
if not user.check_password(password):
|
if not user.check_password(password):
|
||||||
raise Exception(API_ERROR_INVALID_PASSWORD)
|
raise Exception(API_ERROR_INVALID_PASSWORD)
|
||||||
@ -310,7 +322,7 @@ class ApiAccount:
|
|||||||
class ApiSecurity:
|
class ApiSecurity:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@api_method("security.listSessions",
|
@api_method("security.listSessions",
|
||||||
doc="Получение сиписка сессий (кроме текущей)",
|
doc="Получение списка сессий (кроме текущей)",
|
||||||
params=[
|
params=[
|
||||||
ApiParamAccessToken(),
|
ApiParamAccessToken(),
|
||||||
ApiParamPassword(description="Пароль пользователя, нужен для подтверждения личности")
|
ApiParamPassword(description="Пароль пользователя, нужен для подтверждения личности")
|
||||||
@ -319,7 +331,7 @@ class ApiSecurity:
|
|||||||
async def list_sessions(access_token, password):
|
async def list_sessions(access_token, password):
|
||||||
sessions = await access_token.list_sessions()
|
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)
|
raise Exception(API_ERROR_INVALID_PASSWORD)
|
||||||
|
|
||||||
return api_make_response({
|
return api_make_response({
|
||||||
@ -338,14 +350,14 @@ class ApiSecurity:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@api_method("security.removeOtherSessions",
|
@api_method("security.removeOtherSessions",
|
||||||
doc="Получение сиписка сессий (кроме текущей)",
|
doc="Получение списка сессий (кроме текущей)",
|
||||||
params=[
|
params=[
|
||||||
ApiParamAccessToken(),
|
ApiParamAccessToken(),
|
||||||
ApiParamPassword(description="Пароль пользователя, нужен для подтверждения личности")
|
ApiParamPassword(description="Пароль пользователя, нужен для подтверждения личности")
|
||||||
],
|
],
|
||||||
returns="Вернет sessions: [{id: int, name: str, created: unix_timestamp}]")
|
returns="Вернет sessions: [{id: int, name: str, created: unix_timestamp}]")
|
||||||
async def remove_other_sessions(access_token, password):
|
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)
|
raise Exception(API_ERROR_INVALID_PASSWORD)
|
||||||
|
|
||||||
sessions = await access_token.list_sessions()
|
sessions = await access_token.list_sessions()
|
||||||
@ -377,7 +389,7 @@ class ApiSecurity:
|
|||||||
],
|
],
|
||||||
returns="Вернет стандартный отъект в случае успеха")
|
returns="Вернет стандартный отъект в случае успеха")
|
||||||
async def remove_session(access_token, password, session):
|
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)
|
raise Exception(API_ERROR_INVALID_PASSWORD)
|
||||||
|
|
||||||
await access_token.delete_session(session)
|
await access_token.delete_session(session)
|
||||||
@ -394,7 +406,7 @@ class ApiSecurity:
|
|||||||
],
|
],
|
||||||
returns="Вернет стандартный объект успеха")
|
returns="Вернет стандартный объект успеха")
|
||||||
async def change_password(access_token, old_password, password):
|
async def change_password(access_token, old_password, password):
|
||||||
user = access_token.user
|
user = access_token.owner
|
||||||
|
|
||||||
if not user.check_password(old_password):
|
if not user.check_password(old_password):
|
||||||
raise Exception(API_ERROR_INVALID_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_start = models.DateField(null=True, blank=True, default=None, verbose_name="Дата начала")
|
||||||
# date_end = 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="Улица, дом",
|
ApiParamStr(name='address_text', max_length=70, description="Улица, дом",
|
||||||
required=False, default="")
|
required=False, default="")
|
||||||
@ -550,13 +562,11 @@ class ApiOrder:
|
|||||||
access_token = kwargs.pop('access_token')
|
access_token = kwargs.pop('access_token')
|
||||||
ApiOrder._check_write_permissions(access_token)
|
ApiOrder._check_write_permissions(access_token)
|
||||||
|
|
||||||
city = await City.get_by_code(kwargs.pop('address_city'))
|
|
||||||
|
|
||||||
try:
|
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:
|
except ValidationError as ve:
|
||||||
return _make_model_validation_errors(ve, API_ERROR_USER_MODIFY)
|
return _make_model_validation_errors(ve, API_ERROR_USER_MODIFY)
|
||||||
return api_make_response({"order_id": order.id})
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@api_method("order.setPublished",
|
@api_method("order.setPublished",
|
||||||
@ -571,7 +581,7 @@ class ApiOrder:
|
|||||||
ApiOrder._check_write_permissions(access_token)
|
ApiOrder._check_write_permissions(access_token)
|
||||||
query = Order.objects.filter(id=order_id)
|
query = Order.objects.filter(id=order_id)
|
||||||
order = await query.afirst()
|
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')
|
raise Exception(API_ERROR_ACCESS_DENIED, 'edit operation allowed only for owner')
|
||||||
|
|
||||||
await query.aupdate(published=value)
|
await query.aupdate(published=value)
|
||||||
@ -609,13 +619,13 @@ class ApiOrder:
|
|||||||
if order_id is not None:
|
if order_id is not None:
|
||||||
res = await query.aget(id=order_id)
|
res = await query.aget(id=order_id)
|
||||||
if user_id is not None:
|
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)])
|
return api_make_response([ApiOrder._order_to_json(res)])
|
||||||
else:
|
else:
|
||||||
raise Exception(API_ERROR_NOT_ALLOWED, 'attempt access to closed order')
|
raise Exception(API_ERROR_NOT_ALLOWED, 'attempt access to closed order')
|
||||||
|
|
||||||
if user_id is not None:
|
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:
|
if user is None:
|
||||||
raise Exception(API_ERROR_NOT_FOUND, 'user')
|
raise Exception(API_ERROR_NOT_FOUND, 'user')
|
||||||
if user.role != Account.ROLE_CUSTOMER:
|
if user.role != Account.ROLE_CUSTOMER:
|
||||||
@ -629,29 +639,106 @@ class ApiOrder:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_write_permissions(access_token):
|
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)
|
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')
|
raise Exception(API_ERROR_NOT_ALLOWED, 'you must be a customer')
|
||||||
|
|
||||||
|
|
||||||
class ApiMedia:
|
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
|
@staticmethod
|
||||||
@api_method("media.upload",
|
@api_method("media.upload",
|
||||||
doc="Загрузка медиа на сервер. Вызывать методом POST.",
|
doc="Загрузка медиа на сервер. Вызывать методом POST. Обязательно должен быть прикреплен файл"
|
||||||
|
" с именем поля 'file'",
|
||||||
params=[
|
params=[
|
||||||
ApiRequestParam(),
|
ApiRequestParam(),
|
||||||
ApiParamAccessToken(),
|
ApiParamAccessToken(),
|
||||||
ApiParamStr(name="filename", description="Название файла",
|
|
||||||
min_length=5, max_length=60),
|
|
||||||
], returns="<code>id</code> медиа, в противном случае ошибку")
|
], returns="<code>id</code> медиа, в противном случае ошибку")
|
||||||
def upload(request, access_token, filename):
|
async def upload(request, access_token):
|
||||||
# ну шож, метод фейковый, все проверки нужно сделать руками
|
|
||||||
if request.method != "POST":
|
if request.method != "POST":
|
||||||
return make_error_object(Exception(API_ERROR_INVALID_REQUEST, "method must be executed http POST method"))
|
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):
|
async def api_call_method(request, method_name, params: dict):
|
||||||
|
@ -8,39 +8,11 @@ from hashlib import sha512, sha256
|
|||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from django.db.utils import ProgrammingError
|
from arka.settings import CITIES_CHOICES, CITIES_FIELD_SIZE
|
||||||
|
|
||||||
from .api_errors import *
|
from .api_errors import *
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class City(models.Model):
|
|
||||||
code = models.CharField(primary_key=True, max_length=20, verbose_name="Код города", validators=[
|
|
||||||
RegexValidator(regex="^[0-9a-zA-Z_]*$"),
|
|
||||||
])
|
|
||||||
name = models.CharField(unique=True, max_length=50, verbose_name="Название города")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.name} ({self.code})"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def to_choices_async():
|
|
||||||
return [item async for item in City.objects.order_by('name').values_list('code', 'name')]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def to_choices():
|
|
||||||
try:
|
|
||||||
return list(City.objects.order_by('name').values_list('code', 'name'))
|
|
||||||
except ProgrammingError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_by_code(code):
|
|
||||||
if code is None:
|
|
||||||
return None
|
|
||||||
return await City.objects.aget(code=code)
|
|
||||||
|
|
||||||
|
|
||||||
class Account(models.Model):
|
class Account(models.Model):
|
||||||
surname = models.CharField(max_length=60, verbose_name="Фамилия", blank=True, default="")
|
surname = models.CharField(max_length=60, verbose_name="Фамилия", blank=True, default="")
|
||||||
name = models.CharField(max_length=60, verbose_name="Имя", blank=True, default="")
|
name = models.CharField(max_length=60, verbose_name="Имя", blank=True, default="")
|
||||||
@ -65,7 +37,7 @@ class Account(models.Model):
|
|||||||
|
|
||||||
about = models.CharField(max_length=1000, blank=True, default="", verbose_name="О себе")
|
about = models.CharField(max_length=1000, blank=True, default="", verbose_name="О себе")
|
||||||
|
|
||||||
city = models.ForeignKey(City, on_delete=models.SET_NULL, default=None, null=True, blank=True)
|
city = models.CharField(max_length=CITIES_FIELD_SIZE, choices=CITIES_CHOICES, default=None, null=True, blank=True)
|
||||||
|
|
||||||
register_datetime = models.DateTimeField(default=datetime.now, editable=False)
|
register_datetime = models.DateTimeField(default=datetime.now, editable=False)
|
||||||
|
|
||||||
@ -94,7 +66,7 @@ class Account(models.Model):
|
|||||||
raise Exception(API_ERROR_NOT_FOUND, "user")
|
raise Exception(API_ERROR_NOT_FOUND, "user")
|
||||||
|
|
||||||
async def get_by_id(self, user_id: int):
|
async def get_by_id(self, user_id: int):
|
||||||
u = Account.objects.filter(id=user_id).select_related('executoraccount', 'city')
|
u = Account.objects.filter(id=user_id).select_related('executoraccount', 'accountavatar')
|
||||||
if self.role == Account.ROLE_EXECUTOR or self.role == Account.ROLE_CUSTOMER:
|
if self.role == Account.ROLE_EXECUTOR or self.role == Account.ROLE_CUSTOMER:
|
||||||
u.filter(role__in=[Account.ROLE_EXECUTOR, Account.ROLE_CUSTOMER])
|
u.filter(role__in=[Account.ROLE_EXECUTOR, Account.ROLE_CUSTOMER])
|
||||||
|
|
||||||
@ -117,13 +89,16 @@ class Account(models.Model):
|
|||||||
return f"{self.name} {self.surname}: {self.phone} ({r})"
|
return f"{self.name} {self.surname}: {self.phone} ({r})"
|
||||||
|
|
||||||
def is_completed(self):
|
def is_completed(self):
|
||||||
return self.name != '' and self.surname != '' and self.city_id is not None
|
return self.name != '' and self.surname != '' and self.city is not None
|
||||||
|
|
||||||
|
|
||||||
class Media(models.Model):
|
class Media(models.Model):
|
||||||
user = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
|
owner = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True,
|
||||||
storage_name = models.CharField(max_length=100, verbose_name="Файл в хранилище")
|
related_name="media_owner", verbose_name="Владелец")
|
||||||
|
storage_name = models.CharField(max_length=80, verbose_name="Файл в хранилище")
|
||||||
original_name = models.CharField(max_length=100, verbose_name="Имя файла", default="")
|
original_name = models.CharField(max_length=100, verbose_name="Имя файла", default="")
|
||||||
|
extension = models.CharField(max_length=16)
|
||||||
|
size = models.IntegerField(verbose_name='Размер в байтах')
|
||||||
upload_datetime = models.DateTimeField(default=datetime.now, editable=False)
|
upload_datetime = models.DateTimeField(default=datetime.now, editable=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -135,13 +110,21 @@ class Media(models.Model):
|
|||||||
async def get_media():
|
async def get_media():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def generate_storage_name(self):
|
@staticmethod
|
||||||
if self.storage_name is None:
|
def generate_storage_name(original_name, upload_datetime, user_id):
|
||||||
source_str = f"{self.original_name} {self.original_name} {self.upload_datetime} {self.user.id}"
|
source_str = f"{original_name} {upload_datetime} {user_id}"
|
||||||
self.storage_name = sha512(bytearray(source_str, 'utf-8')).hexdigest()
|
return sha256(bytearray(source_str, 'utf-8')).hexdigest()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user}: \"{self.original_name}\" ({self.id})"
|
return f"{self.owner}: \"{self.original_name}\" ({self.id})"
|
||||||
|
|
||||||
|
|
||||||
|
class AccountAvatar(models.Model):
|
||||||
|
account = models.OneToOneField(Account, on_delete=models.CASCADE, related_name="account", verbose_name="Аккаунт")
|
||||||
|
photo = models.ForeignKey(Media, on_delete=models.SET_NULL, null=True,
|
||||||
|
related_name="photo", verbose_name="Аватар")
|
||||||
|
profile_background = models.ForeignKey(Media, on_delete=models.SET_NULL, null=True, default=None,
|
||||||
|
related_name="profile_background", verbose_name="Оформление профиля")
|
||||||
|
|
||||||
|
|
||||||
def _executor_additional_info_default():
|
def _executor_additional_info_default():
|
||||||
@ -205,12 +188,12 @@ class AccessToken(models.Model):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_user_by_token(token: str):
|
async def get_user_by_token(token: str):
|
||||||
return (await AccessToken.get_by_token(token)).user
|
return (await AccessToken.get_by_token(token)).owner
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_by_token(token: str):
|
async def get_by_token(token: str):
|
||||||
t = await AccessToken.objects.filter(access_token=token)\
|
t = await AccessToken.objects.filter(access_token=token).select_related('user',
|
||||||
.select_related('user', 'user__executoraccount', 'user__city').afirst()
|
'user__executoraccount').afirst()
|
||||||
if t is None:
|
if t is None:
|
||||||
raise Exception(API_ERROR_INVALID_TOKEN)
|
raise Exception(API_ERROR_INVALID_TOKEN)
|
||||||
return t
|
return t
|
||||||
@ -342,8 +325,8 @@ class Order(models.Model):
|
|||||||
date_start = models.DateField(null=True, blank=True, default=None, verbose_name="Дата начала")
|
date_start = models.DateField(null=True, blank=True, default=None, verbose_name="Дата начала")
|
||||||
date_end = models.DateField(null=True, blank=True, default=None, verbose_name="Дата окончания")
|
date_end = models.DateField(null=True, blank=True, default=None, verbose_name="Дата окончания")
|
||||||
|
|
||||||
address_city = models.ForeignKey(City, on_delete=models.CASCADE, blank=False, related_name="address_city",
|
address_city = models.CharField(max_length=CITIES_FIELD_SIZE, choices=CITIES_CHOICES,
|
||||||
verbose_name="Город")
|
blank=False, verbose_name="Город")
|
||||||
address_text = models.CharField(max_length=70, blank=True, verbose_name="Улица, дом")
|
address_text = models.CharField(max_length=70, blank=True, verbose_name="Улица, дом")
|
||||||
|
|
||||||
owner = models.ForeignKey(Account, on_delete=models.CASCADE, related_name="owner", verbose_name="Владелец")
|
owner = models.ForeignKey(Account, on_delete=models.CASCADE, related_name="owner", verbose_name="Владелец")
|
||||||
@ -377,7 +360,6 @@ class Order(models.Model):
|
|||||||
return q[0]
|
return q[0]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# def _upload_image_filename(instance, filename):
|
# def _upload_image_filename(instance, filename):
|
||||||
# name, ext = os.path.splitext(filename)
|
# name, ext = os.path.splitext(filename)
|
||||||
# fn = sha256((str(datetime.now()) + name).encode('utf-8')).hexdigest() + ext
|
# fn = sha256((str(datetime.now()) + name).encode('utf-8')).hexdigest() + ext
|
||||||
@ -428,4 +410,3 @@ class Order(models.Model):
|
|||||||
# self.full_clean()
|
# self.full_clean()
|
||||||
#
|
#
|
||||||
# super().save(*args, **kwargs)
|
# super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest
|
from django.http import HttpResponse, HttpResponseBadRequest
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from .api_methods import api_call_method, api_get_documentation
|
from .api_methods import api_call_method, api_get_documentation
|
||||||
|
|
||||||
|
|
||||||
@ -36,4 +38,3 @@ async def call_method(request, method_name):
|
|||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ https://docs.djangoproject.com/en/3.2/ref/settings/
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import dotenv
|
import dotenv
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
@ -49,7 +51,7 @@ MIDDLEWARE = [
|
|||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
# 'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
@ -147,3 +149,11 @@ PHONE_VERIFICATION_ENABLE = int(os.getenv('PHONE_VERIFICATION_ENABLE', "1")) !=
|
|||||||
PHONE_VERIFICATION_ATTEMPTS = int(os.getenv('PHONE_VERIFICATION_ATTEMPTS', "5"))
|
PHONE_VERIFICATION_ATTEMPTS = int(os.getenv('PHONE_VERIFICATION_ATTEMPTS', "5"))
|
||||||
PHONE_VERIFICATION_RESEND_TIME_SECS = int(os.getenv('PHONE_VERIFICATION_RESEND_TIME_SECS', "180"))
|
PHONE_VERIFICATION_RESEND_TIME_SECS = int(os.getenv('PHONE_VERIFICATION_RESEND_TIME_SECS', "180"))
|
||||||
PHONE_VERIFICATION_ACCESS_KEY = os.getenv('PHONE_VERIFICATION_ACCESS_KEY', "EMPTY_ACCESS_KEY")
|
PHONE_VERIFICATION_ACCESS_KEY = os.getenv('PHONE_VERIFICATION_ACCESS_KEY', "EMPTY_ACCESS_KEY")
|
||||||
|
|
||||||
|
|
||||||
|
# настройки выбора городов
|
||||||
|
with open('config/cities.json') as f:
|
||||||
|
CITIES_CHOICES = json.load(f)
|
||||||
|
for i in range(0, len(CITIES_CHOICES)):
|
||||||
|
CITIES_CHOICES[i] = tuple(CITIES_CHOICES[i])
|
||||||
|
CITIES_FIELD_SIZE = 16
|
||||||
|
6
config/cities.json
Normal file
6
config/cities.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
["moscow", "Москва"],
|
||||||
|
["mytishchi", "Мытищи"],
|
||||||
|
["belgorod", "Белгород"],
|
||||||
|
["orel", "Орел"]
|
||||||
|
]
|
@ -2,3 +2,4 @@ psycopg2
|
|||||||
django==4.1.7
|
django==4.1.7
|
||||||
requests==2.28.2
|
requests==2.28.2
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
boto3
|
||||||
|
Reference in New Issue
Block a user