Обновление API Utils для работы с медиа
This commit is contained in:
parent
35b1fae88b
commit
d4158ae1c0
@ -9,6 +9,11 @@ class AccountAdmin(admin.ModelAdmin):
|
|||||||
readonly_fields = ['id', 'register_datetime']
|
readonly_fields = ['id', 'register_datetime']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Media)
|
||||||
|
class MediaAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['id', 'user', 'storage_name', 'original_name']
|
||||||
|
readonly_fields = ['id', 'upload_datetime']
|
||||||
|
|
||||||
@admin.register(ExecutorAccount)
|
@admin.register(ExecutorAccount)
|
||||||
class ExecutorAccountAdmin(admin.ModelAdmin):
|
class ExecutorAccountAdmin(admin.ModelAdmin):
|
||||||
# fields = ['name', 'surname', 'phone', 'email', 'register_datetime']
|
# fields = ['name', 'surname', 'phone', 'email', 'register_datetime']
|
||||||
|
@ -15,6 +15,8 @@ API_ERROR_ACCESS_DENIED = (103, 'you cannot call this method: permission denied'
|
|||||||
API_ERROR_NEED_COMPLETED_ACCOUNT = (104, 'need completed account')
|
API_ERROR_NEED_COMPLETED_ACCOUNT = (104, 'need completed account')
|
||||||
API_ERROR_NOT_ALLOWED = (105, 'operation not allowed')
|
API_ERROR_NOT_ALLOWED = (105, 'operation not allowed')
|
||||||
|
|
||||||
|
API_ERROR_INVALID_REQUEST = (110, 'invalid request')
|
||||||
|
|
||||||
API_ERROR_METHOD_NOT_FOUND = (200, 'method not found')
|
API_ERROR_METHOD_NOT_FOUND = (200, 'method not found')
|
||||||
API_ERROR_MISSING_ARGUMENT = (201, 'missing argument')
|
API_ERROR_MISSING_ARGUMENT = (201, 'missing argument')
|
||||||
API_ERROR_UNKNOWN_ARGUMENT = (202, 'unknown argument')
|
API_ERROR_UNKNOWN_ARGUMENT = (202, 'unknown argument')
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
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 *
|
||||||
@ -547,7 +548,7 @@ class ApiOrder:
|
|||||||
returns="ID созданного заказа, иначе одну из ошибок")
|
returns="ID созданного заказа, иначе одну из ошибок")
|
||||||
async def create(**kwargs):
|
async def create(**kwargs):
|
||||||
access_token = kwargs.pop('access_token')
|
access_token = kwargs.pop('access_token')
|
||||||
ApiOrder._check_modify_permissions(access_token)
|
ApiOrder._check_write_permissions(access_token)
|
||||||
|
|
||||||
city = await City.get_by_code(kwargs.pop('address_city'))
|
city = await City.get_by_code(kwargs.pop('address_city'))
|
||||||
|
|
||||||
@ -567,7 +568,7 @@ class ApiOrder:
|
|||||||
],
|
],
|
||||||
returns="Обновленный объект заказа")
|
returns="Обновленный объект заказа")
|
||||||
async def set_published(access_token, order_id, value):
|
async def set_published(access_token, order_id, value):
|
||||||
ApiOrder._check_modify_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.user.id:
|
||||||
@ -627,16 +628,35 @@ class ApiOrder:
|
|||||||
return api_make_response([ApiOrder._order_to_json(item) async for item in query.all()])
|
return api_make_response([ApiOrder._order_to_json(item) async for item in query.all()])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_modify_permissions(access_token):
|
def _check_write_permissions(access_token):
|
||||||
if not access_token.user.is_completed():
|
if not access_token.user.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.user.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')
|
||||||
|
|
||||||
|
|
||||||
async def api_call_method(method_name, params: dict):
|
class ApiMedia:
|
||||||
|
# поскольку media.upload это не совсем стандартная функция, обернем фейковый метод чтоб была документация
|
||||||
|
@staticmethod
|
||||||
|
@api_method("media.upload",
|
||||||
|
doc="Загрузка медиа на сервер. Вызывать методом POST.",
|
||||||
|
params=[
|
||||||
|
ApiRequestParam(),
|
||||||
|
ApiParamAccessToken(),
|
||||||
|
ApiParamStr(name="filename", description="Название файла",
|
||||||
|
min_length=5, max_length=60),
|
||||||
|
], returns="<code>id</code> медиа, в противном случае ошибку")
|
||||||
|
def upload(request, access_token, filename):
|
||||||
|
# ну шож, метод фейковый, все проверки нужно сделать руками
|
||||||
|
if request.method != "POST":
|
||||||
|
return make_error_object(Exception(API_ERROR_INVALID_REQUEST, "method must be executed http POST method"))
|
||||||
|
|
||||||
|
return HttpResponse("Да пошел ты нах со своим аплоадом")
|
||||||
|
|
||||||
|
|
||||||
|
async def api_call_method(request, method_name, params: dict):
|
||||||
if method_name in api_methods_dict:
|
if method_name in api_methods_dict:
|
||||||
return await api_methods_dict[method_name]["func"](**params)
|
return await api_methods_dict[method_name]["func"](__raw_request=request, **params)
|
||||||
else:
|
else:
|
||||||
return make_error_object(Exception(API_ERROR_METHOD_NOT_FOUND))
|
return make_error_object(Exception(API_ERROR_METHOD_NOT_FOUND))
|
||||||
|
|
||||||
@ -644,10 +664,16 @@ async def api_call_method(method_name, params: dict):
|
|||||||
def api_get_documentation():
|
def api_get_documentation():
|
||||||
out = []
|
out = []
|
||||||
for m in api_methods_dict:
|
for m in api_methods_dict:
|
||||||
|
params = []
|
||||||
|
for p in api_methods_dict[m]["params"]:
|
||||||
|
j = p.to_json()
|
||||||
|
if j is not None:
|
||||||
|
params.append(j)
|
||||||
|
|
||||||
out.append({
|
out.append({
|
||||||
"name": m,
|
"name": m,
|
||||||
"doc": api_methods_dict[m]["doc"],
|
"doc": api_methods_dict[m]["doc"],
|
||||||
"returns": api_methods_dict[m]["returns"],
|
"returns": api_methods_dict[m]["returns"],
|
||||||
"params": [p.to_json() for p in api_methods_dict[m]["params"]]
|
"params": params
|
||||||
})
|
})
|
||||||
return out
|
return out
|
||||||
|
@ -53,6 +53,24 @@ class ApiParam:
|
|||||||
return f"{type(self)}: name={self.name}"
|
return f"{type(self)}: name={self.name}"
|
||||||
|
|
||||||
|
|
||||||
|
# Специальный класс параметра, нужен для получения доступа к raw request
|
||||||
|
class ApiRequestParam(ApiParam):
|
||||||
|
def __init__(self, name="request", description=None, **kwargs):
|
||||||
|
super().__init__(name=name, description=description, **kwargs)
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_type_name(self):
|
||||||
|
return "__internal_request"
|
||||||
|
|
||||||
|
def get_doc(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ApiParamStr(ApiParam):
|
class ApiParamStr(ApiParam):
|
||||||
def __init__(self, regex=None, max_length=None, min_length=None, **kwargs):
|
def __init__(self, regex=None, max_length=None, min_length=None, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import asyncio
|
from django.http import HttpResponse
|
||||||
from .api_errors import *
|
from .api_params import *
|
||||||
from .api_params import ApiParam
|
|
||||||
|
|
||||||
# TODO запилить класс для параметров: телефон, пароль, почта
|
|
||||||
|
|
||||||
|
|
||||||
api_methods_dict = {}
|
api_methods_dict = {}
|
||||||
@ -32,7 +29,7 @@ def api_method(func_name, doc="", params: list or None = None, returns=""):
|
|||||||
Декоратор для методов API, автоматически валидирует и передает параметры методам
|
Декоратор для методов API, автоматически валидирует и передает параметры методам
|
||||||
"""
|
"""
|
||||||
def actual_decorator(func):
|
def actual_decorator(func):
|
||||||
async def wrapper(**kwargs):
|
async def wrapper(__raw_request, **kwargs):
|
||||||
print(f"> call method {func_name} with params {kwargs}. method params: {params}")
|
print(f"> call method {func_name} with params {kwargs}. method params: {params}")
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
@ -43,6 +40,10 @@ def api_method(func_name, doc="", params: list or None = None, returns=""):
|
|||||||
raise Exception(API_ERROR_INTERNAL_ERROR, f"param {p} is not instance of ApiParam class")
|
raise Exception(API_ERROR_INTERNAL_ERROR, f"param {p} is not instance of ApiParam class")
|
||||||
|
|
||||||
name = p.get_name()
|
name = p.get_name()
|
||||||
|
|
||||||
|
if isinstance(p, ApiRequestParam):
|
||||||
|
func_args[name] = __raw_request
|
||||||
|
else:
|
||||||
value = kwargs[name] if name in kwargs else None
|
value = kwargs[name] if name in kwargs else None
|
||||||
|
|
||||||
if asyncio.iscoroutinefunction(p.validate):
|
if asyncio.iscoroutinefunction(p.validate):
|
||||||
@ -69,6 +70,10 @@ def api_method(func_name, doc="", params: list or None = None, returns=""):
|
|||||||
|
|
||||||
if out is None:
|
if out is None:
|
||||||
return make_error_object(Exception(API_ERROR_INTERNAL_ERROR, "method returned null object"))
|
return make_error_object(Exception(API_ERROR_INTERNAL_ERROR, "method returned null object"))
|
||||||
|
|
||||||
|
if not isinstance(out, dict) and not isinstance(out, HttpResponse):
|
||||||
|
return make_error_object(Exception(API_ERROR_INTERNAL_ERROR, "method returned invalid object type"))
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
api_methods_dict[func_name] = {
|
api_methods_dict[func_name] = {
|
||||||
|
@ -8,6 +8,8 @@ from hashlib import sha512, sha256
|
|||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from django.db.utils import ProgrammingError
|
||||||
|
|
||||||
from .api_errors import *
|
from .api_errors import *
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -27,7 +29,10 @@ class City(models.Model):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_choices():
|
def to_choices():
|
||||||
|
try:
|
||||||
return list(City.objects.order_by('name').values_list('code', 'name'))
|
return list(City.objects.order_by('name').values_list('code', 'name'))
|
||||||
|
except ProgrammingError:
|
||||||
|
return []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_by_code(code):
|
async def get_by_code(code):
|
||||||
@ -115,6 +120,30 @@ class Account(models.Model):
|
|||||||
return self.name != '' and self.surname != '' and self.city_id is not None
|
return self.name != '' and self.surname != '' and self.city_id is not None
|
||||||
|
|
||||||
|
|
||||||
|
class Media(models.Model):
|
||||||
|
user = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
|
||||||
|
storage_name = models.CharField(max_length=100, verbose_name="Файл в хранилище")
|
||||||
|
original_name = models.CharField(max_length=100, verbose_name="Имя файла", default="")
|
||||||
|
upload_datetime = models.DateTimeField(default=datetime.now, editable=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_by_id(user_id: int, media_id: int):
|
||||||
|
m = Media.objects.filter(user_id=user_id, id=media_id).select_related('executoraccount', 'city')
|
||||||
|
return await m.afirst()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_media():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def generate_storage_name(self):
|
||||||
|
if self.storage_name is None:
|
||||||
|
source_str = f"{self.original_name} {self.original_name} {self.upload_datetime} {self.user.id}"
|
||||||
|
self.storage_name = sha512(bytearray(source_str, 'utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user}: \"{self.original_name}\" ({self.id})"
|
||||||
|
|
||||||
|
|
||||||
def _executor_additional_info_default():
|
def _executor_additional_info_default():
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import traceback
|
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest
|
from django.http import HttpResponse, HttpResponseBadRequest
|
||||||
from .api_methods import api_call_method, api_get_documentation
|
from .api_methods import api_call_method, api_get_documentation
|
||||||
@ -30,8 +28,12 @@ async def call_method(request, method_name):
|
|||||||
# защита от нескольких параметров с одним именем
|
# защита от нескольких параметров с одним именем
|
||||||
api_params[p] = params[p]
|
api_params[p] = params[p]
|
||||||
|
|
||||||
out = await api_call_method(method_name, api_params)
|
out = await api_call_method(request, method_name, api_params)
|
||||||
|
|
||||||
|
if isinstance(out, dict):
|
||||||
response = HttpResponse(json.dumps(out, default=_default_serializer, ensure_ascii=False, indent=4))
|
response = HttpResponse(json.dumps(out, default=_default_serializer, ensure_ascii=False, indent=4))
|
||||||
response.headers["Content-type"] = "application/json; charset=utf-8"
|
response.headers["Content-type"] = "application/json; charset=utf-8"
|
||||||
return response
|
return response
|
||||||
|
else:
|
||||||
|
return out
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["arka.topserv4824.duckdns.org", "192.168.0.160", "localhost"]
|
ALLOWED_HOSTS = ["arka-dev.topserv4824.duckdns.org", "192.168.0.160", "localhost"]
|
||||||
CSRF_TRUSTED_ORIGINS = ['https://arka.topserv4824.duckdns.org']
|
CSRF_TRUSTED_ORIGINS = ['https://arka-dev.topserv4824.duckdns.org']
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ Including another URLconf
|
|||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
Reference in New Issue
Block a user