Обновление API Utils для работы с медиа
This commit is contained in:
parent
35b1fae88b
commit
d4158ae1c0
@ -9,6 +9,11 @@ class AccountAdmin(admin.ModelAdmin):
|
||||
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)
|
||||
class ExecutorAccountAdmin(admin.ModelAdmin):
|
||||
# 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_NOT_ALLOWED = (105, 'operation not allowed')
|
||||
|
||||
API_ERROR_INVALID_REQUEST = (110, 'invalid request')
|
||||
|
||||
API_ERROR_METHOD_NOT_FOUND = (200, 'method not found')
|
||||
API_ERROR_MISSING_ARGUMENT = (201, 'missing argument')
|
||||
API_ERROR_UNKNOWN_ARGUMENT = (202, 'unknown argument')
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from .api_utils import *
|
||||
from .api_params import *
|
||||
from .models import *
|
||||
@ -547,7 +548,7 @@ class ApiOrder:
|
||||
returns="ID созданного заказа, иначе одну из ошибок")
|
||||
async def create(**kwargs):
|
||||
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'))
|
||||
|
||||
@ -567,7 +568,7 @@ class ApiOrder:
|
||||
],
|
||||
returns="Обновленный объект заказа")
|
||||
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)
|
||||
order = await query.afirst()
|
||||
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()])
|
||||
|
||||
@staticmethod
|
||||
def _check_modify_permissions(access_token):
|
||||
def _check_write_permissions(access_token):
|
||||
if not access_token.user.is_completed():
|
||||
raise Exception(API_ERROR_NEED_COMPLETED_ACCOUNT)
|
||||
if access_token.user.role != Account.ROLE_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:
|
||||
return await api_methods_dict[method_name]["func"](**params)
|
||||
return await api_methods_dict[method_name]["func"](__raw_request=request, **params)
|
||||
else:
|
||||
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():
|
||||
out = []
|
||||
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({
|
||||
"name": m,
|
||||
"doc": api_methods_dict[m]["doc"],
|
||||
"returns": api_methods_dict[m]["returns"],
|
||||
"params": [p.to_json() for p in api_methods_dict[m]["params"]]
|
||||
"params": params
|
||||
})
|
||||
return out
|
||||
|
@ -53,6 +53,24 @@ class ApiParam:
|
||||
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):
|
||||
def __init__(self, regex=None, max_length=None, min_length=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
@ -1,8 +1,5 @@
|
||||
import asyncio
|
||||
from .api_errors import *
|
||||
from .api_params import ApiParam
|
||||
|
||||
# TODO запилить класс для параметров: телефон, пароль, почта
|
||||
from django.http import HttpResponse
|
||||
from .api_params import *
|
||||
|
||||
|
||||
api_methods_dict = {}
|
||||
@ -32,7 +29,7 @@ def api_method(func_name, doc="", params: list or None = None, returns=""):
|
||||
Декоратор для методов API, автоматически валидирует и передает параметры методам
|
||||
"""
|
||||
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}")
|
||||
|
||||
errors = []
|
||||
@ -43,12 +40,16 @@ 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")
|
||||
|
||||
name = p.get_name()
|
||||
value = kwargs[name] if name in kwargs else None
|
||||
|
||||
if asyncio.iscoroutinefunction(p.validate):
|
||||
func_args[name] = await p.validate(value)
|
||||
if isinstance(p, ApiRequestParam):
|
||||
func_args[name] = __raw_request
|
||||
else:
|
||||
func_args[name] = p.validate(value)
|
||||
value = kwargs[name] if name in kwargs else None
|
||||
|
||||
if asyncio.iscoroutinefunction(p.validate):
|
||||
func_args[name] = await p.validate(value)
|
||||
else:
|
||||
func_args[name] = p.validate(value)
|
||||
|
||||
except Exception as ex:
|
||||
errors.append(ex)
|
||||
@ -69,6 +70,10 @@ def api_method(func_name, doc="", params: list or None = None, returns=""):
|
||||
|
||||
if out is None:
|
||||
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
|
||||
|
||||
api_methods_dict[func_name] = {
|
||||
|
@ -8,6 +8,8 @@ from hashlib import sha512, sha256
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from django.db.utils import ProgrammingError
|
||||
|
||||
from .api_errors import *
|
||||
import re
|
||||
|
||||
@ -27,7 +29,10 @@ class City(models.Model):
|
||||
|
||||
@staticmethod
|
||||
def to_choices():
|
||||
return list(City.objects.order_by('name').values_list('code', 'name'))
|
||||
try:
|
||||
return list(City.objects.order_by('name').values_list('code', 'name'))
|
||||
except ProgrammingError:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
|
||||
|
||||
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():
|
||||
return {}
|
||||
|
||||
|
14
api/views.py
14
api/views.py
@ -1,6 +1,4 @@
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
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]
|
||||
|
||||
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.headers["Content-type"] = "application/json; charset=utf-8"
|
||||
return response
|
||||
else:
|
||||
return out
|
||||
|
||||
response = HttpResponse(json.dumps(out, default=_default_serializer, ensure_ascii=False, indent=4))
|
||||
response.headers["Content-type"] = "application/json; charset=utf-8"
|
||||
return response
|
||||
|
@ -29,8 +29,8 @@ SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ["arka.topserv4824.duckdns.org", "192.168.0.160", "localhost"]
|
||||
CSRF_TRUSTED_ORIGINS = ['https://arka.topserv4824.duckdns.org']
|
||||
ALLOWED_HOSTS = ["arka-dev.topserv4824.duckdns.org", "192.168.0.160", "localhost"]
|
||||
CSRF_TRUSTED_ORIGINS = ['https://arka-dev.topserv4824.duckdns.org']
|
||||
|
||||
# Application definition
|
||||
|
||||
|
@ -13,6 +13,7 @@ Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf.urls.static import static
|
||||
|
Reference in New Issue
Block a user