Global API refactoring
This commit is contained in:
parent
ad659b5f30
commit
47359a7932
@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import PermissionsMixin, AbstractBaseUser, BaseUserManager
|
||||
from django.core.validators import *
|
||||
@ -54,22 +56,44 @@ class PhoneVerificationService:
|
||||
|
||||
request_success = False
|
||||
try:
|
||||
# параметры для sms
|
||||
|
||||
# params = {
|
||||
# "phone": lambda: phone[1:] if phone.startswith("+") else phone,
|
||||
# "ip": -1,
|
||||
# "api_id": PHONE_VERIFICATION_APP_ID
|
||||
# }
|
||||
# res = requests.get("https://sms.ru/code/call", params=params, timeout=5)
|
||||
|
||||
# res_json = res.json()
|
||||
# request_success = True
|
||||
# print(res.content)
|
||||
|
||||
# if res_json["status"] == "OK":
|
||||
# with PhoneVerificationService.__lock:
|
||||
# PhoneVerificationService.__codes[phone]["code"] = res_json["code"]
|
||||
# print(f"Verify code for {phone}: {res_json['code']}")
|
||||
# else:
|
||||
# with PhoneVerificationService.__lock:
|
||||
# PhoneVerificationService.__codes[phone]["code"] = "FAILED"
|
||||
|
||||
# для бота vk
|
||||
code = random.randint(1000, 9999)
|
||||
|
||||
params = {
|
||||
"phone": lambda: phone[1:] if phone.startswith("+") else phone,
|
||||
"ip": -1,
|
||||
"api_id": PHONE_VERIFICATION_APP_ID
|
||||
"v": 5.131,
|
||||
"user_ids": '352634831,405800248', # Гоша, Влад
|
||||
"access_token": os.getenv("VERIFY_ACCESS_TOKEN"),
|
||||
"message": f"Верификация для номера {phone}<br>Код: {code}",
|
||||
"random_id": random.randint(1000, 100000000)
|
||||
}
|
||||
res = requests.get("https://sms.ru/code/call", params=params, timeout=5)
|
||||
res_json = res.json()
|
||||
res = requests.get("https://api.vk.com/method/messages.send", params=params, timeout=5)
|
||||
|
||||
request_success = True
|
||||
print(res.content)
|
||||
if res_json["status"] == "OK":
|
||||
with PhoneVerificationService.__lock:
|
||||
PhoneVerificationService.__codes[phone]["code"] = res_json["code"]
|
||||
print(f"Verify code for {phone}: {res_json['code']}")
|
||||
else:
|
||||
with PhoneVerificationService.__lock:
|
||||
PhoneVerificationService.__codes[phone]["code"] = "FAILED"
|
||||
print(f"received content: {res.content}")
|
||||
obj["code"] = code
|
||||
PhoneVerificationService.__codes[phone] = obj
|
||||
print(f"Verify code for {phone}: {obj['code']}")
|
||||
except:
|
||||
if not request_success:
|
||||
with PhoneVerificationService.__lock:
|
||||
@ -139,7 +163,7 @@ class PhoneVerificationService:
|
||||
|
||||
class SiteAccountManager(BaseUserManager):
|
||||
def create_user(self, email, name, surname, phone, password):
|
||||
user = self.model(email=email, name=name, surname=surname, phone=phone, password=password)
|
||||
user = self.model.create_user(email=email, name=name, surname=surname, phone=phone, password=password)
|
||||
user.set_password(password)
|
||||
user.is_staff = False
|
||||
user.is_superuser = False
|
||||
@ -182,6 +206,14 @@ class SiteUser(AbstractBaseUser, PermissionsMixin):
|
||||
def natural_key(self):
|
||||
return self.phone
|
||||
|
||||
@staticmethod
|
||||
def create_user(phone="", **kvargs):
|
||||
if re.match("^[0-9]{10}$", phone) is not None:
|
||||
phone = f"+7{phone}"
|
||||
elif re.match("^7[0-9]{10}$", phone) is not None:
|
||||
phone = f"+{phone}"
|
||||
return SiteUser(phone=phone, **kvargs)
|
||||
|
||||
@staticmethod
|
||||
def get_by_natural_key(key):
|
||||
# Гоша попросил запилить фичу, чтобы принимались номера:
|
||||
|
@ -32,6 +32,6 @@ def profile(request):
|
||||
|
||||
@login_required
|
||||
def my_orders(request):
|
||||
orders = Order.objects.filter(owner_id=request.user.id).select_related('address_city')
|
||||
orders = Order.get_all_for_user(request.user).filter(owner=request.user).select_related('address_city')
|
||||
|
||||
return render(request, 'account/my-orders.html', {"orders": orders})
|
||||
|
@ -5,4 +5,9 @@ from .models import *
|
||||
@admin.register(UserToken)
|
||||
class DevEventAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ['access_token']
|
||||
list_display = ['user', 'creation_time', 'small_access_token']
|
||||
fields = ['user', 'creation_time', 'access_token']
|
||||
ordering = ['-creation_time']
|
||||
|
||||
def small_access_token(self, obj):
|
||||
return f"{obj.access_token[:8]}..."
|
||||
|
@ -7,6 +7,8 @@ from arka.settings import PHONE_VERIFICATION_RESEND_TIME_SECS
|
||||
|
||||
API_OK_OBJ = {"status": "success"}
|
||||
|
||||
API_ERROR_MULTIPLY_ERRORS = (None, 'multiply errors')
|
||||
|
||||
API_ERROR_INTERNAL_ERROR = (100, 'internal error')
|
||||
|
||||
API_ERROR_METHOD_NOT_FOUND = (200, 'method not found')
|
||||
@ -43,29 +45,40 @@ API_ERROR_VALIDATION = {
|
||||
}
|
||||
|
||||
|
||||
def make_error_object(ex: Exception):
|
||||
data = {
|
||||
"status": "error"
|
||||
def __make_error(ex: Exception):
|
||||
if type(ex.args[0]) != tuple:
|
||||
raise ex
|
||||
|
||||
error = {
|
||||
"code": ex.args[0][0],
|
||||
"message": ex.args[0][1]
|
||||
}
|
||||
|
||||
if len(ex.args) >= 2:
|
||||
error["related"] = ex.args[1]
|
||||
|
||||
return error
|
||||
|
||||
|
||||
def make_error_object(ex: Exception | list):
|
||||
try:
|
||||
if type(ex.args[0]) != tuple:
|
||||
raise ex
|
||||
|
||||
data["error"] = {
|
||||
"code": ex.args[0][0],
|
||||
"message": ex.args[0][1]
|
||||
data = {
|
||||
"status": "error"
|
||||
}
|
||||
|
||||
if len(ex.args) >= 2:
|
||||
data["error"]["related"] = ex.args[1]
|
||||
if type(ex) == list:
|
||||
data["error"] = [__make_error(e) for e in ex]
|
||||
else:
|
||||
data["error"] = [__make_error(ex)]
|
||||
|
||||
return data
|
||||
|
||||
except BaseException as err:
|
||||
traceback.print_exc()
|
||||
data["error"] = {
|
||||
"code": API_ERROR_INTERNAL_ERROR[0],
|
||||
"message": API_ERROR_INTERNAL_ERROR[1],
|
||||
"related": f"Exception {type(err)}: {str(err)}"
|
||||
|
||||
return {
|
||||
"status": "error",
|
||||
"error": [{
|
||||
"code": API_ERROR_INTERNAL_ERROR[0],
|
||||
"message": API_ERROR_INTERNAL_ERROR[1],
|
||||
"related": f"Exception {type(err)}: {str(err)}"
|
||||
}]
|
||||
}
|
||||
return data
|
||||
|
@ -1,6 +1,6 @@
|
||||
from .api_utils import *
|
||||
from .models import *
|
||||
from django.core.exceptions import *
|
||||
from order.models import *
|
||||
|
||||
|
||||
def _require_access_token(params):
|
||||
@ -8,169 +8,239 @@ def _require_access_token(params):
|
||||
return UserToken.get_user_by_token(token)
|
||||
|
||||
|
||||
def account_auth(params):
|
||||
login = api_get_param_str(params, "login")
|
||||
password = api_get_param_str(params, "password")
|
||||
user = UserToken.auth(login, password)
|
||||
token = UserToken.create_token(user)
|
||||
class ApiAccount:
|
||||
@staticmethod
|
||||
@api_method("account.auth",
|
||||
doc="Аутентификация пользователя",
|
||||
params=[
|
||||
api_make_param("login", str, "Логин пользователя"),
|
||||
api_make_param("password", str, "Пароль пользователя"),
|
||||
],
|
||||
returns="В случае правильных логина и пароля <code>access_token</code>. "
|
||||
"В противном случае объект ошибки.")
|
||||
def auth(login, password):
|
||||
user = UserToken.auth(login, password)
|
||||
token = UserToken.create_token(user)
|
||||
|
||||
return api_make_response({"access_token": token.access_token})
|
||||
return api_make_response({"access_token": token.access_token})
|
||||
|
||||
@staticmethod
|
||||
@api_method("account.deauth",
|
||||
doc="Удаление токена, дальшейшие вызовы API с этим токеном вернут ошибку невалидного токена",
|
||||
params=[
|
||||
API_PARAM_ACCESS_TOKEN,
|
||||
], returns="В случае успеха стандартный код успеха")
|
||||
def deauth(access_token):
|
||||
UserToken.deauth(access_token.access_token)
|
||||
return api_make_response({})
|
||||
|
||||
def account_deauth(params):
|
||||
UserToken.deauth(api_get_param_str(params, "access_token"))
|
||||
return api_make_response({})
|
||||
@staticmethod
|
||||
@api_method("account.register",
|
||||
doc="Регистрация нового пользователя",
|
||||
params=[
|
||||
api_make_param("name", str, "Имя пользователя"),
|
||||
api_make_param("surname", str, "Фамилия пользователя"),
|
||||
api_make_param("phone", str, "Телефон в формате <code>[[+]7]1112223333</code> "
|
||||
"<i>(в квадратных скобках необязательная часть)</i>"),
|
||||
api_make_param("email", str, "Почта"),
|
||||
api_make_param("password", str, "Пароль пользователя"),
|
||||
], returns="Аналогично методу <code>account.auth</code> в случае успеха")
|
||||
def register(name, surname, phone, email, password):
|
||||
|
||||
|
||||
def account_register(params):
|
||||
name = api_get_param_str(params, "name")
|
||||
surname = api_get_param_str(params, "surname")
|
||||
phone = api_get_param_str(params, "phone")
|
||||
email = api_get_param_str(params, "email")
|
||||
password = api_get_param_str(params, "password")
|
||||
|
||||
user = SiteUser(
|
||||
name=name,
|
||||
surname=surname,
|
||||
phone=phone,
|
||||
email=email,
|
||||
password=password
|
||||
)
|
||||
|
||||
try:
|
||||
user.full_clean()
|
||||
user.save()
|
||||
user = SiteUser.create_user(
|
||||
name=name,
|
||||
surname=surname,
|
||||
phone=phone,
|
||||
email=email,
|
||||
password=password
|
||||
)
|
||||
|
||||
try:
|
||||
token = UserToken.create_token(user)
|
||||
return api_make_response({"access_token": token.access_token})
|
||||
|
||||
except Exception as ex:
|
||||
# если вдруг токен нельзя создать
|
||||
user.delete()
|
||||
raise ex
|
||||
|
||||
except ValidationError as validation_error:
|
||||
traceback.print_exc()
|
||||
errors = {}
|
||||
for field_name in validation_error.error_dict:
|
||||
err_list = validation_error.error_dict[field_name]
|
||||
print(err_list)
|
||||
obj = []
|
||||
for err in err_list:
|
||||
obj.append({
|
||||
"code": err.code
|
||||
})
|
||||
errors[field_name] = obj
|
||||
raise Exception(API_ERROR_USER_REGISTER, errors)
|
||||
|
||||
|
||||
def account_verify_phone(params):
|
||||
user = _require_access_token(params)
|
||||
|
||||
if user.is_phone_verified:
|
||||
raise Exception(API_ERROR_VALIDATION_CURRENTLY_VERIFIED)
|
||||
|
||||
code = api_get_param_int(params, "code", False, None)
|
||||
|
||||
if code is None:
|
||||
res, err_code = PhoneVerificationService.send_verify(user.phone)
|
||||
|
||||
if not res:
|
||||
if err_code in API_ERROR_VALIDATION:
|
||||
raise Exception(API_ERROR_VALIDATION[err_code])
|
||||
else:
|
||||
raise Exception(API_ERROR_VALIDATION_UNKNOWN)
|
||||
|
||||
return api_make_response({"action": "phone_call"})
|
||||
else:
|
||||
res, err_code = PhoneVerificationService.check_code(user.phone, code)
|
||||
|
||||
if res:
|
||||
user.is_phone_verified = True
|
||||
user.full_clean()
|
||||
user.save()
|
||||
return api_make_response({})
|
||||
|
||||
try:
|
||||
token = UserToken.create_token(user)
|
||||
return api_make_response({"access_token": token.access_token})
|
||||
|
||||
except Exception as ex:
|
||||
# если вдруг токен нельзя создать
|
||||
user.delete()
|
||||
raise ex
|
||||
|
||||
except ValidationError as validation_error:
|
||||
traceback.print_exc()
|
||||
errors = {}
|
||||
for field_name in validation_error.error_dict:
|
||||
err_list = validation_error.error_dict[field_name]
|
||||
print(err_list)
|
||||
obj = []
|
||||
for err in err_list:
|
||||
obj.append({
|
||||
"code": err.code
|
||||
})
|
||||
errors[field_name] = obj
|
||||
raise Exception(API_ERROR_USER_REGISTER, errors)
|
||||
|
||||
@staticmethod
|
||||
@api_method("account.verifyPhone",
|
||||
doc="Запросить верификацию номера телефона."
|
||||
"Если телефон уже верифицирован, метод вернет соответствующую ошибку",
|
||||
params=[
|
||||
API_PARAM_ACCESS_TOKEN,
|
||||
api_make_param("code", int, "Код верификации. Если не передать, будет выполнен звонок", False),
|
||||
],
|
||||
returns='{"status": "success"}, если верификация пройдена. Иначе одну из стандартных ошибок')
|
||||
def verify_phone(access_token, code):
|
||||
user = access_token.user
|
||||
|
||||
if user.is_phone_verified:
|
||||
raise Exception(API_ERROR_VALIDATION_CURRENTLY_VERIFIED)
|
||||
|
||||
if code is None:
|
||||
res, err_code = PhoneVerificationService.send_verify(user.phone)
|
||||
|
||||
if not res:
|
||||
if err_code in API_ERROR_VALIDATION:
|
||||
raise Exception(API_ERROR_VALIDATION[err_code])
|
||||
else:
|
||||
raise Exception(API_ERROR_VALIDATION_UNKNOWN)
|
||||
|
||||
return api_make_response({"action": "phone_call"})
|
||||
else:
|
||||
if err_code in API_ERROR_VALIDATION:
|
||||
raise Exception(API_ERROR_VALIDATION[err_code])
|
||||
res, err_code = PhoneVerificationService.check_code(user.phone, code)
|
||||
|
||||
if res:
|
||||
user.is_phone_verified = True
|
||||
user.save()
|
||||
return api_make_response({})
|
||||
else:
|
||||
raise Exception(API_ERROR_VALIDATION_UNKNOWN)
|
||||
if err_code in API_ERROR_VALIDATION:
|
||||
raise Exception(API_ERROR_VALIDATION[err_code])
|
||||
else:
|
||||
raise Exception(API_ERROR_VALIDATION_UNKNOWN)
|
||||
|
||||
@staticmethod
|
||||
@api_method("account.get",
|
||||
doc="Получение информации о пользователе",
|
||||
params=[
|
||||
API_PARAM_ACCESS_TOKEN,
|
||||
],
|
||||
returns="Поля пользователя (name, surname, email, phone, phone_verified).")
|
||||
def get(access_token):
|
||||
user = access_token.user
|
||||
return api_make_response({
|
||||
"id": user.id,
|
||||
"name": user.name,
|
||||
"surname": user.surname,
|
||||
"email": user.email,
|
||||
"phone": user.phone,
|
||||
"phone_verified": user.is_phone_verified
|
||||
})
|
||||
|
||||
|
||||
def account_get(params):
|
||||
user = _require_access_token(params)
|
||||
return api_make_response({
|
||||
"id": user.id,
|
||||
"name": user.name,
|
||||
"surname": user.surname,
|
||||
"email": user.email,
|
||||
"phone": user.phone,
|
||||
"phone_verified": user.is_phone_verified
|
||||
})
|
||||
class ApiOrder:
|
||||
@staticmethod
|
||||
@api_method("order.getForm",
|
||||
doc="Получение формы создания заказа в виде полей, которые нужно показать пользователю",
|
||||
params=[],
|
||||
returns="<code>JSON</code> объект, содержащий данные формы. Структура пока не определена")
|
||||
def get_form():
|
||||
return api_make_response({
|
||||
"fields": [
|
||||
# name = models.CharField(max_length=200, verbose_name="Название заказа")
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Название заказа",
|
||||
"widget": "text",
|
||||
"attrs": {
|
||||
"max_len": 200
|
||||
},
|
||||
"required": True
|
||||
},
|
||||
|
||||
# square = models.DecimalField(max_digits=7, decimal_places=2, blank=False, verbose_name="Площадь в м²")
|
||||
{
|
||||
"name": "square",
|
||||
"label": "Площадь в м²",
|
||||
"widget": "decimal",
|
||||
"attrs": {
|
||||
"max_digits": 7,
|
||||
"decimal_places": 2
|
||||
},
|
||||
"required": True
|
||||
},
|
||||
|
||||
# work_time = models.CharField(max_length=100, blank=True, verbose_name="Рабочее время")
|
||||
{
|
||||
"name": "work_time",
|
||||
"label": "Рабочее время",
|
||||
"widget": "text",
|
||||
"attrs": {
|
||||
"max_len": 100
|
||||
},
|
||||
"required": False
|
||||
},
|
||||
|
||||
# type_of_renovation = models.CharField(max_length=10, choices=TYPE_OF_RENOVATION_CHOICES,
|
||||
# default=CHOICE_UNDEFINED, blank=True, verbose_name="Тип ремонта")
|
||||
{
|
||||
"name": "type_of_renovation",
|
||||
"label": "Тип ремонта",
|
||||
"widget": "choice",
|
||||
"attrs": {
|
||||
"default": None,
|
||||
"choices": Order.TYPE_OF_RENOVATION_CHOICES
|
||||
},
|
||||
"required": False
|
||||
},
|
||||
|
||||
# type_of_room = models.CharField(max_length=10, choices=TYPE_OF_ROOM_CHOICES,
|
||||
# blank=True, default=CHOICE_UNDEFINED, verbose_name="Тип квартиры")
|
||||
{
|
||||
"name": "type_of_room",
|
||||
"label": "Тип квартиры",
|
||||
"widget": "radio",
|
||||
"attrs": {
|
||||
"default": None,
|
||||
"choices": Order.TYPE_OF_ROOM_CHOICES
|
||||
},
|
||||
"required": False
|
||||
},
|
||||
|
||||
# флажок
|
||||
{
|
||||
"name": "is_with_warranty",
|
||||
"label": "С гарантией",
|
||||
"widget": "checkbox",
|
||||
"attrs": {
|
||||
"default": True
|
||||
},
|
||||
"required": False
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
def __make_argument_doc(name, arg_type, description, required=True):
|
||||
return {
|
||||
"name": name,
|
||||
"type": arg_type,
|
||||
"description": description,
|
||||
"required": required
|
||||
}
|
||||
def api_call_method(method_name, params: dict):
|
||||
try:
|
||||
if method_name in api_methods_dict:
|
||||
out = api_methods_dict[method_name]["func"](**params)
|
||||
if out is None:
|
||||
raise Exception(API_ERROR_INTERNAL_ERROR, "method returned null object")
|
||||
else:
|
||||
raise Exception(API_ERROR_METHOD_NOT_FOUND)
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
out = make_error_object(ex)
|
||||
return out
|
||||
|
||||
|
||||
def __make_argument_access_token():
|
||||
return __make_argument_doc("access_token", "string", "<i>Токен</i>, выданный методом <code>account.auth</code>")
|
||||
|
||||
|
||||
__doc_type_string = "string"
|
||||
|
||||
|
||||
api_methods = {
|
||||
"account.auth": {
|
||||
"func": account_auth,
|
||||
"doc": "Аутентификация пользователя",
|
||||
"params": [
|
||||
__make_argument_doc("login", __doc_type_string, "Логин пользователя"),
|
||||
__make_argument_doc("password", __doc_type_string, "Пароль пользователя"),
|
||||
],
|
||||
"returns": "В случае правильных логина и пароля <code>access_token</code>. В противном случае объект ошибки."
|
||||
},
|
||||
|
||||
"account.deauth": {
|
||||
"func": account_deauth,
|
||||
"doc": "Удаление токена, дальшейшие вызовы API с этим токеном вернут ошибку невалидного токена",
|
||||
"params": [
|
||||
__make_argument_access_token()
|
||||
],
|
||||
"returns": "В случае успеха стандартный код успеха"
|
||||
},
|
||||
|
||||
"account.register": {
|
||||
"func": account_register,
|
||||
"doc": "Регистрация нового пользователя",
|
||||
"params": [
|
||||
|
||||
],
|
||||
"returns": "Поля пользователя (id, name, surname, email, phone, phone_verified)."
|
||||
},
|
||||
|
||||
"account.verifyPhone": {
|
||||
"func": account_verify_phone,
|
||||
"doc": "Запросить верификацию номера телефона."
|
||||
"Если телефон уже верифицирован, метод вернет соответствующую ошибку",
|
||||
"params": [
|
||||
__make_argument_access_token(),
|
||||
__make_argument_doc("code", __doc_type_string, "Код верификации. Если не передать, будет выполнен звонок"),
|
||||
],
|
||||
"returns": '{"status": "success"}, если верификация пройдена. Иначе одну из стандартных ошибок'
|
||||
},
|
||||
|
||||
"account.get": {
|
||||
"func": account_get,
|
||||
"doc": "Получение информации о пользователе",
|
||||
"params": [
|
||||
__make_argument_access_token()
|
||||
],
|
||||
"returns": "Поля пользователя (name, surname, email, phone, phone_verified)."
|
||||
},
|
||||
}
|
||||
def api_get_documentation():
|
||||
# {
|
||||
# "name": p["name"],
|
||||
# "type": p["type"],
|
||||
# "description": p["description"],
|
||||
# "required": p["required"]
|
||||
# }
|
||||
return []
|
@ -1,4 +1,6 @@
|
||||
from .api_errors import *
|
||||
from .models import UserToken
|
||||
api_methods_dict = {}
|
||||
|
||||
|
||||
def __make_invalid_argument_type_error(name, value, except_type):
|
||||
@ -10,7 +12,7 @@ def api_make_response(response):
|
||||
return API_OK_OBJ | {"response": response}
|
||||
|
||||
|
||||
def api_get_param_int(params: dict, name: str, required=True, default=0):
|
||||
def api_get_param_int(params: dict, name: str, required=True, default=None):
|
||||
if name in params:
|
||||
try:
|
||||
return int(params[name])
|
||||
@ -30,4 +32,70 @@ def api_get_param_str(params: dict, name: str, required=True, default=""):
|
||||
if required:
|
||||
raise Exception(API_ERROR_MISSING_ARGUMENT, name)
|
||||
|
||||
return default
|
||||
return None
|
||||
|
||||
|
||||
def api_get_access_token(params: dict, unused_name, required=True):
|
||||
token = api_get_param_str(params, "access_token", required)[0]
|
||||
print(f"checking token '{token}'")
|
||||
return UserToken.get_by_token(token)
|
||||
|
||||
|
||||
def api_make_param(name, arg_class, description, required=True):
|
||||
return {
|
||||
"name": name,
|
||||
"type": arg_class,
|
||||
"description": description,
|
||||
"required": required
|
||||
}
|
||||
|
||||
|
||||
API_PARAM_ACCESS_TOKEN = api_make_param(
|
||||
"access_token",
|
||||
UserToken,
|
||||
"<i>Токен</i>, выданный методом <code>account.auth</code>"
|
||||
)
|
||||
|
||||
|
||||
def api_method(func_name, doc="", params: list or None = None, returns=""):
|
||||
"""
|
||||
Декоратор для методов API, автоматически передает параметры методам
|
||||
"""
|
||||
def actual_decorator(func):
|
||||
def wrapper(**kwargs):
|
||||
print(f"> call method {func_name} with params {kwargs}. method params: {params}")
|
||||
|
||||
errors = []
|
||||
func_args = {}
|
||||
for p in params:
|
||||
parser_funcs = {
|
||||
str: api_get_param_str,
|
||||
int: api_get_param_int,
|
||||
UserToken: api_get_access_token
|
||||
}
|
||||
|
||||
try:
|
||||
if p["type"] not in parser_funcs:
|
||||
raise Exception(API_ERROR_INTERNAL_ERROR, f"param type {p['type']} is unsupported")
|
||||
func_args[p["name"]] = parser_funcs[p["type"]](kwargs, p["name"], p["required"])
|
||||
except Exception as ex:
|
||||
errors.append(ex)
|
||||
print(f"errors: {errors}, args: {func_args}")
|
||||
if len(errors) > 0:
|
||||
return make_error_object(errors)
|
||||
else:
|
||||
out = func(**func_args)
|
||||
if out is None:
|
||||
raise Exception(API_ERROR_INTERNAL_ERROR, "method returned null object")
|
||||
return out
|
||||
|
||||
api_methods_dict[func_name] = {
|
||||
"doc": doc,
|
||||
"params": params,
|
||||
"func": wrapper,
|
||||
"return": returns
|
||||
}
|
||||
return wrapper
|
||||
|
||||
return actual_decorator
|
||||
|
||||
|
@ -46,11 +46,15 @@ class UserToken(models.Model):
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_token(token: str):
|
||||
t = UserToken.objects.filter(access_token=token)
|
||||
return UserToken.get_by_token(token).user
|
||||
|
||||
@staticmethod
|
||||
def get_by_token(token: str):
|
||||
t = UserToken.objects.filter(access_token=token).select_related('user')
|
||||
|
||||
if len(t) == 0:
|
||||
raise Exception(API_ERROR_INVALID_TOKEN)
|
||||
return t[0].user
|
||||
return t[0]
|
||||
|
||||
def __str__(self):
|
||||
return self.user.email + ": " + self.access_token[:10] + "..."
|
||||
|
28
api/views.py
28
api/views.py
@ -3,12 +3,23 @@ import traceback
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from .api_methods import api_methods
|
||||
from .api_methods import api_call_method, api_get_documentation
|
||||
from .api_errors import *
|
||||
|
||||
|
||||
def view_methods(request):
|
||||
return render(request, 'api/index.html', {'api_methods': api_methods})
|
||||
methods = []
|
||||
|
||||
def __make_param(p):
|
||||
return {
|
||||
"name": p["name"],
|
||||
"type": p["type"],
|
||||
"description": p["description"],
|
||||
"required": p["required"]
|
||||
}
|
||||
|
||||
methods = api_get_documentation()
|
||||
return render(request, 'api/index.html', {'api_methods': methods})
|
||||
|
||||
|
||||
def call_method(request, method_name):
|
||||
@ -19,17 +30,8 @@ def call_method(request, method_name):
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
if method_name in api_methods:
|
||||
out = api_methods[method_name]["func"](params)
|
||||
if out is None:
|
||||
raise Exception(API_ERROR_INTERNAL_ERROR, "method returned null object")
|
||||
else:
|
||||
raise Exception(API_ERROR_METHOD_NOT_FOUND)
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
out = make_error_object(ex)
|
||||
out = api_call_method(method_name, params)
|
||||
|
||||
response = HttpResponse(json.dumps(out, ensure_ascii=False))
|
||||
response.headers["Content-type"] = "application/json"
|
||||
response.headers["Content-type"] = "application/json; charset=utf-8"
|
||||
return response
|
||||
|
@ -148,7 +148,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# LOGIN_REDIRECT_URL = '/account/'
|
||||
|
||||
PHONE_VERIFICATION_ENABLE = False
|
||||
PHONE_VERIFICATION_ENABLE = True
|
||||
PHONE_VERIFICATION_ATTEMPTS = 5
|
||||
PHONE_VERIFICATION_RESEND_TIME_SECS = 180
|
||||
PHONE_VERIFICATION_APP_ID = "ACC90F4A-FE4A-5137-45C9-5D9E84DE9440"
|
||||
|
@ -9,6 +9,8 @@ from account.models import SiteUser
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class City(models.Model):
|
||||
code = models.CharField(primary_key=True, max_length=20, verbose_name="Код города", validators=[
|
||||
@ -180,7 +182,7 @@ class Order(models.Model):
|
||||
create_time = models.DateTimeField(default=datetime.now, editable=False, verbose_name="Время создания")
|
||||
|
||||
moderated = models.BooleanField(default=True, verbose_name="Модерирован")
|
||||
published = models.BooleanField(default=False, verbose_name="Опубликован")
|
||||
published = models.BooleanField(default=True, verbose_name="Опубликован")
|
||||
|
||||
def clean(self):
|
||||
errors = {}
|
||||
@ -242,16 +244,27 @@ def _upload_image_filename(instance, filename):
|
||||
return "order-images/" + fn
|
||||
|
||||
|
||||
# TOFIX сделать удаление ненужных картинок из файловой системы
|
||||
|
||||
class OrderImage(models.Model):
|
||||
MAX_IMAGES = 10
|
||||
|
||||
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="order", verbose_name="Заказ")
|
||||
image = models.ImageField(upload_to=_upload_image_filename, verbose_name="Картинка",
|
||||
width_field=None, height_field=None)
|
||||
image = models.ImageField(upload_to=_upload_image_filename, verbose_name="Картинка")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.id}: {self.order}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(OrderImage, self).save(*args, **kwargs)
|
||||
img = Image.open(self.image.path)
|
||||
|
||||
if img.width > 1920 or img.height > 1080:
|
||||
# нужно урезать фортку так, чтобы пропорции уменьшились пропорционально
|
||||
output_size = (1920, 1080)
|
||||
img.thumbnail(output_size)
|
||||
img.save(self.image.path)
|
||||
|
||||
|
||||
class OrderRespond(models.Model):
|
||||
create_time = models.DateTimeField(default=datetime.now, editable=False, verbose_name="Время отклика")
|
||||
|
@ -1,28 +1,79 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %} Аккаунт | вход {% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: var(--brand-color) solid 1px;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
th {
|
||||
color: var(--brand-color);
|
||||
}
|
||||
|
||||
details > div {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
margin-left: 0.5em;
|
||||
padding-left: 1em;
|
||||
border-left: var(--brand-color) solid 1px;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class=deprecated-page-header"> Список методов API </h1>
|
||||
|
||||
{% for method in api_methods %}
|
||||
<div>
|
||||
<h2>{{ method }}</h2>
|
||||
<h2>{{ method.name }}</h2>
|
||||
|
||||
<details>
|
||||
<summary>Описание</summary>
|
||||
<div>
|
||||
{{ api_methods.method.doc | safe }}
|
||||
{{ method.doc | safe }}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Парамеры</summary>
|
||||
<div>
|
||||
{% for param in api_methods.method.params %}
|
||||
<div>
|
||||
{{ param }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if method.params %}
|
||||
<table>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Тип</th>
|
||||
<th>Описание</th>
|
||||
<th>Обязательный</th>
|
||||
</tr>
|
||||
|
||||
{% for param in method.params %}
|
||||
<tr>
|
||||
<td>{{ param.name }}</td>
|
||||
<td>{{ param.type }}</td>
|
||||
<td>{{ param.description | safe }}</td>
|
||||
<td>{{ param.required }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<p>
|
||||
Этот метод не принимает параметров.
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Результат</summary>
|
||||
<div>
|
||||
{{ method.returns | safe }}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user