Обновление API Utils для работы с медиа

This commit is contained in:
VladislavOstapov 2023-03-14 15:15:36 +03:00
parent 35b1fae88b
commit d4158ae1c0
9 changed files with 113 additions and 25 deletions

View File

@ -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']

View File

@ -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')

View File

@ -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

View File

@ -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)

View File

@ -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,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")
name = p.get_name()
if isinstance(p, ApiRequestParam):
func_args[name] = __raw_request
else:
value = kwargs[name] if name in kwargs else None
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:
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] = {

View File

@ -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():
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 {}

View File

@ -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

View File

@ -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

View File

@ -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