Внедрение проекта

main
pilot 3 months ago
parent b5b5398946
commit 71974e2dce

@ -3,3 +3,4 @@ requests
mysqlclient mysqlclient
qrcode qrcode
django-environ django-environ
pillow

@ -1,9 +1,8 @@
import calendar
import json import json
import requests import requests
from max_bot.models import Client from max_bot.models import Client
from restoran_max_bot.common import common_get_menu from restoran_max_bot.common import common_get_menu, common_get_data
def maxbot_send_text_message(chat_id: str, max_token: str, message: str): def maxbot_send_text_message(chat_id: str, max_token: str, message: str):
@ -61,8 +60,9 @@ def maxbot_send_menu_button(client: Client, chat_id: str, max_token: str, messag
emojii = str(mn.emojii) emojii = str(mn.emojii)
else: else:
emojii = '' emojii = ''
payload_data = {"payload_type": "menu", "data": mn.title}
button_data = {"type": "callback", "text": " ".join([emojii, str(mn.title)]), "payload": mn.title} button_data = {"type": "callback", "text": " ".join([emojii, str(mn.title)]),
"payload": json.dumps(payload_data)}
button.append(button_data) button.append(button_data)
cnt = cnt + 1 cnt = cnt + 1
@ -155,3 +155,104 @@ def maxbot_get_phone(chat_id: str, max_token: str, text: str):
rt = {'success': True, 'error': '', 'data': response} rt = {'success': True, 'error': '', 'data': response}
return rt return rt
def maxbot_send_day_button(client: Client, chat_id: str, max_token: str, message: str):
url = f"https://platform-api.max.ru/messages?chat_id={chat_id}"
buttons = []
button = []
cnt = 0
for dt in range(1, 32):
if cnt == 6:
cnt = 0
buttons.append(button)
button = []
payload_data = {"payload_type": "day_birthday", "data": dt}
button_data = {"type": "callback", "text": dt, "payload": json.dumps(payload_data)}
button.append(button_data)
cnt = cnt + 1
buttons.append(button)
payload = json.dumps({
"text": message,
"attachments": [
{"type": "inline_keyboard",
"payload": {
"buttons": buttons
}
}
]
})
headers = {
'Authorization': f'{max_token}',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
rt = {'success': True, 'error': '', 'data': response}
return rt
def maxbot_send_month_button(client: Client, chat_id: str, max_token: str, message: str):
months = [
'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
]
url = f"https://platform-api.max.ru/messages?chat_id={chat_id}"
buttons = []
button = []
cnt = 0
for dt in range(1, 13):
if cnt == 3:
cnt = 0
buttons.append(button)
button = []
payload_data = {"payload_type": "month_birthday", "data": dt}
button_data = {"type": "callback", "text": months[dt-1], "payload": json.dumps(payload_data)}
button.append(button_data)
cnt = cnt + 1
buttons.append(button)
payload = json.dumps({
"text": message,
"attachments": [
{"type": "inline_keyboard",
"payload": {
"buttons": buttons
}
}
]
})
headers = {
'Authorization': f'{max_token}',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
rt = {'success': True, 'error': '', 'data': response}
return rt
def maxbot_send_content(client: Client, chat_id: str, max_token: str, message: str):
data = common_get_data(client=client, key=message.lower())
if data:
for dt in data:
if dt.img:
url = f"https://cdn.telefon-ip.ru/{dt.img}?thumb=600"
maxbot_send_img_message(chat_id=chat_id, max_token=max_token, img=url, message=dt.descr)
else:
maxbot_send_text_message(chat_id=chat_id, max_token=max_token, message=dt.descr)
if dt.url:
maxbot_send_text_message(chat_id=chat_id, max_token=max_token, message=dt.url)
return True
return False

@ -164,6 +164,9 @@ class Contact(models.Model):
qr_menu = models.BooleanField(default=False) qr_menu = models.BooleanField(default=False)
webast_bot = models.BooleanField(default=False) webast_bot = models.BooleanField(default=False)
descr = models.CharField(max_length=500, null=True) descr = models.CharField(max_length=500, null=True)
field1 = models.CharField(max_length=100, null=True)
field2 = models.CharField(max_length=100, null=True)
field3 = models.CharField(max_length=100, null=True)
class Meta: class Meta:
db_table = 'lk_contact' db_table = 'lk_contact'
@ -590,3 +593,18 @@ class ProductCategory(models.Model):
class Meta: class Meta:
db_table = 'lk_product_category' db_table = 'lk_product_category'
# Транзакции по бонусам
class BonusTransaction(models.Model):
objects = None
client = models.ForeignKey(Client, on_delete=models.PROTECT)
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True)
source = models.CharField(max_length=100)
sum = models.FloatField()
balance = models.FloatField()
status = models.IntegerField()
class Meta:
db_table = 'lk_qr_bonus_transaction'

@ -1,8 +1,8 @@
from django.contrib import admin
from django.urls import path from django.urls import path
from max_bot.views import api_start_max_v1, iiko_webhook, iiko_send_message
from max_bot.views import api_start_max_v1
urlpatterns = [ urlpatterns = [
path('api/v1', api_start_max_v1), path('api/v1', api_start_max_v1),
path('iiko/webhook/<str:token>', iiko_webhook),
path('iiko/send-message/<str:token>', iiko_send_message),
] ]

@ -1,10 +1,15 @@
import json import json
from django.core.handlers.wsgi import WSGIRequest
from django.http import JsonResponse from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from max_bot.models import Partner, Integration, Client
from max_bot.max_api import maxbot_send_text_message
from max_bot.models import Partner, Integration, Client, BonusTransaction, App
from restoran_max_bot.bot_started import bot_started from restoran_max_bot.bot_started import bot_started
from restoran_max_bot.bot_stopped import bot_stopped from restoran_max_bot.bot_stopped import bot_stopped
from restoran_max_bot.bot_message import bot_message from restoran_max_bot.bot_message import bot_message
from restoran_max_bot.common import common_get_contact
from restoran_max_bot.utils import is_json, has_key from restoran_max_bot.utils import is_json, has_key
@ -12,7 +17,6 @@ def api_decorator(func):
def wrapper_api_decorator(*args, **kwargs): def wrapper_api_decorator(*args, **kwargs):
if 'X-Max-Bot-Api-Secret' in args[0].headers: if 'X-Max-Bot-Api-Secret' in args[0].headers:
token = args[0].headers['X-Max-Bot-Api-Secret'] token = args[0].headers['X-Max-Bot-Api-Secret']
token = '71232b71-dacc-4586-95d0-0f352d9d09c3'
else: else:
rt = {'success': False, 'error': 'Unauthorized token', 'data': {}} rt = {'success': False, 'error': 'Unauthorized token', 'data': {}}
return JsonResponse(rt, status=401) return JsonResponse(rt, status=401)
@ -85,3 +89,44 @@ def api_start_max_v1(request, **kwargs):
rt = {'success': True, 'error': '', 'data': ''} rt = {'success': True, 'error': '', 'data': ''}
return JsonResponse(rt, status=200) return JsonResponse(rt, status=200)
#https://maxbot.telefon-ip.ru/max/iiko/webhook/71232b71-dacc-4586-95d0-0f352d9d09c3
@csrf_exempt
def iiko_webhook(request: WSGIRequest, token):
rt = {'success': False, 'data': ''}
if request.method == "POST":
data = json.loads(request.body)
client = Client.objects.filter(token=token).first()
if client:
contact = common_get_contact(client=client, phone=data['phone'][1:])
if contact:
integration = Integration.objects.filter(client=client, status=1, partner=13).first()
if integration:
BonusTransaction.objects.create(client=client, contact=contact, source='iiko', sum=data['sum'],
balance=data['balance'], status=0)
if int(data['sum']) > 0:
text = f"💯 Изменение баланса.\n Начисления: {data['sum']} руб.\nВаш баланс: {data['balance']} руб."
else:
text = f"💯 Изменение баланса.\n Списание: {data['sum']} руб.\nВаш баланс: {data['balance']} руб."
maxbot_send_text_message(chat_id=contact.maxx, max_token=integration.token, message=text)
rt = {'success': True, 'data': ''}
return JsonResponse(rt, status=200)
@csrf_exempt
def iiko_send_message(request: WSGIRequest, token):
rt = {'success': False, 'data': ''}
# print(request.body)
if request.method == "POST":
data = json.loads(request.body)
client = Client.objects.filter(token=token).first()
if client:
contact = common_get_contact(client=client, phone=data['phone'][1:])
if contact:
integration = Integration.objects.filter(client=client, status=1, partner=13).first()
if integration:
text = 'test'
maxbot_send_text_message(chat_id=contact.maxx, max_token=integration.token, message=text)
rt = {'success': True, 'data': ''}
return JsonResponse(rt, status=200)

@ -1,32 +1,92 @@
import re
from max_bot.max_api import * from max_bot.max_api import *
from max_bot.models import Client import qrcode
from restoran_max_bot.common import common_get_data, common_get_contact from restoran_max_bot.bot_started import check_registration
from restoran_max_bot.common import *
from restoran_max_bot.iiko_api import customer_info, customer_create, customer_wallet
from restoran_max_bot.rkiper_api import rkiper_customer_info, rkiper_customer_create
from restoran_max_bot.utils import has_key
# проверка есть ли регистрация клиента # получение информации по интеграции с терминалом ikko, rkeeper
def get_registration(client: Client, chat_id: str, settings: dict): def get_bonus(client: Client, phone: str, default_bonus: float, contact: Contact):
contact = common_get_contact(client=client, uid_client=chat_id) integration = common_get_integration(client=client)
if not contact: bonus = 0
data = common_get_data(client=client, key='согласие'.lower()) date = contact.field1
if data:
for dt in data: if len(date) < 2:
if dt.img: date = '0' + date
url = f"https://cdn.telefon-ip.ru/{dt.img}?thumb=600"
maxbot_send_img_message(chat_id=chat_id, max_token=settings['token'], img=url, message=dt.descr) month = contact.field2
if len(month) < 2:
month = '0' + month
if integration:
if integration.partner.slug == 'iiko':
config = json.loads(integration.setting)
customerinfo = customer_info(config['token'], phone, config['id_org'])
if not customerinfo:
birthday = f"1990-{month}-{date} 00:00:00.000"
dt = customer_create(api_key=config['token'], phone=phone, name=contact.name,
organization_id=config['id_org'], birthday=birthday)
dt = customer_info(config['token'], phone, config['id_org'])
customer_wallet(api_key=config['token'], iiko_wallet_id=dt['walletBalances'][0]['id'],
customer_id=dt['id'], organization_id=config['id_org'],
bonus=default_bonus)
bonus = default_bonus
else: else:
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.descr) bonus = customerinfo['walletBalances']
if dt.url: bonus = bonus[0]['balance']
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.url)
# http://192.168.9.186:8890/docs
if integration.partner.slug == 'rkeeper':
config = json.loads(integration.setting)
rs = rkiper_customer_info(rkiper_server=config['url'], rkiper_token=config['token'], phone=phone)
if len(rs):
rs = rs[0]
if has_key(rs, 'card_use'):
bonus = int(rs['card_use']['sum1']) / 100
else:
birthday = f"1990-{month}-{date}"
rs = rkiper_customer_create(rkiper_server=config['url'], rkiper_token=config['token'], phone=phone,
name=contact.name, birthday=birthday)
bonus = 0
return bonus
maxbot_get_phone(chat_id=chat_id, max_token=settings['token'], text='Подтвердите согласие')
return False # получение вложения в сообщениях для их обработки (регистрация по номеру)
return True def get_attachments(message: dict):
chat_id = message['message']['recipient']['chat_id']
if 'attachments' in message['message']['body']:
if 'vcf_info' in message['message']['body']['attachments'][0]['payload']:
# Регулярное выражение: ищем всё после 'TEL;TYPE=cell:' до \r или конца строки
match = re.search(r'TEL;TYPE=cell:(\d+)',
message['message']['body']['attachments'][0]['payload']['vcf_info'])
if match:
phone_number = match.group(1)
return {'attachments_type': 'vcf_info', 'data': phone_number}
return {'attachments_type': False, 'data': ''}
def bot_message(client: Client, message: dict, settings: dict, message_type): def bot_message(client: Client, message: dict, settings: dict, message_type):
chat_id = message['message']['recipient']['chat_id'] chat_id = message['message']['recipient']['chat_id']
attachments = get_attachments(message=message)
if not get_registration(client=client, chat_id=chat_id, settings=settings): # регистрируем по номеру телефона выдаем соответствующий контент
if attachments['attachments_type'] == 'vcf_info':
common_set_contact(client=client, uid_client=chat_id, name=message['message']['sender']['name'],
phone=attachments['data'])
message['message']['body']['text'] = 'system_registration'
contact = check_registration(client=client, chat_id=chat_id, message=message, settings=settings)
if not contact:
return False return False
data = None data = None
@ -34,21 +94,35 @@ def bot_message(client: Client, message: dict, settings: dict, message_type):
data = common_get_data(client=client, key=str(message['message']['body']['text']).lower()) data = common_get_data(client=client, key=str(message['message']['body']['text']).lower())
if message_type == 'message_callback': if message_type == 'message_callback':
data = common_get_data(client=client, key=str(message['callback']['payload']).lower()) key = json.loads(message['callback']['payload'])
data = common_get_data(client=client, key=str(key['data']).lower())
if data: if data:
# maxbot_delete_all_messages(chat_id=chat_id, max_token=settings['token'])
for dt in data: for dt in data:
if dt.title == '##bonus##':
img = qrcode.make(contact.phone)
img.save(f"./restoran_max_bot/static/client_qr/{contact.phone}.png")
url = f"https://maxbot.telefon-ip.ru/static/client_qr/{contact.phone}.png"
maxbot_send_img_message(chat_id=chat_id, max_token=settings['token'], img=url,
message='QR-код для начисления, списания бонусов.')
bonus = get_bonus(client=client, phone=contact.phone, default_bonus=float(dt.price), contact=contact)
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'],
message=f"Ваши бонусы: {bonus} руб")
if dt.img: if dt.img:
url = f"https://cdn.telefon-ip.ru/{dt.img}?thumb=600" url = f"https://cdn.telefon-ip.ru/{dt.img}?thumb=600"
maxbot_send_img_message(chat_id=chat_id, max_token=settings['token'], img=url, message=dt.descr) maxbot_send_img_message(chat_id=chat_id, max_token=settings['token'], img=url, message=dt.descr)
else:
if '##' not in dt.title and not dt.img:
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.descr) maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.descr)
if dt.url: if dt.url:
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.url) maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.url)
# отправка меню # отправка меню
maxbot_send_menu_button(chat_id=chat_id, max_token=settings['token'], message='---', client=client) maxbot_send_menu_button(chat_id=chat_id, max_token=settings['token'], message='👇 Выбери раздел', client=client)
else: else:
# отправка меню # отправка меню
maxbot_send_menu_button(chat_id=chat_id, max_token=settings['token'], message='Выберите раздел', client=client) maxbot_send_menu_button(chat_id=chat_id, max_token=settings['token'], message='👇 Выбери раздел', client=client)
return True return True

