From dd4b0b48e2698b340e39e34f62f86c88da396091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D0=BC=D0=B8=D1=80=20=D0=97?= =?UTF-8?q?=D0=B0=D1=85=D0=B0=D1=80=D0=BE=D0=B2?= Date: Sun, 26 Nov 2023 06:32:56 +0700 Subject: [PATCH 01/16] Async Django. Websockets. Uvicorn. Asgi. --- azu_bot_django/menu/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/azu_bot_django/menu/views.py b/azu_bot_django/menu/views.py index 47b96e5..ad7299e 100644 --- a/azu_bot_django/menu/views.py +++ b/azu_bot_django/menu/views.py @@ -1,4 +1,3 @@ -from asgiref.sync import sync_to_async from rest_framework import viewsets from rest_framework.response import Response @@ -23,7 +22,6 @@ def get_serializer_class(self): def perform_create(self, serializer): return serializer.save() - @sync_to_async def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) From c321b8963441a448e22102b4dec76437e2198316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=81=D1=82=D0=B0=D0=BD=D1=82=D0=B8?= =?UTF-8?q?=D0=BD=20=D0=93=D0=B0=D1=88=D0=B5=D0=B2?= Date: Sun, 26 Nov 2023 15:51:00 +0500 Subject: [PATCH 02/16] Pre-reservation free-tables display --- .../filters/is_correct_person_amount.py | 25 +++++- azu_bot_aiogram/handlers/api.py | 14 +++- azu_bot_aiogram/handlers/basic.py | 37 ++++++--- azu_bot_aiogram/handlers/get_free_places.py | 22 ++++++ azu_bot_aiogram/keyboards/reply_keyboards.py | 7 +- azu_bot_aiogram/main.py | 20 ++--- .../admin_users/migrations/0001_initial.py | 47 +++++++++++ .../cafe/migrations/0001_initial.py | 32 ++++++++ azu_bot_django/cafe/views.py | 34 ++++++++ azu_bot_django/db.sqlite3 | Bin 0 -> 270336 bytes .../menu/migrations/0001_initial.py | 65 ++++++++++++++++ .../reservation/migrations/0001_initial.py | 73 ++++++++++++++++++ .../tables/migrations/0001_initial.py | 43 +++++++++++ azu_bot_django/tables/models.py | 2 +- 14 files changed, 390 insertions(+), 31 deletions(-) create mode 100644 azu_bot_aiogram/handlers/get_free_places.py create mode 100644 azu_bot_django/admin_users/migrations/0001_initial.py create mode 100644 azu_bot_django/cafe/migrations/0001_initial.py create mode 100644 azu_bot_django/db.sqlite3 create mode 100644 azu_bot_django/menu/migrations/0001_initial.py create mode 100644 azu_bot_django/reservation/migrations/0001_initial.py create mode 100644 azu_bot_django/tables/migrations/0001_initial.py diff --git a/azu_bot_aiogram/filters/is_correct_person_amount.py b/azu_bot_aiogram/filters/is_correct_person_amount.py index 40ecb9c..4a7394c 100644 --- a/azu_bot_aiogram/filters/is_correct_person_amount.py +++ b/azu_bot_aiogram/filters/is_correct_person_amount.py @@ -1,13 +1,16 @@ import re +from aiogram import Bot from aiogram.filters import BaseFilter +from aiogram.fsm.context import FSMContext from aiogram.types import Message +from handlers.api import get_cafe, post_quantity class IsPersonAmount(BaseFilter): async def __call__(self, message: Message) -> bool: """Проверка корректности ввода количества человек.""" - pattern = r'[1-4]$' + pattern = r'^(0?[1-9]|[12][0-9]|3[01])$' if re.match(pattern, message.text): return {'amount': message.text} else: @@ -15,10 +18,24 @@ async def __call__(self, message: Message) -> bool: class TooManyPersons(BaseFilter): - async def __call__(self, message: Message) -> bool: + async def __call__( + self, message: Message, bot: Bot, state: FSMContext + ) -> bool: """Проверка числа клиентов для брони столов, если клиентов много.""" - max_table_size = 4 - if message.text.isdigit() and int(message.text) > max_table_size: + cafes = await get_cafe(bot) + context_data = await state.get_data() + address_cafe = context_data.get('address') + for cafe in cafes: + if cafe['address'] == address_cafe: + break + data_dict = {} + data_dict['date'] = '-'.join( + context_data.get('date').split('.')[::-1] + ) + data_dict['quantity'] = 0 + check_current_cafe = await post_quantity(cafe['id'], data=data_dict) + free_places = check_current_cafe['quantity'] + if message.text.isdigit() and int(message.text) > int(free_places): return {'amount': message.text} else: return False diff --git a/azu_bot_aiogram/handlers/api.py b/azu_bot_aiogram/handlers/api.py index e6282ab..727d524 100644 --- a/azu_bot_aiogram/handlers/api.py +++ b/azu_bot_aiogram/handlers/api.py @@ -25,10 +25,18 @@ async def get_cafe(bot=None): return response +async def post_quantity(cafe, data): + mysession = await get_session() + async with mysession as session: + async with session.post( + f'http://127.0.0.1:8000/cafes/{cafe}/quantity/', json=data + ) as response: + return await response.json() + + async def post_reservation(cafe_id, data): - async with aiohttp.ClientSession( - headers={"Authorization": f'Token {django_token}'} - ) as session: + mysession = await get_session() + async with mysession as session: async with session.post( f'http://127.0.0.1:8000/cafes/{cafe_id}/reservations/', json=data ) as response: diff --git a/azu_bot_aiogram/handlers/basic.py b/azu_bot_aiogram/handlers/basic.py index ac52472..4f796bd 100644 --- a/azu_bot_aiogram/handlers/basic.py +++ b/azu_bot_aiogram/handlers/basic.py @@ -1,8 +1,10 @@ from aiogram import Bot from aiogram.fsm.context import FSMContext from aiogram.types import Message -from handlers.api import get_cafe +from handlers.api import get_cafe, post_quantity +from handlers.get_free_places import get_free_places from handlers.media_group import get_media_group +from handlers.sets_for_order import make_sets from keyboards.reply_keyboards import (back_kbd, cafe_select_kbd, check_order_kbd, choose_another_cafe_kbd, @@ -13,7 +15,6 @@ move_tables_or_change_cafe_kbd, people_per_table_kbd, reserve_or_back_kbd, table_or_back_kbd) -from handlers.sets_for_order import make_sets from utils.states import StepsForm @@ -148,12 +149,27 @@ async def choose_date(message: Message, bot: Bot, state: FSMContext): async def person_per_table(message: Message, bot: Bot, state: FSMContext): """Выбор количества персон для брони стола.""" - await message.answer('Укажите количество персон', - reply_markup=people_per_table_kbd()) if message.text.startswith('Назад'): pass else: await state.update_data(date=message.text) + cafes = await get_cafe(bot) + context_data = await state.get_data() + address_cafe = context_data.get('address') + for cafe in cafes: + if cafe['address'] == address_cafe: + break + data_dict = {} + data_dict['date'] = '-'.join(context_data.get('date').split('.')[::-1]) + data_dict['quantity'] = 0 + check_current_cafe = await post_quantity(cafe['id'], data=data_dict) + free_places = check_current_cafe['quantity'] + await message.answer( + 'Количество свободных мест в этом кафе ' + f'на выбранную дату: {free_places}.\n' + 'Укажите количество гостей.', + reply_markup=people_per_table_kbd() + ) await state.set_state(StepsForm.PERSON_AMOUNT) @@ -193,7 +209,7 @@ async def get_phone(message: Message, bot: Bot, state: FSMContext): async def choose_set(message: Message, bot: Bot, state: FSMContext): - """Интерактивное меню для оформления заказа с количеством порций.""" + """Меню для оформления заказа с количеством порций.""" if message.text is not None and not message.text.startswith('Назад'): await state.update_data(phone=message.text) else: @@ -273,8 +289,8 @@ async def no_free_table(message: Message, bot: Bot, state: FSMContext): """Диалог при отсутствии свободных столов.""" await state.update_data(person_amount=message.text) await message.answer('К сожалению нужного Вам столика нет в наличии.\n' - 'Можем предложить Вам соединить несколько столов ' - ' или забронировать стол в другом кафе нашей сети.', + 'Можем предложить Вам забронировать стол ' + 'в другом кафе нашей сети.', reply_markup=move_tables_or_change_cafe_kbd()) await state.set_state(StepsForm.NO_FREE_TABLE) @@ -282,9 +298,12 @@ async def no_free_table(message: Message, bot: Bot, state: FSMContext): async def choose_another_cafe(message: Message, bot: Bot, state: FSMContext): """Выбрать кафе со свободными столами запрошенной вместимости.""" cafes = await get_cafe(bot) + context_data = await state.get_data() + cafe_list = await get_free_places(cafes, context_data) await message.answer( - '***Тут список кафе со свободными столами запрошенной вместимости***', - reply_markup=choose_another_cafe_kbd(cafes)) + 'На кнопках ниже представлены адреса кафе с подходящим количеством ' + 'свободных столов. \n Пожалуйста выберите адрес.', + reply_markup=choose_another_cafe_kbd(cafe_list)) await state.set_state(StepsForm.CHOOSE_ANOTHER_CAFE) diff --git a/azu_bot_aiogram/handlers/get_free_places.py b/azu_bot_aiogram/handlers/get_free_places.py new file mode 100644 index 0000000..77e93b7 --- /dev/null +++ b/azu_bot_aiogram/handlers/get_free_places.py @@ -0,0 +1,22 @@ +from handlers.api import post_quantity + + +async def get_free_places(cafes, context_data): + avaliable_cafes = [] + person_amount = int(context_data.get('person_amount')) + for cafe in cafes: + if cafe['address'] == context_data.get('address'): + del cafe + else: + data_dict = {} + data_dict['date'] = '-'.join( + context_data.get('date').split('.')[::-1] + ) + data_dict['quantity'] = 0 + check_current_cafe = await post_quantity(cafe['id'], data=data_dict) + free_places = int(check_current_cafe['quantity']) + if person_amount <= free_places: + avaliable_cafes.append(cafe['address']) + else: + del cafe + return avaliable_cafes diff --git a/azu_bot_aiogram/keyboards/reply_keyboards.py b/azu_bot_aiogram/keyboards/reply_keyboards.py index e4024e5..ac03b7c 100644 --- a/azu_bot_aiogram/keyboards/reply_keyboards.py +++ b/azu_bot_aiogram/keyboards/reply_keyboards.py @@ -93,7 +93,6 @@ def move_tables_or_change_cafe_kbd(): """Сдвигать столы или сменить кафе.""" keyboard_builder = ReplyKeyboardBuilder() keyboard_builder.button(text='Выбрать другое кафе') - keyboard_builder.button(text='Сдвигать столы') keyboard_builder.button(text='Назад ' + emojize(':calendar:')) keyboard_builder.button(text='Отмена') keyboard_builder.adjust(2) @@ -103,11 +102,11 @@ def move_tables_or_change_cafe_kbd(): ) -def choose_another_cafe_kbd(cafes): +def choose_another_cafe_kbd(cafe_list): """Выбрать другое кафе, если в текущем нет столов.""" keyboard_builder = ReplyKeyboardBuilder() - for cafe in cafes: - keyboard_builder.button(text=cafe['address']) + for cafe_address in cafe_list: + keyboard_builder.button(text=cafe_address) keyboard_builder.adjust(2) keyboard_builder.button(text='Назад ' + emojize(':reverse_button:')) keyboard_builder.button(text='Отмена') diff --git a/azu_bot_aiogram/main.py b/azu_bot_aiogram/main.py index 8f18d09..a3f2c64 100644 --- a/azu_bot_aiogram/main.py +++ b/azu_bot_aiogram/main.py @@ -146,16 +146,6 @@ async def start(): StepsForm.CAFE_INFO, StepsForm.CAFE_ADDRESS, StepsForm.MENU_WATCH ) ) - dp.message.register( - name_for_reserving, - IsPersonAmount(), - or_f(StepsForm.PERSON_AMOUNT, StepsForm.CHOOSE_ANOTHER_CAFE) - ) - dp.message.register( - no_free_table, - TooManyPersons(), - StepsForm.PERSON_AMOUNT - ) dp.message.register( choose_another_cafe, F.text == 'Выбрать другое кафе', @@ -221,6 +211,16 @@ async def start(): IsCorrectDate(), StepsForm.CHOOSE_DATE ) + dp.message.register( + no_free_table, + TooManyPersons(), + StepsForm.PERSON_AMOUNT + ) + dp.message.register( + name_for_reserving, + IsPersonAmount(), + or_f(StepsForm.PERSON_AMOUNT, StepsForm.CHOOSE_ANOTHER_CAFE) + ) dp.message.register( wrong_input ) diff --git a/azu_bot_django/admin_users/migrations/0001_initial.py b/azu_bot_django/admin_users/migrations/0001_initial.py new file mode 100644 index 0000000..d3e2d5c --- /dev/null +++ b/azu_bot_django/admin_users/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.7 on 2023-11-26 07:18 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('cafe', '0001_initial'), + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='CustomUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('cafe', models.ForeignKey(blank=True, help_text='Для сотрудников кафе обязателен выбор к привязанному кафе', null=True, on_delete=django.db.models.deletion.CASCADE, to='cafe.cafe', verbose_name='В кафе')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'Пользователь', + 'verbose_name_plural': 'Пользователи', + 'ordering': ('username',), + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/azu_bot_django/cafe/migrations/0001_initial.py b/azu_bot_django/cafe/migrations/0001_initial.py new file mode 100644 index 0000000..21807f2 --- /dev/null +++ b/azu_bot_django/cafe/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.7 on 2023-11-26 07:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Cafe', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=256, unique=True, verbose_name='Название кафе')), + ('address', models.CharField(max_length=256, unique=True, verbose_name='Адрес кафе')), + ('number', models.CharField(max_length=256, verbose_name='Номер кафе')), + ], + options={ + 'verbose_name': 'Кафе', + 'verbose_name_plural': 'Кафе', + 'ordering': ('name',), + }, + ), + migrations.AddConstraint( + model_name='cafe', + constraint=models.UniqueConstraint(fields=('name', 'address'), name='unique_name_address'), + ), + ] diff --git a/azu_bot_django/cafe/views.py b/azu_bot_django/cafe/views.py index 240b61d..e1dccaf 100644 --- a/azu_bot_django/cafe/views.py +++ b/azu_bot_django/cafe/views.py @@ -1,9 +1,43 @@ from rest_framework import viewsets +from rest_framework.decorators import action +from django.http import JsonResponse +from datetime import date from cafe.models import Cafe +from tables.models import Table +from reservation.models import Reservation from cafe.serializers import CafeSerializer class CafeViewSet(viewsets.ReadOnlyModelViewSet): queryset = Cafe.objects.all() serializer_class = CafeSerializer + + @action(methods=['POST'], detail=True) + def quantity(self, request, pk): + data = request.data +# if 'date' not in data.keys(): + if 'quantity' not in data.keys(): + return JsonResponse({ + 'date': [ + 'Обязательное поле.' + ] + }) + cafe = Cafe.objects.get(id=pk) + res_date = date.fromisoformat(data['date']) + available_tables = Table.objects.filter( + cafe=cafe).exclude( + id__in=Reservation.table.through.objects.filter( + reservation__cafe__id=cafe.id, + reservation__date=res_date, + reservation__status='booked' + ).values('table') + ) + quantity = 0 + for table in available_tables: + quantity += table.quantity + return JsonResponse({ + 'cafe': cafe.address, + 'date': res_date, + 'quantity': quantity + }) diff --git a/azu_bot_django/db.sqlite3 b/azu_bot_django/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..247caee18fc50d87c27ef36cebdbc776019762c1 GIT binary patch literal 270336 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 Date: Sun, 26 Nov 2023 16:06:00 +0500 Subject: [PATCH 03/16] Pre-reservation free-tables check, improved navigation --- azu_bot_aiogram/handlers/basic.py | 2 -- azu_bot_django/cafe/views.py | 1 - 2 files changed, 3 deletions(-) diff --git a/azu_bot_aiogram/handlers/basic.py b/azu_bot_aiogram/handlers/basic.py index 4f796bd..47df354 100644 --- a/azu_bot_aiogram/handlers/basic.py +++ b/azu_bot_aiogram/handlers/basic.py @@ -179,8 +179,6 @@ async def name_for_reserving(message: Message, bot: Bot, state: FSMContext): reply_markup=enter_name_kbd()) if message.text.startswith('Назад'): pass - elif message.text.startswith('Сдвигать'): - pass elif message.text.startswith('ул.'): await state.update_data(address=message.text) else: diff --git a/azu_bot_django/cafe/views.py b/azu_bot_django/cafe/views.py index e1dccaf..a81840e 100644 --- a/azu_bot_django/cafe/views.py +++ b/azu_bot_django/cafe/views.py @@ -16,7 +16,6 @@ class CafeViewSet(viewsets.ReadOnlyModelViewSet): @action(methods=['POST'], detail=True) def quantity(self, request, pk): data = request.data -# if 'date' not in data.keys(): if 'quantity' not in data.keys(): return JsonResponse({ 'date': [ From 45d123177c98a72017c6b1b4eb871478f9b79f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D0=BC=D0=B8=D1=80=20=D0=97?= =?UTF-8?q?=D0=B0=D1=85=D0=B0=D1=80=D0=BE=D0=B2?= Date: Sun, 26 Nov 2023 18:31:05 +0700 Subject: [PATCH 04/16] Async Django. Websockets. Uvicorn. Asgi. Static --- azu_bot_django/azu_bot_django/settings.py | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/azu_bot_django/azu_bot_django/settings.py b/azu_bot_django/azu_bot_django/settings.py index 3fec115..d5e8d74 100644 --- a/azu_bot_django/azu_bot_django/settings.py +++ b/azu_bot_django/azu_bot_django/settings.py @@ -46,6 +46,7 @@ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', diff --git a/requirements.txt b/requirements.txt index 95e8056..7a11d9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,3 +47,4 @@ untokenize==0.1.1 yarl==1.9.2 channels==4.0.0 uvicorn==0.24.0.post1 +whitenoise-6.6.0 From 8c88d5b7f0ebebdeb673b3342d44ab2498191bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D0=BC=D0=B8=D1=80=20=D0=97?= =?UTF-8?q?=D0=B0=D1=85=D0=B0=D1=80=D0=BE=D0=B2?= Date: Sun, 26 Nov 2023 18:41:40 +0700 Subject: [PATCH 05/16] Async Django. Websockets. Uvicorn. Asgi. Static --- README.md | 1 + azu_bot_django/azu_bot_django/settings.py | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index f2c2aa5..6abe570 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ - Перейти в папку с базой данных и запустить её: ``` ~ cd azu_bot_django +~ python manage.py collectstatic ~ uvicorn azu_bot_django.asgi:application ``` - Выйти из папки с базой данных, перейти в папку бота, запустить бота: diff --git a/azu_bot_django/azu_bot_django/settings.py b/azu_bot_django/azu_bot_django/settings.py index d5e8d74..0a91a10 100644 --- a/azu_bot_django/azu_bot_django/settings.py +++ b/azu_bot_django/azu_bot_django/settings.py @@ -122,6 +122,7 @@ STATIC_URL = '/static/' STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),) +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' From a40740f13dd78ed20b9b8c0cdf76f181c53e342d Mon Sep 17 00:00:00 2001 From: zakharovvladimir <111356019+zakharovvladimir@users.noreply.github.com> Date: Sun, 26 Nov 2023 19:23:15 +0700 Subject: [PATCH 06/16] Update requirements.txt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Исправление ошибки в requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7a11d9d..a4862b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,4 +47,4 @@ untokenize==0.1.1 yarl==1.9.2 channels==4.0.0 uvicorn==0.24.0.post1 -whitenoise-6.6.0 +whitenoise==6.6.0 From 7e03a0d92bd27831843f378667980e32c9200093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=81=D1=82=D0=B0=D0=BD=D1=82=D0=B8?= =?UTF-8?q?=D0=BD=20=D0=93=D0=B0=D1=88=D0=B5=D0=B2?= Date: Sun, 26 Nov 2023 19:03:28 +0500 Subject: [PATCH 07/16] Added menu for watch before reservation --- azu_bot_aiogram/handlers/basic.py | 8 +++--- azu_bot_aiogram/handlers/media_group.py | 34 ++++++++++++++++++++++++ azu_bot_django/db.sqlite3 | Bin 270336 -> 270336 bytes 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/azu_bot_aiogram/handlers/basic.py b/azu_bot_aiogram/handlers/basic.py index 47df354..8eaede3 100644 --- a/azu_bot_aiogram/handlers/basic.py +++ b/azu_bot_aiogram/handlers/basic.py @@ -3,7 +3,7 @@ from aiogram.types import Message from handlers.api import get_cafe, post_quantity from handlers.get_free_places import get_free_places -from handlers.media_group import get_media_group +from handlers.media_group import get_media_group, watch_media_group from handlers.sets_for_order import make_sets from keyboards.reply_keyboards import (back_kbd, cafe_select_kbd, check_order_kbd, @@ -109,8 +109,10 @@ async def get_contacts(message: Message, bot: Bot, state: FSMContext): async def cafe_menu(message: Message, bot: Bot, state: FSMContext): """Страничка меню выбранного кафе (до начала бронирования).""" - await message.answer('***Тут должны появляться сеты***', - reply_markup=table_or_back_kbd()) + await watch_media_group(message, bot) + await message.answer( + 'Чтобы заказать ифтар-сет, выберите "Забронировать стол".', + reply_markup=table_or_back_kbd()) await state.set_state(StepsForm.MENU_WATCH) diff --git a/azu_bot_aiogram/handlers/media_group.py b/azu_bot_aiogram/handlers/media_group.py index c5920ac..ca3c1ce 100644 --- a/azu_bot_aiogram/handlers/media_group.py +++ b/azu_bot_aiogram/handlers/media_group.py @@ -3,6 +3,7 @@ async def get_media_group(message: Message, bot: Bot): + """Сеты для оформления заказа.""" set_1 = InputMediaPhoto( type='photo', media=FSInputFile(r'images/set1.jpg'), caption=('Выберите сеты для ифтара и введите в формате ' @@ -36,3 +37,36 @@ async def get_media_group(message: Message, bot: Bot): ) media = [set_1, set_2, set_3, set_4, set_5, set_6, set_7, set_8, set_9] await message.answer_media_group(media) + + +async def watch_media_group(message: Message, bot: Bot): + """Сеты для просмотра перед бронированием стола.""" + set_1 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set1.jpg'), + ) + set_2 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set2.jpg') + ) + set_3 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set3.jpg') + ) + set_4 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set4.jpg') + ) + set_5 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set5.jpg') + ) + set_6 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set6.jpg') + ) + set_7 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set7.jpg') + ) + set_8 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set8.jpg') + ) + set_9 = InputMediaPhoto( + type='photo', media=FSInputFile(r'images/set9.jpg') + ) + media = [set_1, set_2, set_3, set_4, set_5, set_6, set_7, set_8, set_9] + await message.answer_media_group(media) diff --git a/azu_bot_django/db.sqlite3 b/azu_bot_django/db.sqlite3 index 247caee18fc50d87c27ef36cebdbc776019762c1..037954661c9c20be5f82870b9596c9c6776ed714 100644 GIT binary patch delta 412 zcmXw!ze@sP9Eb0{_wLmT>!WGWPfCJ@A}`vS0vlS}TKx|UX>$=8qNyz6_Wpx#xT#Ru zBoHkJ1&@AdWQJ$-gEFx1smTI!J)C6ORng-|5M6u((odowD(+E6fv^j>T?%tV!YD`i0x?#v)F$c?7BShL$|Jg} zzOWgUM+O8c659Ac#q`)L*X0!1FU*Gn9$coVn&WDrLCb4oy6F>|YBq9w%*ie~+iX0+ mLXn6|Xf9QiG%C^ztI6jHp`bS2@rqhE=juP1EVT}wnEeCwf`A(U delta 308 zcmXYrJxjw-6o&83Neb5LdzC7F6)l2;LgVV>;NVYib#Sj_%~Eg>Cyhn2`wyhUUDGK} ziVi}f6s@I28|ke`#Sf@A()Bz%?{i9lCGT77s6WCkrVnKStZ!tLDmQ1t&$b z8QK`}s?+6Vdxx6`u6^j*T)MV#^q3|nhF|odDic8izFMTLJ~|9HIEPco@9)8PY&spc zP-FN)4=;E^O@k1!wn=LuW1^F6d}`LwEBCN0;+%XnFtS>U7{2k2S2Pf5?Gk=eq*V=? z!?H$A4Q?^|?+xv4(U_8^;?I!nW6G`TOq>Wk#OUkH17v(nGa_xGo;;?|mO{}L495I= XmsT`rG5p{IZ&CzwmjKxs(A?4=E>LU9 From e6ab21cf2bb497a9f7c7d20d448544267808fa18 Mon Sep 17 00:00:00 2001 From: zakharovvladimir <111356019+zakharovvladimir@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:45:40 +0700 Subject: [PATCH 08/16] Update README.md PORT to ENV --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cfe2b06..7b84d1b 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ - ADMIN_ID = - PROVIDER_TOKEN = <Токен платежной системы> - DJANGO_TOKEN = <Токен для бота, который был создан в разделе базы данных> +- PORT = <Порт подключения Django> ### Запуск Для полного запуска телеграмм-бота с базой данных нужно: From c536c28ec5d12ce546a583c647c929f779377b60 Mon Sep 17 00:00:00 2001 From: Certelen Date: Mon, 27 Nov 2023 17:50:12 +0500 Subject: [PATCH 09/16] add docker --- azu_bot_aiogram/Dockerfile | 9 ++++ azu_bot_aiogram/handlers/api.py | 8 ++-- azu_bot_aiogram/requirements.txt | 49 ++++++++++++++++++++++ azu_bot_aiogram/settings.py | 1 + azu_bot_django/Dockerfile | 12 ++++++ azu_bot_django/azu_bot_django/settings.py | 14 +++++-- azu_bot_django/azu_bot_django/urls.py | 5 --- azu_bot_django/requirements.txt | 50 +++++++++++++++++++++++ azu_bot_django/reservation/views.py | 11 +---- azu_bot_django/templates/menu.html | 43 ------------------- infra/docker-compose.yml | 32 +++++++++++++++ infra/nginx/default.conf | 37 +++++++++++++++++ 12 files changed, 205 insertions(+), 66 deletions(-) create mode 100644 azu_bot_aiogram/Dockerfile create mode 100644 azu_bot_aiogram/requirements.txt create mode 100644 azu_bot_django/Dockerfile create mode 100644 azu_bot_django/requirements.txt delete mode 100644 azu_bot_django/templates/menu.html create mode 100644 infra/docker-compose.yml create mode 100644 infra/nginx/default.conf diff --git a/azu_bot_aiogram/Dockerfile b/azu_bot_aiogram/Dockerfile new file mode 100644 index 0000000..af232f6 --- /dev/null +++ b/azu_bot_aiogram/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY ./ ./ + +RUN pip3 install -r requirements.txt --no-cache-dir + +CMD ["python", "main.py"] \ No newline at end of file diff --git a/azu_bot_aiogram/handlers/api.py b/azu_bot_aiogram/handlers/api.py index 727d524..712d5bd 100644 --- a/azu_bot_aiogram/handlers/api.py +++ b/azu_bot_aiogram/handlers/api.py @@ -1,5 +1,5 @@ import aiohttp -from settings import django_token, settings +from settings import django_token, settings, port async def get_session(): @@ -13,7 +13,7 @@ async def get_cafe(bot=None): mysession = await get_session() async with mysession as session: async with session.get( - 'http://127.0.0.1:8000/cafes/' + f'http://backend:{port}/cafes/' ) as response: response = await response.json() if not isinstance(response, list): @@ -29,7 +29,7 @@ async def post_quantity(cafe, data): mysession = await get_session() async with mysession as session: async with session.post( - f'http://127.0.0.1:8000/cafes/{cafe}/quantity/', json=data + f'http://backend:{port}/cafes/{cafe}/quantity/', json=data ) as response: return await response.json() @@ -38,6 +38,6 @@ async def post_reservation(cafe_id, data): mysession = await get_session() async with mysession as session: async with session.post( - f'http://127.0.0.1:8000/cafes/{cafe_id}/reservations/', json=data + f'http://backend:{port}/cafes/{cafe_id}/reservations/', json=data ) as response: return await response.json() diff --git a/azu_bot_aiogram/requirements.txt b/azu_bot_aiogram/requirements.txt new file mode 100644 index 0000000..95e8056 --- /dev/null +++ b/azu_bot_aiogram/requirements.txt @@ -0,0 +1,49 @@ +aiofiles==23.1.0 +aiogram==3.1.1 +aiohttp==3.8.6 +aiosignal==1.3.1 +annotated-types==0.6.0 +anyio==4.0.0 +APScheduler==3.10.4 +asgiref==3.7.2 +async-timeout==4.0.3 +attrs==23.1.0 +autoflake==2.2.1 +autopep8==2.0.4 +certifi==2023.7.22 +emoji==2.8.0 +environs==9.5.0 +charset-normalizer==3.3.2 +Django==4.2.7 +django-ckeditor==6.7.0 +django-js-asset==2.1.0 +djangorestframework==3.14.0 +docformatter==1.7.5 +frozenlist==1.4.0 +h11==0.14.0 +httpcore==0.18.0 +httpx==0.25.0 +idna==3.4 +magic-filter==1.0.12 +marshmallow==3.20.1 +multidict==6.0.4 +packaging==23.2 +pydantic==2.3.0 +pydantic_core==2.6.3 +python-dotenv==1.0.0 +pytz==2023.3.post1 +six==1.16.0 +Pillow==10.1.0 +pycodestyle==2.11.1 +pyflakes==3.1.0 +sniffio==1.3.0 +sqlparse==0.4.4 +typing_extensions==4.7.1 +tzdata==2023.3 +tzlocal==5.2 +requests==2.31.0 +unify==0.5 +untokenize==0.1.1 +yarl==1.9.2 +channels==4.0.0 +uvicorn==0.24.0.post1 diff --git a/azu_bot_aiogram/settings.py b/azu_bot_aiogram/settings.py index b63a79e..6c4695b 100644 --- a/azu_bot_aiogram/settings.py +++ b/azu_bot_aiogram/settings.py @@ -32,5 +32,6 @@ def get_settings(path: str): ) +port = os.getenv('PORT') django_token = os.getenv('DJANGO_TOKEN') settings = get_settings('.env') diff --git a/azu_bot_django/Dockerfile b/azu_bot_django/Dockerfile new file mode 100644 index 0000000..91a3b26 --- /dev/null +++ b/azu_bot_django/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY ./ ./ + +RUN pip3 install -r requirements.txt --no-cache-dir +RUN python manage.py makemigrations cafe tables menu admin_users reservation +RUN python manage.py migrate +RUN python manage.py collectstatic --no-input + +CMD ["uvicorn", "--host", "0.0.0.0", "--port", "8080", "azu_bot_django.asgi:application"] \ No newline at end of file diff --git a/azu_bot_django/azu_bot_django/settings.py b/azu_bot_django/azu_bot_django/settings.py index 3fec115..faaf576 100644 --- a/azu_bot_django/azu_bot_django/settings.py +++ b/azu_bot_django/azu_bot_django/settings.py @@ -9,11 +9,12 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -SECRET_KEY = 'django-insecure-$9h!ujjvgrgilsbc&%idh%)tb86u+^qkr#2#p!o^ej48n6)=m7' +SECRET_KEY = os.getenv( + 'SECRET_KEY', default='django-insecure-$9h!ujjvgrgilsbc&%idh%)tb86u+^qkr#2#p!o^ej48n6)=m7') DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['*'] MAX_CHAR_LENGTH = 256 MAX_DIGIT_LENGTH = 15 @@ -55,6 +56,8 @@ ROOT_URLCONF = 'azu_bot_django.urls' +CSRF_TRUSTED_ORIGINS = ["http://127.0.0.1"] + TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates') TEMPLATES = [ { @@ -88,7 +91,7 @@ # 'NAME': os.getenv('DATABASE_NAME', default='postgres'), # 'USER': os.getenv('DATABASE_USERNAME', default='postgres'), # 'PASSWORD': os.getenv('DATABASE_PASSWORD', default='postgres'), -# 'HOST': os.getenv('DATABASE_HOST', default='localhost'), +# 'HOST': os.getenv('DATABASE_HOST', default='db'), # 'PORT': os.getenv('DATABASE_PORT', default='5432'), # } # } @@ -120,7 +123,10 @@ STATIC_URL = '/static/' -STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),) +STATIC_ROOT = os.path.join(BASE_DIR, 'static') + +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/azu_bot_django/azu_bot_django/urls.py b/azu_bot_django/azu_bot_django/urls.py index 84437da..b4c2fc3 100644 --- a/azu_bot_django/azu_bot_django/urls.py +++ b/azu_bot_django/azu_bot_django/urls.py @@ -16,9 +16,4 @@ TemplateView.as_view(template_name='redoc.html'), name='redoc' ), - path( - 'menulist/', - TemplateView.as_view(template_name='menu.html'), - name='menulist' - ), ] diff --git a/azu_bot_django/requirements.txt b/azu_bot_django/requirements.txt new file mode 100644 index 0000000..f020542 --- /dev/null +++ b/azu_bot_django/requirements.txt @@ -0,0 +1,50 @@ +aiofiles==23.1.0 +aiogram==3.1.1 +aiohttp==3.8.6 +aiosignal==1.3.1 +annotated-types==0.6.0 +anyio==4.0.0 +APScheduler==3.10.4 +asgiref==3.7.2 +async-timeout==4.0.3 +attrs==23.1.0 +autoflake==2.2.1 +autopep8==2.0.4 +certifi==2023.7.22 +emoji==2.8.0 +environs==9.5.0 +charset-normalizer==3.3.2 +Django==4.2.7 +django-ckeditor==6.7.0 +django-js-asset==2.1.0 +djangorestframework==3.14.0 +docformatter==1.7.5 +frozenlist==1.4.0 +h11==0.14.0 +httpcore==0.18.0 +httpx==0.25.0 +idna==3.4 +magic-filter==1.0.12 +marshmallow==3.20.1 +multidict==6.0.4 +packaging==23.2 +pydantic==2.3.0 +pydantic_core==2.6.3 +python-dotenv==1.0.0 +pytz==2023.3.post1 +six==1.16.0 +Pillow==10.1.0 +pycodestyle==2.11.1 +pyflakes==3.1.0 +sniffio==1.3.0 +sqlparse==0.4.4 +typing_extensions==4.7.1 +tzdata==2023.3 +tzlocal==5.2 +requests==2.31.0 +unify==0.5 +untokenize==0.1.1 +yarl==1.9.2 +channels==4.0.0 +uvicorn==0.24.0.post1 +psycopg2-binary==2.9.9 \ No newline at end of file diff --git a/azu_bot_django/reservation/views.py b/azu_bot_django/reservation/views.py index 1c8f8b9..8074c9e 100644 --- a/azu_bot_django/reservation/views.py +++ b/azu_bot_django/reservation/views.py @@ -1,9 +1,8 @@ -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404 from rest_framework import viewsets from rest_framework.response import Response from cafe.models import Cafe -from menu.models import Set from reservation.models import Reservation from reservation.serializers import (ReservationReadSerializer, ReservationWriteSerializer) @@ -39,11 +38,3 @@ def create(self, request, *args, **kwargs): def perform_update(self, serializer): cancell_reservation(serializer) - - -def cafe_menu(request): - sets = Set.objects.all() - context = { - 'sets': sets, - } - return render(request, 'menu.html', context) diff --git a/azu_bot_django/templates/menu.html b/azu_bot_django/templates/menu.html deleted file mode 100644 index 8dc483c..0000000 --- a/azu_bot_django/templates/menu.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - Меню - - - -
-

