From 6e8d1fb6e3d05e81e4a1e8b970fed1553eadadb6 Mon Sep 17 00:00:00 2001 From: trevor Date: Fri, 6 Dec 2024 10:47:30 +0900 Subject: [PATCH] init commit 2 --- .gitignore | 11 ++ README.md | 164 ++++++++++++++++++++++++++ bot/__init__.py | 0 bot/admin.py | 3 + bot/apps.py | 6 + bot/bot.py | 39 +++++++ bot/migrations/__init__.py | 0 bot/models.py | 3 + bot/tests.py | 3 + bot/urls.py | 0 bot/views.py | 3 + hotels/__init__.py | 0 hotels/admin.py | 17 +++ hotels/apps.py | 6 + hotels/migrations/0001_initial.py | 33 ++++++ hotels/migrations/0002_initial.py | 22 ++++ hotels/migrations/__init__.py | 0 hotels/models.py | 23 ++++ hotels/tests.py | 3 + hotels/urls.py | 0 hotels/views.py | 3 + manage.py | 22 ++++ req.txt | 25 ++++ touchh/__init__.py | 0 touchh/asgi.py | 16 +++ touchh/settings.py | 185 ++++++++++++++++++++++++++++++ touchh/urls.py | 6 + touchh/wsgi.py | 16 +++ users/__init__.py | 0 users/admin.py | 15 +++ users/apps.py | 6 + users/migrations/0001_initial.py | 59 ++++++++++ users/migrations/__init__.py | 0 users/models.py | 51 ++++++++ users/tests.py | 3 + users/urls.py | 0 users/views.py | 3 + 37 files changed, 746 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bot/__init__.py create mode 100644 bot/admin.py create mode 100644 bot/apps.py create mode 100644 bot/bot.py create mode 100644 bot/migrations/__init__.py create mode 100644 bot/models.py create mode 100644 bot/tests.py create mode 100644 bot/urls.py create mode 100644 bot/views.py create mode 100644 hotels/__init__.py create mode 100644 hotels/admin.py create mode 100644 hotels/apps.py create mode 100644 hotels/migrations/0001_initial.py create mode 100644 hotels/migrations/0002_initial.py create mode 100644 hotels/migrations/__init__.py create mode 100644 hotels/models.py create mode 100644 hotels/tests.py create mode 100644 hotels/urls.py create mode 100644 hotels/views.py create mode 100755 manage.py create mode 100644 req.txt create mode 100644 touchh/__init__.py create mode 100644 touchh/asgi.py create mode 100644 touchh/settings.py create mode 100644 touchh/urls.py create mode 100644 touchh/wsgi.py create mode 100644 users/__init__.py create mode 100644 users/admin.py create mode 100644 users/apps.py create mode 100644 users/migrations/0001_initial.py create mode 100644 users/migrations/__init__.py create mode 100644 users/models.py create mode 100644 users/tests.py create mode 100644 users/urls.py create mode 100644 users/views.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1bdca65a --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.venv +.env +__pycache__ +.vscode +.history +.idea +.git +.DS_Store +node_modules +package-lock.json +package.json diff --git a/README.md b/README.md new file mode 100644 index 00000000..3c9ac9ae --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# Проект управления отелями + +## Описание + +Проект для управления отелями, пользователями, интеграциями с PMS, а также для контроля статистики и уведомлений. Включает веб-админку, REST API для управления данными и Telegram-бота для взаимодействия с пользователями. + +## Стек технологий + +- Django (для бэкенда) +- Jazzmin (для кастомизации админки) +- MySQL (для хранения данных) +- python-telegram-bot (для Telegram-бота) +- Docker (для контейнеризации базы данных и проекта) + +## Установка + +### 1. Клонирование репозитория + +```bash +git clone +cd yourrepository +```` + +### 2. Установка зависимостей + +```bash + +python -m venv .venv +source .venv/bin/activate # Для Linux/MacOS +.venv\Scripts\activate # Для Windows + +pip install -r requirements.txt +``` +### 3. Настройка базы данных MySQL +#### 3.1. Запуск контейнера с MySQL + +Используем Docker для поднятия MySQL контейнера: + +``` bash +docker-compose up -d mysql +``` +#### 3.2. Создание базы данных и пользователя + +После поднятия контейнера с MySQL, создайте базу данных и пользователя: + +### 3.2. Настройка базы данных + +После поднятия контейнера с MySQL, создайте таблицы и загрузите дамп: + +``` +docker exec -it mysql_container bash +mysql -u root -p +``` + +Введите пароль от MySQL, а затем загрузите ваш дамп: + +```bash +mysql -u root -p your_database_name < /path/to/your_dump.sql +``` +### 3.3. Применение миграций + +После настройки базы данных выполните миграции для вашего Django-проекта: + +```bash +python manage.py migrate +``` + +### 4. Настройка админки + +Для настройки админки с использованием Dazzling и Jazzmin, добавьте соответствующие настройки в settings.py: + +```python +INSTALLED_APPS = [ + 'dazzle', + 'jazzmin', + ... +] + +JAZZMIN_SETTINGS = { + "site_title": "My Admin", + "site_header": "My Administration", + "site_brand": "My Brand", + "footer": { + "copyright": False, + "version": False, + }, +} +``` + +### 5. Запуск проекта + +Запустите сервер Django: + +```bash +python manage.py runserver +``` + +Теперь проект будет доступен по адресу http://127.0.0.1:8000. + +##### Структура проекта + + hotel_manager/ — основная директория проекта. + hotel_manager/settings.py — настройки Django. + hotel_manager/models.py — модели данных для отелей, пользователей и статистики. + hotel_manager/views.py — представления для работы с данными. + hotel_manager/urls.py — маршруты проекта. + bot/ — директория для бота, использующего python-telegram-bot. + +##### Модели + + Отель (Hotel) + Название отеля + ID отеля + PMS (Bnovo, Travel Line, Realty) + Статус интеграции с PMS + + Пользователь (User) + Имя пользователя + Роль (Admin или Hotel User) + Связь с отелем + + Настройки уведомлений (Notification Settings) + Включено/выключено уведомление + Часовой пояс + Время отправки уведомлений + + Статистика (Statistics) + Количество несанкционированных заселений за период + Статус ошибок + Даты и номера нарушений + +##### API + + Администратор: + Добавить/удалить отель + Добавить/удалить пользователя + Проверка статуса интеграции с PMS + Управление уведомлениями + + Пользователь отеля: + Получение статистики по заселениям + Управление уведомлениями + +##### Интеграция с Telegram-ботом + + Бот для администраторов позволяет управлять отелями, пользователями, уведомлениями и проверять статус интеграций. + + Бот для пользователей отелей позволяет получать статистику по заселениям и управлять уведомлениями. + +##### Пример команды для администратора + + Добавить отель + Список отелей + Удалить отель + Проверить статус PMS + +##### Пример команды для пользователя отеля + + Показать статистику за вчера + Управление уведомлениями + +#### Проверка интеграции с PMS + +Для каждого отеля можно проверять статус интеграции с PMS (Bnovo, Travel Line, Realty) и получать ответ о доступности PMS. \ No newline at end of file diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bot/admin.py b/bot/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/bot/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/bot/apps.py b/bot/apps.py new file mode 100644 index 00000000..1cd7ff2e --- /dev/null +++ b/bot/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BotConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'bot' diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 00000000..e50bfaf1 --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,39 @@ +from telegram import Update +from telegram.ext import Updater, CommandHandler, CallbackContext +from users.models import User, UserConfirmation +import uuid + +def start(update: Update, context: CallbackContext): + user_id = update.message.from_user.id + chat_id = update.message.chat_id + + user, created = User.objects.get_or_create(telegram_id=user_id, defaults={'chat_id': chat_id}) + if not user.confirmed: + confirmation_code = str(uuid.uuid4()) + UserConfirmation.objects.create(user=user, confirmation_code=confirmation_code) + update.message.reply_text(f"Ваш код подтверждения: {confirmation_code}") + else: + update.message.reply_text("Вы уже зарегистрированы!") + +def confirm(update: Update, context: CallbackContext): + user_id = update.message.from_user.id + code = ' '.join(context.args) + + try: + confirmation = UserConfirmation.objects.get(user__telegram_id=user_id, confirmation_code=code) + confirmation.user.confirmed = True + confirmation.user.save() + confirmation.delete() + update.message.reply_text("Регистрация подтверждена!") + except UserConfirmation.DoesNotExist: + update.message.reply_text("Неверный код подтверждения!") + +def main(): + updater = Updater("YOUR_TELEGRAM_BOT_TOKEN") + dispatcher = updater.dispatcher + + dispatcher.add_handler(CommandHandler("start", start)) + dispatcher.add_handler(CommandHandler("confirm", confirm)) + + updater.start_polling() + updater.idle() diff --git a/bot/migrations/__init__.py b/bot/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bot/models.py b/bot/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/bot/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/bot/tests.py b/bot/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/bot/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/bot/urls.py b/bot/urls.py new file mode 100644 index 00000000..e69de29b diff --git a/bot/views.py b/bot/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/bot/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/hotels/__init__.py b/hotels/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hotels/admin.py b/hotels/admin.py new file mode 100644 index 00000000..09fe2327 --- /dev/null +++ b/hotels/admin.py @@ -0,0 +1,17 @@ +from django.contrib import admin +from .models import Hotel, UserHotel + +@admin.register(Hotel) +class HotelAdmin(admin.ModelAdmin): + list_display = ('name', 'pms_type', 'created_at') + search_fields = ('name',) + list_filter = ('pms_type',) + ordering = ('-created_at',) + +@admin.register(UserHotel) +class UserHotelAdmin(admin.ModelAdmin): + list_display = ('user', 'hotel') + search_fields = ('user', 'hotel') + list_filter = ('hotel',) + ordering = ('-hotel',) + \ No newline at end of file diff --git a/hotels/apps.py b/hotels/apps.py new file mode 100644 index 00000000..d8bd62fa --- /dev/null +++ b/hotels/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HotelsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'hotels' diff --git a/hotels/migrations/0001_initial.py b/hotels/migrations/0001_initial.py new file mode 100644 index 00000000..3df5b4c0 --- /dev/null +++ b/hotels/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 5.1.4 on 2024-12-05 23:39 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Hotel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Hotel Name')), + ('pms_type', models.CharField(choices=[('bnovo', 'Bnovo'), ('travelline', 'Travel Line')], max_length=50, verbose_name='PMS Type')), + ('api_key', models.CharField(blank=True, max_length=255, null=True, verbose_name='API Key')), + ('public_key', models.CharField(blank=True, max_length=255, null=True, verbose_name='Public Key')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ], + ), + migrations.CreateModel( + name='UserHotel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Hotel')), + ], + ), + ] diff --git a/hotels/migrations/0002_initial.py b/hotels/migrations/0002_initial.py new file mode 100644 index 00000000..bd71123e --- /dev/null +++ b/hotels/migrations/0002_initial.py @@ -0,0 +1,22 @@ +# Generated by Django 5.1.4 on 2024-12-05 23:39 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('hotels', '0001_initial'), + ('users', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='userhotel', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='User'), + ), + ] diff --git a/hotels/migrations/__init__.py b/hotels/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hotels/models.py b/hotels/models.py new file mode 100644 index 00000000..ac8453df --- /dev/null +++ b/hotels/models.py @@ -0,0 +1,23 @@ +from django.db import models +from users.models import User + +class Hotel(models.Model): + name = models.CharField(max_length=255, verbose_name="Hotel Name") + pms_type = models.CharField( + max_length=50, + choices=[('bnovo', 'Bnovo'), ('travelline', 'Travel Line')], + verbose_name="PMS Type" + ) + api_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="API Key") + public_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="Public Key") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created At") + + def __str__(self): + return self.name + +class UserHotel(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="User") + hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Hotel") + + def __str__(self): + return f"{self.user.username} - {self.hotel.name}" \ No newline at end of file diff --git a/hotels/tests.py b/hotels/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/hotels/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hotels/urls.py b/hotels/urls.py new file mode 100644 index 00000000..e69de29b diff --git a/hotels/views.py b/hotels/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/hotels/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/manage.py b/manage.py new file mode 100755 index 00000000..850eae6b --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'touchh.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/req.txt b/req.txt new file mode 100644 index 00000000..89b7714f --- /dev/null +++ b/req.txt @@ -0,0 +1,25 @@ +anyio==4.6.2.post1 +asgiref==3.8.1 +certifi==2024.8.30 +Django==5.1.4 +django-filter==24.3 +django-jazzmin==3.0.1 +django-jet==1.0.8 +et_xmlfile==2.0.0 +h11==0.14.0 +httpcore==1.0.7 +httpx==0.28.0 +idna==3.10 +numpy==2.1.3 +openpyxl==3.1.5 +pandas==2.2.3 +pillow==11.0.0 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +python-telegram-bot==21.8 +pytz==2024.2 +PyYAML==6.0.2 +six==1.17.0 +sniffio==1.3.1 +sqlparse==0.5.2 +tzdata==2024.2 diff --git a/touchh/__init__.py b/touchh/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/touchh/asgi.py b/touchh/asgi.py new file mode 100644 index 00000000..f8d90caa --- /dev/null +++ b/touchh/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for touchh project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'touchh.settings') + +application = get_asgi_application() diff --git a/touchh/settings.py b/touchh/settings.py new file mode 100644 index 00000000..a0c9dc57 --- /dev/null +++ b/touchh/settings.py @@ -0,0 +1,185 @@ +""" +Django settings for touchh project. + +Generated by 'django-admin startproject' using Django 5.1.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path + +import os + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-l_8uu8#p*^zf)9zry80)6u+!+2g1a4tg!wx7@^!uw(+^axyh&h' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['0.0.0.0', '192.168.219.140', '127.0.0.1', '192.168.219.114'] + + +# Application definition + +INSTALLED_APPS = [ + 'jazzmin', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'bot', + 'hotels', + 'users', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'touchh.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'touchh.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = 'ru-RU' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ +STATIC_ROOT = BASE_DIR / 'staticfiles' + +STATIC_URL = '/static/' +STATICFILES_DIRS = [BASE_DIR / 'static'] + + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +JAZZMIN_SETTINGS = { + "site_title": "Hotel Management", + "site_header": "Hotel Manager Admin", + "site_brand": "HotelPro", + "welcome_sign": "Welcome to Hotel Management System", + "show_sidebar": True, + "navigation_expanded": True, + "site_logo": None, # Путь к логотипу, например "static/images/logo.png" + "site_logo_classes": "img-circle", # Классы CSS для логотипа + "site_icon": None, # Иконка сайта (favicon), например "static/images/favicon.ico" + "welcome_sign": "Welcome to Touchh Admin", # Приветствие на странице входа + "copyright": "Touchh © 2024", # Кастомный текст в футере + "search_model": "auth.User", # Модель для строки поиска + "icons": { + "auth": "fas fa-users-cog", + "users": "fas fa-user-circle", + "hotels": "fas fa-hotel", + }, + "theme": "flatly", + "dark_mode_theme": "cyborg", + "footer": { + "copyright": "Touchh © 2024", + "version": False, + }, + "dashboard_links": [ + {"name": "Google", "url": "https://google.com", "new_window": True}, + ], + + "custom_links": { # Кастомные ссылки в боковом меню + "auth": [{ + "name": "Сбросить пароль", + "url": "reset_password", + "icon": "fas fa-key", + "permissions": ["auth.change_user"], + }], + }, + + # Пользовательский интерфейс + "show_sidebar": True, # Отображать боковую панель + "navigation_expanded": True, # Раскрывать меню по умолчанию + "hide_apps": [], # Список приложений, которые нужно скрыть + "hide_models": [], # Список моделей, которые нужно скрыть + "order_with_respect_to": ["auth", "users", "hotels"], # Порядок приложений + "icons": { # Иконки для приложений и моделей + "auth": "fas fa-users-cog", + "users": "fas fa-users", + "bot": "fas fa-robot", + "hotels": "fas fa-hotel", + }, +} + diff --git a/touchh/urls.py b/touchh/urls.py new file mode 100644 index 00000000..7aff8672 --- /dev/null +++ b/touchh/urls.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), +] \ No newline at end of file diff --git a/touchh/wsgi.py b/touchh/wsgi.py new file mode 100644 index 00000000..d5f0e3ee --- /dev/null +++ b/touchh/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for touchh project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'touchh.settings') + +application = get_wsgi_application() diff --git a/users/__init__.py b/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/users/admin.py b/users/admin.py new file mode 100644 index 00000000..3d03faec --- /dev/null +++ b/users/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin +from .models import User, UserConfirmation + +@admin.register(User) +class UserAdmin(admin.ModelAdmin): + list_display = ('username', 'telegram_id', 'chat_id', 'role', 'confirmed') + search_fields = ('username', 'telegram_id', 'chat_id') + list_filter = ('role', 'confirmed') + ordering = ('-id',) + +@admin.register(UserConfirmation) +class UserConfirmationAdmin(admin.ModelAdmin): + list_display = ('user', 'confirmation_code', 'created_at') + search_fields = ('user__username', 'confirmation_code') + list_filter = ('created_at',) diff --git a/users/apps.py b/users/apps.py new file mode 100644 index 00000000..72b14010 --- /dev/null +++ b/users/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'users' diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py new file mode 100644 index 00000000..67cccc82 --- /dev/null +++ b/users/migrations/0001_initial.py @@ -0,0 +1,59 @@ +# Generated by Django 5.1.4 on 2024-12-05 23:39 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.db.models.deletion +import django.utils.timezone +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + 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')), + ('telegram_id', models.BigIntegerField(blank=True, null=True, unique=True, verbose_name='Telegram ID')), + ('chat_id', models.BigIntegerField(blank=True, null=True, unique=True, verbose_name='Chat ID')), + ('role', models.CharField(choices=[('admin', 'Administrator'), ('hotel_user', 'Hotel User')], default='hotel_user', max_length=20, verbose_name='Role')), + ('confirmed', models.BooleanField(default=False, verbose_name='Confirmed')), + ('groups', models.ManyToManyField(blank=True, related_name='custom_user_set', to='auth.group')), + ('user_permissions', models.ManyToManyField(blank=True, related_name='custom_user_set', to='auth.permission')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='UserConfirmation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('confirmation_code', models.UUIDField(default=uuid.uuid4, verbose_name='Confirmation Code')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='User')), + ], + ), + ] diff --git a/users/migrations/__init__.py b/users/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/users/models.py b/users/models.py new file mode 100644 index 00000000..4760f274 --- /dev/null +++ b/users/models.py @@ -0,0 +1,51 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +import uuid + +class User(AbstractUser): + TELEGRAM_ROLES = [ + ('admin', 'Administrator'), + ('hotel_user', 'Hotel User'), + ] + + telegram_id = models.BigIntegerField( + unique=True, + null=True, + blank=True, + verbose_name="Telegram ID" + ) + chat_id = models.BigIntegerField( + unique=True, + null=True, + blank=True, + verbose_name="Chat ID" + ) + role = models.CharField( + max_length=20, + choices=TELEGRAM_ROLES, + default='hotel_user', + verbose_name="Role" + ) + confirmed = models.BooleanField(default=False, verbose_name="Confirmed") + + groups = models.ManyToManyField( + 'auth.Group', + related_name='custom_user_set', # Уникальное имя для обратной связи + blank=True + ) + user_permissions = models.ManyToManyField( + 'auth.Permission', + related_name='custom_user_set', # Уникальное имя для обратной связи + blank=True + ) + + def __str__(self): + return self.username or f"Telegram User {self.telegram_id}" + +class UserConfirmation(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="User") + confirmation_code = models.UUIDField(default=uuid.uuid4, verbose_name="Confirmation Code") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created At") + + def __str__(self): + return f"Confirmation for {self.user.username} - {self.confirmation_code}" \ No newline at end of file diff --git a/users/tests.py b/users/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/users/urls.py b/users/urls.py new file mode 100644 index 00000000..e69de29b diff --git a/users/views.py b/users/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/users/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.