@ -1,6 +1,6 @@
from max_bot.max_api import * from max_bot.max_api import *
from max_bot.models import Client from max_bot.models import Client
from restoran_max_bot.common import common_get_data from restoran_max_bot.common import common_get_data, common_get_contact, common_set_contact
def bot_started(client: Client, message: dict, settings: dict): def bot_started(client: Client, message: dict, settings: dict):
@ -19,3 +19,89 @@ def bot_started(client: Client, message: dict, settings: dict):
# отправка меню # отправка меню
maxbot_get_phone(chat_id=chat_id, max_token=settings['token'], text='Согласен на обработку') maxbot_get_phone(chat_id=chat_id, max_token=settings['token'], text='Согласен на обработку')
return True return True
# проверка есть ли регистрация клиента
def get_registration_phone(client: Client, chat_id: str, settings: dict):
contact = common_get_contact(client=client, uid_client=chat_id)
if not contact:
data = common_get_data(client=client, key='start'.lower())
if data:
for dt in data:
if dt.img:
url = f"https://cdn.telefon-ip.ru/{dt.img}?thumb=600"
maxbot_send_img_message(chat_id=chat_id, max_token=settings['token'], img=url, message=dt.descr)
else:
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.descr)
if dt.url:
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.url)
maxbot_get_phone(chat_id=chat_id, max_token=settings['token'], text='Подтвердите согласие')
return False
return contact
# проверка есть ли регистрация клиента
def get_registration_birthday(client: Client, chat_id: str, settings: dict):
contact = common_get_contact(client=client, uid_client=chat_id)
if not contact:
data = common_get_data(client=client, key='start'.lower())
if data:
for dt in data:
if dt.img:
url = f"https://cdn.telefon-ip.ru/{dt.img}?thumb=600"
maxbot_send_img_message(chat_id=chat_id, max_token=settings['token'], img=url, message=dt.descr)
else:
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.descr)
if dt.url:
maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.url)
maxbot_get_phone(chat_id=chat_id, max_token=settings['token'], text='Подтвердите согласие')
return False
if not contact.field1:
return False
return contact
def check_registration(client: Client, chat_id: str, message: dict, settings: dict):
contact = get_registration_phone(client=client, chat_id=chat_id, settings=settings)
if not contact:
maxbot_send_content(client=client, chat_id=chat_id, max_token=settings['token'], message='start')
return False
if contact.field1 is None or contact.field1 == '':
if 'day_birthday' in json.dumps(message):
dt = json.loads(str(message['callback']['payload']))
common_set_contact(client=client, uid_client=chat_id, date=dt['data'])
maxbot_send_month_button(client=client, chat_id=chat_id, max_token=settings['token'], message='👇 Месяц')
return False
else:
dt = maxbot_send_content(client=client, chat_id=chat_id, max_token=settings['token'],
message='system_reg_date')
if not dt:
maxbot_send_day_button(client=client, chat_id=chat_id, max_token=settings['token'],
message='👇 Для регистрации отправьте дату рождения')
else:
maxbot_send_day_button(client=client, chat_id=chat_id, max_token=settings['token'], message='👇')
return False
if contact.field2 is None or contact.field2 == '':
if 'month_birthday' in json.dumps(message):
dt = json.loads(str(message['callback']['payload']))
contact = common_set_contact(client=client, uid_client=chat_id, month=dt['data'])
maxbot_send_content(client=client, chat_id=chat_id, max_token=settings['token'],
message='system_registration')
return contact
else:
dt = maxbot_send_content(client=client, chat_id=chat_id, max_token=settings['token'],
message='system_reg_month')
if not dt:
maxbot_send_month_button(client=client, chat_id=chat_id, max_token=settings['token'],
message='👇 Выберите месяц')
else:
maxbot_send_month_button(client=client, chat_id=chat_id, max_token=settings['token'], message='👇')
return False
return contact