Меню

-

Комбо-сеты

- {% for set in sets %} -
-
-
- {% if set.image %} - - {% else %} - - {% endif %} -
-
-
-
{{ set.name }}
-

{{ set.description }}

-

Состав: - {% for dish in set.dishes.all %} - {% if not forloop.last %} - {{ dish.name }} - {% endif %} - {% endfor %} -

-

{{ set.price }} ₽

-
-
-
-
- {% endfor %} -
- - diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml new file mode 100644 index 0000000..4300711 --- /dev/null +++ b/infra/docker-compose.yml @@ -0,0 +1,32 @@ +version: '3.8' + +services: + backend: + build: ../azu_bot_django + restart: always + volumes: + - static_value:/app/static/ + - media_value:/app/media/ + + env_file: + - ../.env + frontend: + build: ../azu_bot_aiogram + env_file: + - ../.env + nginx: + image: nginx:1.21.3-alpine + restart: always + ports: + - "80:80" + volumes: + - ./nginx/default.conf:/etc/nginx/conf.d/default.conf + - static_value:/var/html/static/ + - media_value:/var/html/media/ + depends_on: + - backend + - frontend + +volumes: + static_value: + media_value: diff --git a/infra/nginx/default.conf b/infra/nginx/default.conf new file mode 100644 index 0000000..98b99e2 --- /dev/null +++ b/infra/nginx/default.conf @@ -0,0 +1,37 @@ +server { + listen 80; + server_tokens off; + server_name 127.0.0.1 62.84.124.68; + + location /media/ { + root /var/html; + autoindex on; + } + + location /static/admin/ { + root /var/html/; + autoindex on; + } + + location /static/rest_framework/ { + root /var/html/; + autoindex on; + } + + location /static/ { + root /var/html/; + autoindex on; + } + + location /favicon.ico { + log_not_found off; + } + + location /admin/ { + proxy_pass http://backend:8080/admin/; + } + + location / { + proxy_pass http://backend:8080; + } +} \ No newline at end of file From 4e2432989d6fac26b6e800a8fbdea8a62d546a12 Mon Sep 17 00:00:00 2001 From: Certelen Date: Mon, 27 Nov 2023 18:02:11 +0500 Subject: [PATCH 10/16] fix compose --- infra/docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 4300711..e9862d7 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: backend: - build: ../azu_bot_django + image: certelen/azu_bot_django:v1 restart: always volumes: - static_value:/app/static/ @@ -11,7 +11,7 @@ services: env_file: - ../.env frontend: - build: ../azu_bot_aiogram + build: certelen/azu_bot_aiogram:v1 env_file: - ../.env nginx: From e34bf1e11582f7f8d02f9d999023455c0536a30e Mon Sep 17 00:00:00 2001 From: Certelen Date: Mon, 27 Nov 2023 18:13:56 +0500 Subject: [PATCH 11/16] change port nginx --- infra/docker-compose.yml | 6 +++--- infra/nginx/default.conf | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index e9862d7..b58e92b 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: backend: - image: certelen/azu_bot_django:v1 + image: certelen/azu_cafe_django:v1 restart: always volumes: - static_value:/app/static/ @@ -11,14 +11,14 @@ services: env_file: - ../.env frontend: - build: certelen/azu_bot_aiogram:v1 + image: certelen/azu_cafe_aiogram:v1 env_file: - ../.env nginx: image: nginx:1.21.3-alpine restart: always ports: - - "80:80" + - "81:81" volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - static_value:/var/html/static/ diff --git a/infra/nginx/default.conf b/infra/nginx/default.conf index 98b99e2..484ca38 100644 --- a/infra/nginx/default.conf +++ b/infra/nginx/default.conf @@ -1,5 +1,5 @@ server { - listen 80; + listen 81; server_tokens off; server_name 127.0.0.1 62.84.124.68; From f96d19aaa6fc081fde7e2cca77e58fc6d9c06cfd Mon Sep 17 00:00:00 2001 From: Certelen Date: Mon, 27 Nov 2023 20:00:25 +0500 Subject: [PATCH 12/16] upd ip --- azu_bot_django/azu_bot_django/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azu_bot_django/azu_bot_django/settings.py b/azu_bot_django/azu_bot_django/settings.py index faaf576..01e0867 100644 --- a/azu_bot_django/azu_bot_django/settings.py +++ b/azu_bot_django/azu_bot_django/settings.py @@ -56,7 +56,7 @@ ROOT_URLCONF = 'azu_bot_django.urls' -CSRF_TRUSTED_ORIGINS = ["http://127.0.0.1"] +CSRF_TRUSTED_ORIGINS = ["http://62.84.124.68:81"] TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates') TEMPLATES = [ From 42c2de2cea74ba7faa5bc22f0d6f5c53a25af242 Mon Sep 17 00:00:00 2001 From: Certelen Date: Tue, 28 Nov 2023 05:22:08 +0500 Subject: [PATCH 13/16] new feathures --- .../filters/is_correct_person_amount.py | 2 +- azu_bot_aiogram/handlers/api.py | 38 +++---- azu_bot_aiogram/handlers/appsched.py | 53 ++++++++- azu_bot_aiogram/handlers/basic.py | 13 ++- azu_bot_aiogram/handlers/get_free_places.py | 5 +- azu_bot_aiogram/handlers/get_sunset.py | 3 +- azu_bot_aiogram/handlers/media_group.py | 17 ++- azu_bot_aiogram/handlers/pay.py | 9 +- azu_bot_aiogram/keyboards/reply_keyboards.py | 8 +- azu_bot_aiogram/main.py | 8 +- azu_bot_aiogram/settings.py | 6 - azu_bot_django/Dockerfile | 4 +- azu_bot_django/admin_users/admin.py | 2 +- .../admin_users/migrations/0001_initial.py | 47 -------- .../admin_users/migrations/__init__.py | 0 azu_bot_django/admin_users/models.py | 6 + azu_bot_django/azu_bot_django/settings.py | 105 ++++++++---------- azu_bot_django/cafe/management/__init__.py | 0 .../cafe/management/commands/__int__.py | 0 .../cafe/management/commands/_private.py | 0 .../management/commands/load_data_cafe.py | 22 ---- .../cafe/migrations/0001_initial.py | 32 ------ azu_bot_django/cafe/migrations/__init__.py | 0 azu_bot_django/cafe/views.py | 16 +++ azu_bot_django/db.sqlite3 | Bin 270336 -> 0 bytes azu_bot_django/menu/management/__init__.py | 0 .../menu/management/commands/__int__.py | 0 .../menu/management/commands/_private.py | 0 .../management/commands/load_data_menu.py | 41 ------- .../menu/migrations/0001_initial.py | 65 ----------- azu_bot_django/menu/migrations/__init__.py | 0 azu_bot_django/requirements.txt | 2 +- .../reservation/migrations/0001_initial.py | 73 ------------ .../reservation/migrations/__init__.py | 0 azu_bot_django/static/data/dishes.csv | 2 +- .../tables/migrations/0001_initial.py | 43 ------- azu_bot_django/tables/migrations/__init__.py | 0 infra/docker-compose.yml | 15 ++- infra/nginx/default.conf | 5 +- requirements.txt | 1 + 40 files changed, 191 insertions(+), 452 deletions(-) delete mode 100644 azu_bot_django/admin_users/migrations/0001_initial.py delete mode 100644 azu_bot_django/admin_users/migrations/__init__.py delete mode 100644 azu_bot_django/cafe/management/__init__.py delete mode 100644 azu_bot_django/cafe/management/commands/__int__.py delete mode 100644 azu_bot_django/cafe/management/commands/_private.py delete mode 100644 azu_bot_django/cafe/management/commands/load_data_cafe.py delete mode 100644 azu_bot_django/cafe/migrations/0001_initial.py delete mode 100644 azu_bot_django/cafe/migrations/__init__.py delete mode 100644 azu_bot_django/db.sqlite3 delete mode 100644 azu_bot_django/menu/management/__init__.py delete mode 100644 azu_bot_django/menu/management/commands/__int__.py delete mode 100644 azu_bot_django/menu/management/commands/_private.py delete mode 100644 azu_bot_django/menu/management/commands/load_data_menu.py delete mode 100644 azu_bot_django/menu/migrations/0001_initial.py delete mode 100644 azu_bot_django/menu/migrations/__init__.py delete mode 100644 azu_bot_django/reservation/migrations/0001_initial.py delete mode 100644 azu_bot_django/reservation/migrations/__init__.py delete mode 100644 azu_bot_django/tables/migrations/0001_initial.py delete mode 100644 azu_bot_django/tables/migrations/__init__.py diff --git a/azu_bot_aiogram/filters/is_correct_person_amount.py b/azu_bot_aiogram/filters/is_correct_person_amount.py index 4a7394c..340d110 100644 --- a/azu_bot_aiogram/filters/is_correct_person_amount.py +++ b/azu_bot_aiogram/filters/is_correct_person_amount.py @@ -10,7 +10,7 @@ class IsPersonAmount(BaseFilter): async def __call__(self, message: Message) -> bool: """Проверка корректности ввода количества человек.""" - pattern = r'^(0?[1-9]|[12][0-9]|3[01])$' + pattern = r'^(0?[1-9]|[123][0-9]|4[0])$' if re.match(pattern, message.text): return {'amount': message.text} else: diff --git a/azu_bot_aiogram/handlers/api.py b/azu_bot_aiogram/handlers/api.py index 712d5bd..1ac940b 100644 --- a/azu_bot_aiogram/handlers/api.py +++ b/azu_bot_aiogram/handlers/api.py @@ -1,43 +1,33 @@ import aiohttp -from settings import django_token, settings, port -async def get_session(): - session = aiohttp.ClientSession( - headers={"Authorization": f'Token {django_token}'} - ) - return session +async def get_cafe(bot=None): + async with aiohttp.ClientSession() as session: + async with session.get( + 'http://backend:8000/cafes/' + ) as response: + return await response.json() -async def get_cafe(bot=None): - mysession = await get_session() - async with mysession as session: +async def get_cafe_admins(cafe): + async with aiohttp.ClientSession() as session: async with session.get( - f'http://backend:{port}/cafes/' + f'http://backend:8000/cafes/{cafe}/admins/' ) as response: - response = await response.json() - if not isinstance(response, list): - await bot.send_message( - chat_id=settings.bots.admin_id, - text='Нет связи с базой данных!' - 'Проверьте валидность django token!') - return None - return response + return await response.json() async def post_quantity(cafe, data): - mysession = await get_session() - async with mysession as session: + async with aiohttp.ClientSession() as session: async with session.post( - f'http://backend:{port}/cafes/{cafe}/quantity/', json=data + f'http://backend:8000/cafes/{cafe}/quantity/', json=data ) as response: return await response.json() async def post_reservation(cafe_id, data): - mysession = await get_session() - async with mysession as session: + async with aiohttp.ClientSession() as session: async with session.post( - f'http://backend:{port}/cafes/{cafe_id}/reservations/', json=data + f'http://backend:8000/cafes/{cafe_id}/reservations/', json=data ) as response: return await response.json() diff --git a/azu_bot_aiogram/handlers/appsched.py b/azu_bot_aiogram/handlers/appsched.py index 0544f3c..a7373bb 100644 --- a/azu_bot_aiogram/handlers/appsched.py +++ b/azu_bot_aiogram/handlers/appsched.py @@ -4,6 +4,8 @@ from aiogram.fsm.context import FSMContext from aiogram.types import Message from apscheduler.schedulers.asyncio import AsyncIOScheduler + +from handlers.api import get_cafe from handlers.get_sunset import get_sunset_from_api from keyboards.reply_keyboards import reminder_kbd from utils.states import StepsForm @@ -13,7 +15,7 @@ async def get_reminder_time(message: Message, bot: Bot, state: FSMContext): """Выбор клиентом времени напоминания об ифтаре.""" await message.answer( 'Уведомляем Вас, что отмена брони и возврат средств ' - 'возможны не менее чем за 3 часа до начала ифтара.\n' + 'возможны не менее чем за 2 часа до начала ифтара.\n' 'За какой период времени до начала ифтара отправить Вам напоминание?', reply_markup=reminder_kbd()) await state.set_state(StepsForm.REMINDER_STATE) @@ -40,21 +42,35 @@ async def three_hours_before_iftar( 'bot': bot, 'chat_id': message.from_user.id, 'state': state } ) + await message.answer( + 'За 3 часа до ифтара в выбранный день мы отправим напоминание.\n' + 'С нетерпением ждем встречи с Вами!\n' + 'Ваше кафе "АЗУ"' + ) async def send_reminder_3_hours(bot: Bot, chat_id: int, state: FSMContext): """Напоминание о предстоящем ифтаре за 3 часа до начала.""" + cafes = await get_cafe(bot) context_data = await state.get_data() name = context_data.get('name') address = context_data.get('address') date = context_data.get('date') person_amount = context_data.get('person_amount') + cafe_number = '' + for cafe in cafes: + if cafe['address'] == address: + break + cafe_number = cafe['number'] text = ( f'Ассэламуалейкум, {name}!\n' f'Напоминаем Вам, что {date} вы забронировали стол ' f'на {person_amount} человека в кафе A Z U.\n' 'До начала ифтара осталось менее 3 часов.\n' - f'Мы ждем Вас по адресу: г. Казань, {address}.') + f'Мы ждем Вас по адресу: г. Казань, {address}.\n' + 'Для отмены брони пожалуйста свяжитесь с нами ' + f'по телефону {cafe_number}' + ) await bot.send_message(chat_id=chat_id, text=text) @@ -79,24 +95,53 @@ async def one_day_before_iftar( 'bot': bot, 'chat_id': message.from_user.id, 'state': state } ) + await message.answer( + 'За 24 часа до начала ифтара мы отправим напоминание.\n' + 'С нетерпением ждем встречи с Вами!\n' + 'Ваше кафе "АЗУ"' + ) async def send_reminder_1_day(bot: Bot, chat_id: int, state: FSMContext): """Напоминание о предстоящем ифтаре за 1 сутки до начала.""" + cafes = await get_cafe(bot) context_data = await state.get_data() name = context_data.get('name') address = context_data.get('address') date = context_data.get('date') person_amount = context_data.get('person_amount') + cafe_number = '' + for cafe in cafes: + if cafe['address'] == address: + break + cafe_number = cafe['number'] text = ( f'Ассэламуалейкум, {name}!\n' f'Напоминаем Вам, что {date} вы забронировали стол ' f'на {person_amount} человека в кафе A Z U.\n' 'До начала ифтара осталось менее 24 часов.\n' - f'Мы ждем Вас по адресу: г. Казань, {address}.') + f'Мы ждем Вас по адресу: г. Казань, {address}.' + 'Для отмены брони пожалуйста свяжитесь с нами ' + f'по телефону {cafe_number}' + ) await bot.send_message(chat_id=chat_id, text=text) async def no_reminder(message: Message, bot: Bot, state: FSMContext): """Обработчик отсутствия необходимости напоминания.""" - pass + cafes = await get_cafe(bot) + context_data = await state.get_data() + address = context_data.get('address') + cafe_number = '' + for cafe in cafes: + if cafe['address'] == address: + break + cafe_number = cafe['number'] + await message.answer( + 'Вы отказались от напоминания об ифтаре.\n' + 'Если, к нашему глубокому сожалению, Вы не сможете ' + 'посетить ифтар, пожалуйста свяжитесь с нами по телефону ' + f'{cafe_number} для отмены заказа.\n' + 'С нетерпением ждем встречи с Вами!\n' + 'Ваше кафе "АЗУ"' + ) diff --git a/azu_bot_aiogram/handlers/basic.py b/azu_bot_aiogram/handlers/basic.py index 8eaede3..d577a1e 100644 --- a/azu_bot_aiogram/handlers/basic.py +++ b/azu_bot_aiogram/handlers/basic.py @@ -232,7 +232,11 @@ async def confirm_order( text = 'Вы выбрали:\n' for number, amount in data_sets_order.items(): text += f'Сет №{number} в количестве {amount} шт.\n' - text += f'Общая стоимость: {total_price} руб.' + text += ( + f'Общая стоимость: {total_price} руб.\n' + 'Чтобы изменить заказ - просто введите здесь новую комбинацию ' + 'сетов и их количества.' + ) await message.answer(text=text, reply_markup=go_to_pay_or_choose_food_kbd()) @@ -287,8 +291,11 @@ async def choose_pay_method(message: Message, bot: Bot, state: FSMContext): async def no_free_table(message: Message, bot: Bot, state: FSMContext): """Диалог при отсутствии свободных столов.""" - await state.update_data(person_amount=message.text) - await message.answer('К сожалению нужного Вам столика нет в наличии.\n' + if message.text.startswith('Назад'): + pass + else: + await state.update_data(person_amount=message.text) + await message.answer('К сожалению нужного столика нет в наличии.\n' 'Можем предложить Вам забронировать стол ' 'в другом кафе нашей сети.', reply_markup=move_tables_or_change_cafe_kbd()) diff --git a/azu_bot_aiogram/handlers/get_free_places.py b/azu_bot_aiogram/handlers/get_free_places.py index 77e93b7..4b5b89b 100644 --- a/azu_bot_aiogram/handlers/get_free_places.py +++ b/azu_bot_aiogram/handlers/get_free_places.py @@ -2,6 +2,7 @@ async def get_free_places(cafes, context_data): + """Получить количество свободных мест в кафе.""" avaliable_cafes = [] person_amount = int(context_data.get('person_amount')) for cafe in cafes: @@ -13,7 +14,9 @@ async def get_free_places(cafes, context_data): context_data.get('date').split('.')[::-1] ) data_dict['quantity'] = 0 - check_current_cafe = await post_quantity(cafe['id'], data=data_dict) + check_current_cafe = await post_quantity( + cafe['id'], data=data_dict + ) free_places = int(check_current_cafe['quantity']) if person_amount <= free_places: avaliable_cafes.append(cafe['address']) diff --git a/azu_bot_aiogram/handlers/get_sunset.py b/azu_bot_aiogram/handlers/get_sunset.py index 1234f16..8e90eae 100644 --- a/azu_bot_aiogram/handlers/get_sunset.py +++ b/azu_bot_aiogram/handlers/get_sunset.py @@ -2,14 +2,13 @@ import requests - SUNSET_API = ( 'https://api.sunrisesunset.io/json?lat=55.78874&lng=49.12214&date=' ) def get_sunset_from_api(date): - """Получаем время захода солнца""" + """Получаем время захода солнца в выбраный день.""" response = requests.get(SUNSET_API + str(date)) data = response.json() sunset_of_day = data['results']['sunset'] diff --git a/azu_bot_aiogram/handlers/media_group.py b/azu_bot_aiogram/handlers/media_group.py index ca3c1ce..e4921fe 100644 --- a/azu_bot_aiogram/handlers/media_group.py +++ b/azu_bot_aiogram/handlers/media_group.py @@ -1,15 +1,13 @@ from aiogram import Bot -from aiogram.types import FSInputFile, InputMediaPhoto, Message +from aiogram.types import ( + FSInputFile, InputMediaPhoto, Message, ReplyKeyboardRemove +) async def get_media_group(message: Message, bot: Bot): """Сеты для оформления заказа.""" set_1 = InputMediaPhoto( - type='photo', media=FSInputFile(r'images/set1.jpg'), - caption=('Выберите сеты для ифтара и введите в формате ' - 'номер сета - количество сетов в заказе, например, ' - '"1-1, 2-3" - это означает, что Вы выбрали 1 сет №1 и ' - '3 сета № 2.') + type='photo', media=FSInputFile(r'images/set1.jpg') ) set_2 = InputMediaPhoto( type='photo', media=FSInputFile(r'images/set2.jpg') @@ -37,6 +35,13 @@ async def get_media_group(message: Message, bot: Bot): ) media = [set_1, set_2, set_3, set_4, set_5, set_6, set_7, set_8, set_9] await message.answer_media_group(media) + await message.answer( + 'Выберите сеты и закажите в формате:\n' + '"номер сета - количество сетов", например,\n' + '"1-1, 2-3" - означает, что выбраны\n' + '"сет №1 - 1 шт. и сет № 2 - 3 шт."\n', + reply_markup=ReplyKeyboardRemove() + ) async def watch_media_group(message: Message, bot: Bot): diff --git a/azu_bot_aiogram/handlers/pay.py b/azu_bot_aiogram/handlers/pay.py index cea8605..f49e879 100644 --- a/azu_bot_aiogram/handlers/pay.py +++ b/azu_bot_aiogram/handlers/pay.py @@ -3,7 +3,7 @@ from aiogram.types import LabeledPrice, Message, PreCheckoutQuery from handlers.basic import cafe_select_kbd -from handlers.api import get_cafe, post_reservation +from handlers.api import get_cafe, post_reservation, get_cafe_admins from handlers.appsched import get_reminder_time from settings import settings from utils.states import StepsForm @@ -88,6 +88,7 @@ async def pre_checkout_query( pre_checkout_query.id, ok=True ) + await state.update_data(cafe_id=cafe['id']) else: await bot.answer_pre_checkout_query( pre_checkout_query.id, @@ -133,6 +134,10 @@ async def succesfull_payment( for number, amount in data_sets_order.items(): text += f'Сет №{number} в количестве {amount} шт.\n' text += f'Общая стоимость: {total_price} руб.' - await bot.send_message(chat_id=settings.bots.admin_id, text=text) + cafe_id = context_data.get('cafe_id') + admins = await get_cafe_admins(cafe_id) + admins = admins['admins'] + for admin in admins: + await bot.send_message(chat_id=admin['telegram'], text=text) await state.set_state(StepsForm.FINAL_STATE) await get_reminder_time(message, bot, state) diff --git a/azu_bot_aiogram/keyboards/reply_keyboards.py b/azu_bot_aiogram/keyboards/reply_keyboards.py index ac03b7c..1d054fe 100644 --- a/azu_bot_aiogram/keyboards/reply_keyboards.py +++ b/azu_bot_aiogram/keyboards/reply_keyboards.py @@ -141,7 +141,6 @@ def enter_phone_kbd(): keyboard_builder.adjust(1, 2) return keyboard_builder.as_markup( resize_keyboard=True, - one_time_keyboard=True, input_field_placeholder='Введите номер телефона.' ) @@ -155,8 +154,7 @@ def go_to_pay_or_choose_food_kbd(): keyboard_builder.adjust(1, 2) return keyboard_builder.as_markup( resize_keyboard=True, - one_time_keyboard=True, - input_field_placeholder='Перейти к оплате?' + input_field_placeholder='Измените заказ или перейдите к оплате.' ) @@ -169,7 +167,6 @@ def check_order_kbd(): keyboard_builder.adjust(1, 2) return keyboard_builder.as_markup( resize_keyboard=True, - one_time_keyboard=True, input_field_placeholder='Проверьте Ваш заказ.' ) @@ -190,7 +187,6 @@ def no_free_tables_kbd(): """Появляется в случае отсутствия свободных столов в кафе.""" keyboard_builder = ReplyKeyboardBuilder() keyboard_builder.button(text='Выбрать другое кафе') - keyboard_builder.button(text='Сдвигать столы') keyboard_builder.button(text='Назад ' + emojize(':calendar:')) keyboard_builder.button(text='Отмена') keyboard_builder.adjust(2) @@ -210,5 +206,5 @@ def reminder_kbd(): return keyboard_builder.as_markup( resize_keyboard=True, one_time_keyboard=True, - input_field_placeholder='За сколько до начала ифтара напомнить?' + input_field_placeholder='Выберите одну из кнопок.' ) diff --git a/azu_bot_aiogram/main.py b/azu_bot_aiogram/main.py index a3f2c64..e4a954c 100644 --- a/azu_bot_aiogram/main.py +++ b/azu_bot_aiogram/main.py @@ -45,6 +45,10 @@ async def start(): scheduler.start() dp.update.middleware.register(SchedulerMiddleware(scheduler)) + dp.message.register( + get_start, + Command(commands=['start', 'run']) + ) dp.message.register( order, F.text == 'Оплатить через ЮКасса', @@ -64,10 +68,6 @@ async def start(): F.successful_payment, StepsForm.PAY_STATE ) - dp.message.register( - get_start, - Command(commands=['start', 'run']) - ) dp.message.register( get_true_contact, F.contact, diff --git a/azu_bot_aiogram/settings.py b/azu_bot_aiogram/settings.py index 6c4695b..d051307 100644 --- a/azu_bot_aiogram/settings.py +++ b/azu_bot_aiogram/settings.py @@ -1,10 +1,6 @@ from dataclasses import dataclass -import os from environs import Env -from dotenv import load_dotenv - -load_dotenv() @dataclass @@ -32,6 +28,4 @@ def get_settings(path: str): ) -port = os.getenv('PORT') -django_token = os.getenv('DJANGO_TOKEN') settings = get_settings('.env') diff --git a/azu_bot_django/Dockerfile b/azu_bot_django/Dockerfile index 91a3b26..4324066 100644 --- a/azu_bot_django/Dockerfile +++ b/azu_bot_django/Dockerfile @@ -6,7 +6,5 @@ COPY ./ ./ RUN pip3 install -r requirements.txt --no-cache-dir RUN python manage.py makemigrations cafe tables menu admin_users reservation -RUN python manage.py migrate -RUN python manage.py collectstatic --no-input -CMD ["uvicorn", "--host", "0.0.0.0", "--port", "8080", "azu_bot_django.asgi:application"] \ No newline at end of file +CMD ["uvicorn", "--host", "0.0.0.0", "--port", "8000", "azu_bot_django.asgi:application"] \ No newline at end of file diff --git a/azu_bot_django/admin_users/admin.py b/azu_bot_django/admin_users/admin.py index 4b88528..5b79227 100644 --- a/azu_bot_django/admin_users/admin.py +++ b/azu_bot_django/admin_users/admin.py @@ -5,7 +5,7 @@ ADD_CUSTOM_FIELDS_IN_USER_FORM = (( 'Работает в кафе', - {'fields': ('cafe',)} + {'fields': ('cafe', 'telegram_id',)} ),) diff --git a/azu_bot_django/admin_users/migrations/0001_initial.py b/azu_bot_django/admin_users/migrations/0001_initial.py deleted file mode 100644 index d3e2d5c..0000000 --- a/azu_bot_django/admin_users/migrations/0001_initial.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-26 07:18 - -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('cafe', '0001_initial'), - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='CustomUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('cafe', models.ForeignKey(blank=True, help_text='Для сотрудников кафе обязателен выбор к привязанному кафе', null=True, on_delete=django.db.models.deletion.CASCADE, to='cafe.cafe', verbose_name='В кафе')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'Пользователь', - 'verbose_name_plural': 'Пользователи', - 'ordering': ('username',), - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - ] diff --git a/azu_bot_django/admin_users/migrations/__init__.py b/azu_bot_django/admin_users/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/azu_bot_django/admin_users/models.py b/azu_bot_django/admin_users/models.py index bbc23cb..8fb379c 100644 --- a/azu_bot_django/admin_users/models.py +++ b/azu_bot_django/admin_users/models.py @@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError from django.db import models +from azu_bot_django.settings import MAX_CHAR_LENGTH from cafe.models import Cafe from reservation.models import OrderSets, Reservation @@ -16,6 +17,11 @@ class CustomUser(AbstractUser): null=True, help_text='Для сотрудников кафе обязателен выбор к привязанному кафе', ) + telegram_id = models.CharField( + 'Телеграмм администратора', + max_length=MAX_CHAR_LENGTH, + help_text='Администратору так же требуется написать боту хотя бы раз', + ) class Meta: verbose_name = 'Пользователь' diff --git a/azu_bot_django/azu_bot_django/settings.py b/azu_bot_django/azu_bot_django/settings.py index 01e0867..8e78404 100644 --- a/azu_bot_django/azu_bot_django/settings.py +++ b/azu_bot_django/azu_bot_django/settings.py @@ -1,14 +1,47 @@ import os from dataclasses import dataclass -from pathlib import Path from dotenv import load_dotenv from environs import Env load_dotenv() + +@dataclass +class Bots: + bot_token: str + admin_id: int + provider_token: str + + +@dataclass +class Settings: + bots: Bots + + +def get_settings(path: str): + env = Env() + env.read_env(path) + + return Settings( + bots=Bots( + bot_token=env.str('TOKEN'), + admin_id=env.int('ADMIN_ID'), + provider_token=env.str('PROVIDER_TOKEN'), + ) + ) + + +settings = get_settings('.env') + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +WEB_HOST = os.getenv( + 'WEB_HOST', default='127.0.0.1') +WEB_PORT = os.getenv( + 'WEB_PORT', default='') +WEB_PROTOCOL = os.getenv( + 'WEB_PROTOCOL', default='http://') SECRET_KEY = os.getenv( 'SECRET_KEY', default='django-insecure-$9h!ujjvgrgilsbc&%idh%)tb86u+^qkr#2#p!o^ej48n6)=m7') @@ -30,7 +63,6 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', - 'rest_framework.authtoken', 'ckeditor', 'cafe', 'menu', @@ -56,7 +88,7 @@ ROOT_URLCONF = 'azu_bot_django.urls' -CSRF_TRUSTED_ORIGINS = ["http://62.84.124.68:81"] +CSRF_TRUSTED_ORIGINS = [f"{WEB_PROTOCOL}{WEB_HOST}{WEB_PORT}"] TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates') TEMPLATES = [ @@ -78,24 +110,24 @@ # WSGI_APPLICATION = 'azu_bot_django.wsgi.application' ASGI_APPLICATION = 'azu_bot_django.asgi.application' -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} - # DATABASES = { # 'default': { -# 'ENGINE': 'django.db.backends.postgresql', -# 'NAME': os.getenv('DATABASE_NAME', default='postgres'), -# 'USER': os.getenv('DATABASE_USERNAME', default='postgres'), -# 'PASSWORD': os.getenv('DATABASE_PASSWORD', default='postgres'), -# 'HOST': os.getenv('DATABASE_HOST', default='db'), -# 'PORT': os.getenv('DATABASE_PORT', default='5432'), +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('DATABASE_NAME', default='postgres'), + 'USER': os.getenv('DATABASE_USERNAME', default='postgres'), + 'PASSWORD': os.getenv('DATABASE_PASSWORD', default='postgres'), + 'HOST': os.getenv('DATABASE_HOST', default='db'), + 'PORT': os.getenv('DATABASE_PORT', default='5432'), + } +} + AUTH_PASSWORD_VALIDATORS = [ { @@ -130,44 +162,3 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - -REST_FRAMEWORK = { - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', - ], - - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.TokenAuthentication', - ], - - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', -} - - -@dataclass -class Bots: - bot_token: str - admin_id: int - provider_token: str - - -@dataclass -class Settings: - bots: Bots - - -def get_settings(path: str): - env = Env() - env.read_env(path) - - return Settings( - bots=Bots( - bot_token=env.str('TOKEN'), - admin_id=env.int('ADMIN_ID'), - provider_token=env.str('PROVIDER_TOKEN'), - ) - ) - - -django_token = os.getenv('DJANGO_TOKEN') -settings = get_settings('.env') diff --git a/azu_bot_django/cafe/management/__init__.py b/azu_bot_django/cafe/management/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/azu_bot_django/cafe/management/commands/__int__.py b/azu_bot_django/cafe/management/commands/__int__.py deleted file mode 100644 index e69de29..0000000 diff --git a/azu_bot_django/cafe/management/commands/_private.py b/azu_bot_django/cafe/management/commands/_private.py deleted file mode 100644 index e69de29..0000000 diff --git a/azu_bot_django/cafe/management/commands/load_data_cafe.py b/azu_bot_django/cafe/management/commands/load_data_cafe.py deleted file mode 100644 index 2dd773d..0000000 --- a/azu_bot_django/cafe/management/commands/load_data_cafe.py +++ /dev/null @@ -1,22 +0,0 @@ -from csv import DictReader - -from django.core.management import BaseCommand - -from cafe.models import Cafe - - -class Command(BaseCommand): - """Загрузка кафе из static/data/cafes.csv в базу данных""" - help = "Loads data from csv files in static/data/cafes.csv" - - def handle(self, *args, **options): - for cafes in DictReader( - open('static/data/cafes.csv', encoding="utf8") - ): - cafe = Cafe( - id=cafes['id'], - name=cafes['name'], - address=cafes['address'], - number=cafes['number'] - ) - cafe.save() diff --git a/azu_bot_django/cafe/migrations/0001_initial.py b/azu_bot_django/cafe/migrations/0001_initial.py deleted file mode 100644 index 21807f2..0000000 --- a/azu_bot_django/cafe/migrations/0001_initial.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-26 07:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Cafe', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=256, unique=True, verbose_name='Название кафе')), - ('address', models.CharField(max_length=256, unique=True, verbose_name='Адрес кафе')), - ('number', models.CharField(max_length=256, verbose_name='Номер кафе')), - ], - options={ - 'verbose_name': 'Кафе', - 'verbose_name_plural': 'Кафе', - 'ordering': ('name',), - }, - ), - migrations.AddConstraint( - model_name='cafe', - constraint=models.UniqueConstraint(fields=('name', 'address'), name='unique_name_address'), - ), - ] diff --git a/azu_bot_django/cafe/migrations/__init__.py b/azu_bot_django/cafe/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/azu_bot_django/cafe/views.py b/azu_bot_django/cafe/views.py index a81840e..eb308b2 100644 --- a/azu_bot_django/cafe/views.py +++ b/azu_bot_django/cafe/views.py @@ -7,6 +7,7 @@ from tables.models import Table from reservation.models import Reservation from cafe.serializers import CafeSerializer +from admin_users.models import CustomUser class CafeViewSet(viewsets.ReadOnlyModelViewSet): @@ -40,3 +41,18 @@ def quantity(self, request, pk): 'date': res_date, 'quantity': quantity }) + + @action(methods=['GET'], detail=True) + def admins(self, request, pk): + cafe = Cafe.objects.get(id=pk) + cafe_admins = CustomUser.objects.filter(cafe=cafe) + answer = [] + for admin in cafe_admins: + answer.append({ + 'name': admin.first_name, + 'telegram': admin.telegram_id + }) + return JsonResponse( + {'cafe': cafe.address, + 'admins': answer} + ) diff --git a/azu_bot_django/db.sqlite3 b/azu_bot_django/db.sqlite3 deleted file mode 100644 index 037954661c9c20be5f82870b9596c9c6776ed714..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270336 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 Date: Tue, 28 Nov 2023 05:43:24 +0500 Subject: [PATCH 14/16] Fix redoc --- README.md | 127 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index f86a391..8a4b8f4 100644 --- a/README.md +++ b/README.md @@ -4,103 +4,106 @@ ## Технологии - Python 3.11 -- aiogram 3.1.1 - Django 4.2.7 +- Django REST Framework 3.14.0 +- Aiogram 3.1.1 +- PostgreSQL 13.0 +- Nginx 1.21.3 +- Docker +- Docker-compose +- Docker Hub - SunriseSunset.io API # Установка ## Копирование репозитория -Клонируем репозиторий +Клонируем репозиторий и переходим в директорию infra: ``` ~ git clone git@github.com:Studio-Yandex-Practicum-Hackathons/cafe_azu_bot_2.git +~ cd ./cafe_azu_bot_2/infra/ ``` +Требуется изменить server_name и listen в ./infra/nginx/default.conf, ports в docker-compose.yml -## Виртуальное окружение -Переходим в клонированный репозиторий +## Подготовка боевого сервера: +1. Перейдите на боевой сервер: ``` -~ cd {путь до папки с клонированным репозиторем} -~ cd cafe_azu_bot_2 +ssh username@server_address ``` -Устанавливаем и активируем виртуальное окружение +2. Обновите индекс пакетов APT: ``` -~ py -3.11 -m venv venv -~ . venv/Scripts/activate +sudo apt update ``` -Устанавливаем требуемые зависимости: +и обновите установленные в системе пакеты и установите обновления безопасности: ``` -~ pip install -r requirements.txt +sudo apt upgrade -y ``` - -## База данных -В azu_bot_django/azu_bot_django/settings.py выбрать нужную базу данных (sqlite3 или postgresql) -### env -Выполнять только если ваша база данных postgresql, иначе пропустить этот шаг. - -Создать файл .env со следующими строками для базы данных: -- DATABASE_NAME = <Имя для подключения к базе данных> -- DATABASE_USERNAME = <Имя пользователя для подключения к базе данных> -- DATABASE_PASSWORD = <Пароль для подключения к базе данных> -- DATABASE_HOST = <Адрес для подключения к базе данных> -- DATABASE_PORT = <Порт для подключения к базе данных> - -### Миграция базы данных -Переходим в раздел с базой данных: +Создайте папку `nginx`: ``` -~ cd azu_bot_django +mkdir nginx +``` +Скопируйте файлы docker-compose.yaml, nginx/default.conf из вашего проекта на сервер в home/<ваш_username>/docker-compose.yaml, home/<ваш_username>/nginx/default.conf соответственно: ``` -Создаем миграцию для базы данных: +scp docker-compose.yaml @/home//docker-compose.yaml +scp default.conf @/home//nginx/default.conf ``` -~ py manage.py makemigrations cafe tables menu admin_users reservation +Установите Docker и Docker-compose: ``` -И применяем её: +sudo apt install docker.io ``` -~ py manage.py migrate ``` -Создаем суперпользователя: +sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose ``` -~ py manage.py createsuperuser ``` -Вводим поочередно Имя пользователя, почту и пароль. -После чего запускаем базу данных: +sudo chmod +x /usr/local/bin/docker-compose ``` -~ uvicorn azu_bot_django.asgi:application +Проверьте правильность установки Docker-compose: ``` - -### Наполнение базы данных -Доступ к сервису становится доступен по [адресу](http://127.0.0.1:8000/admin/) - -По указаному адресу нужно создать хотя бы по одному экземпляру кафе, стола, блюда, сета. - -Так же можно наполнить базу данных готовыми сетами, блюдами, кафе и столам: - +sudo docker-compose --version ``` -~ py manage.py load_data +На боевом сервере создайте файл .env: ``` - -Создать пользователя-бота и по [адресу](http://127.0.0.1:8000/admin/authtoken/tokenproxy/) нужно создать токен для бота. - -## Телеграмм-бот -### env -В уже существующем файле .env добавить строки: +touch .env +``` +и заполните переменные окружения +``` +nano .env - TOKEN = <Токен бота, можно получить у BotFather> - ADMIN_ID = - PROVIDER_TOKEN = <Токен платежной системы> -- DJANGO_TOKEN = <Токен для бота, который был создан в разделе базы данных> +- SECRET_KEY= -### Запуск -Для полного запуска телеграмм-бота с базой данных нужно: -- Выполнить все вышеуказанные действия по установке. -- Перейти в папку с базой данных и запустить её: +- POSTGRES_DB = postgres +- POSTGRES_USER = postgres +- POSTGRES_PASSWORD = postgres +- POSTGRES_HOST = db +- POSTGRES_PORT = 5432 + +- WEB_HOST = +- WEB_PORT = <Порт сервера> +- WEB_PROTOKOL = <Протокол сервера> +``` + +## Развертывание проекта с помощью Docker: +Разворачиваем контейнеры в фоновом режиме из папки infra: +``` +sudo docker compose up -d +``` +При первом запуске выполняем миграции: ``` -~ cd azu_bot_django -~ uvicorn azu_bot_django.asgi:application +sudo docker compose exec backend python manage.py migrate ``` -- Выйти из папки с базой данных, перейти в папку бота, запустить бота: +При первом запуске собираем статику: ``` -~ cd azu_bot_aiogram -~ py main.py +sudo docker compose exec backend python manage.py collectstatic --no-input ``` -Телеграмм-бот готов к работе. +Создайте суперпользователя: +``` +sudo docker compose exec backend python manage.py createsuperuser +``` +Загружаем данные из csv-таблиц в базу данных: +``` +sudo docker compose exec backend python manage.py load_data +``` + # Адресные пути - [Документация к API базе данных](http://127.0.0.1:8000/redoc) - [Админ-панель базы данных](http://127.0.0.1:8000/admin) From 6978626f18dc9e9ff6947d15b0ac8d8ebf314c2b Mon Sep 17 00:00:00 2001 From: Certelen Date: Tue, 28 Nov 2023 05:56:15 +0500 Subject: [PATCH 15/16] fix compose --- infra/docker-compose.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index ccd5cbf..1d36d72 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -9,8 +9,7 @@ services: env_file: - ../.env backend: - # image: certelen/azu_cafe_django:v1 - build: ../azu_bot_django/ + image: certelen/azu_cafe_django:v1 restart: always volumes: - static_value:/app/static/ @@ -18,8 +17,7 @@ services: env_file: - ../.env frontend: - # image: certelen/azu_cafe_aiogram:v1 - build: ../azu_bot_aiogram/ + image: certelen/azu_cafe_aiogram:v1 env_file: - ../.env nginx: From 6f2b117f0d908f5954702a00596249e44ddbdc51 Mon Sep 17 00:00:00 2001 From: Davletova Lyudmila <108341911+luydmila-davletova@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:55:34 +0300 Subject: [PATCH 16/16] Create main.yml --- .github/workflows/main.yml | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..d064e53 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,63 @@ +name: Django-app workflow + +on: [push] + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + # обновление pip + python -m pip install --upgrade pip + pip install flake8 pep8-naming flake8-broken-line flake8-return flake8-isort + pip install -r requirements.txt + + - name: Test with flake8 and django tests + run: | + python -m flake8 + + build_and_push_to_docker_hub: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + needs: tests + steps: + - name: Check out the repo + uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to Docker + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Push to Docker Hub + uses: docker/build-push-action@v2 + with: + push: true + tags: certelen/azu_cafe_django + + deploy: + runs-on: ubuntu-latest + needs: build_and_push_to_docker_hub + steps: + - name: executing remote ssh commands to deploy + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USER }} + key: ${{ secrets.SSH_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + script: | + # Выполняет pull образа с DockerHub + sudo docker pull certelen/azu_cafe_django + #остановка всех контейнеров + sudo docker stop $(sudo docker ps -a -q) + sudo docker run --rm -d -p 5000:5000 certelen/azu_cafe_django