@ -1,13 +1,13 @@
# функция поиска контента по ключевому слову # функция поиска контента по ключевому слову
import json
from max_bot.models import Client, ProductCategory, Product, Contact, Integration, Partner
from max_bot.models import Client, ProductCategory, Product, Contact
def common_get_data(client: Client, key: str) -> Product: def common_get_data(client: Client, key: str) -> Product:
product_category = ProductCategory.objects.filter(client=client, status=1, title=key, src=1).first() product_category = ProductCategory.objects.filter(client=client, status=1, title=key, src=1).first()
if product_category: if product_category:
data = Product.objects.filter(up=product_category.pk, status=1) data = Product.objects.filter(up=product_category.pk, status=1).order_by('id')
else: else:
data = None data = None
return data return data
@ -34,7 +34,37 @@ def common_get_group_contact(client: Client, name: str) -> Contact:
return group return group
def common_get_contact(client: Client, uid_client: str) -> Contact: def common_get_contact(client: Client, uid_client: str = None, phone: str = None) -> Contact:
up = common_get_group_contact(client=client, name='max-bot').pk up = common_get_group_contact(client=client, name='max-bot').pk
if phone:
contact = Contact.objects.filter(client=client, status=1, phone=phone, up=up).first()
else:
contact = Contact.objects.filter(client=client, status=1, maxx=uid_client, up=up).first() contact = Contact.objects.filter(client=client, status=1, maxx=uid_client, up=up).first()
return contact return contact
def common_set_contact(client: Client, uid_client: str, name: str = None, phone: str = None,
date: str = None, month: str = None) -> Contact:
up = common_get_group_contact(client=client, name='max-bot').pk
contact = Contact.objects.filter(client=client, maxx=uid_client, up=up).first()
if contact:
if contact.status != 1:
contact.status = 1
if date:
contact.field1 = date
if month:
contact.field2 = month
contact.field3 = json.dumps({"month": month, "date": contact.field1})
contact.save()
else:
contact = Contact.objects.create(client=client, maxx=uid_client, up=up, name=name, phone=phone, group=0)
return contact
def common_get_integration(client: Client) -> Integration:
partner = Partner.objects.filter(status=1, slug__in=['iiko', 'rkeeper'])
integration = Integration.objects.filter(client=client, status=1, partner__in=partner).first()
return integration

@ -38,7 +38,8 @@ def iiko_organizations(api_key, organization_id):
return data return data
def customer_create(api_key, phone, name, organization_id): def customer_create(api_key, phone, name, organization_id, birthday: str):
# birthday string <yyyy-MM-dd HH:mm:ss.fff>
url = "https://api-ru.iiko.services/api/1/loyalty/iiko/customer/create_or_update" url = "https://api-ru.iiko.services/api/1/loyalty/iiko/customer/create_or_update"
payload = json.dumps({ payload = json.dumps({
@ -46,13 +47,15 @@ def customer_create(api_key, phone, name, organization_id):
"phone": phone, "phone": phone,
"name": name, "name": name,
"sex": 0, "sex": 0,
"userData": "telegram bot" "birthday": birthday,
"userData": "max bot"
}) })
headers = { headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + access_token(api_key) 'Authorization': 'Bearer ' + access_token(api_key)
} }
response = requests.request("POST", url, headers=headers, data=payload) response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
if response.status_code == 200: if response.status_code == 200:
data = response.json() data = response.json()
else: else:

@ -24,3 +24,25 @@ def rkiper_customer_info(rkiper_server, rkiper_token, phone):
response = requests.request("POST", url, headers=headers, data=payload).json() response = requests.request("POST", url, headers=headers, data=payload).json()
return response return response
def rkiper_customer_create(rkiper_server, rkiper_token, phone, name, birthday):
url = f"http://{rkiper_server}/cardimp"
phone = normalize_mobile_phone(phone)
payload = json.dumps([{
"card_number": f"{phone}",
"expiry_date": "2040-12-31",
"holder": f"{name}",
"phone1": f"{phone}",
"phone2": f"{phone}",
"email": f"{phone}",
"birthday": f"{birthday}",
"department": "max bot",
}])
headers = {
'Content-Type': 'application/json',
'X-API-Key': rkiper_token
}
response = requests.request("POST", url, headers=headers, data=payload)
return response.json()

@ -116,6 +116,11 @@ USE_TZ = True
STATIC_URL = 'static/' STATIC_URL = 'static/'
# Дополнительные директории для поиска статических файлов
STATICFILES_DIRS = [
BASE_DIR / 'static', # основная папка со статикой
]
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field

Loading…
Cancel
Save