From 22fb6a357f1b6b91f04a180604e2f992280ee661 Mon Sep 17 00:00:00 2001 From: clarkzjw Date: Tue, 21 Feb 2023 16:46:11 -0800 Subject: add django app template --- bot.py | 314 +++++++++++++++++++++++++++++++++++++++++ bot/__init__.py | 13 -- bot/admin.py | 5 + bot/apps.py | 6 + bot/bot.py | 300 --------------------------------------- bot/migrations/0001_initial.py | 44 ++++++ bot/migrations/__init__.py | 0 bot/models.py | 9 ++ bot/tests.py | 3 + bot/urls.py | 7 + bot/views.py | 5 + checkin/settings.py | 2 + checkin/urls.py | 3 +- 13 files changed, 397 insertions(+), 314 deletions(-) create mode 100644 bot.py create mode 100644 bot/admin.py create mode 100644 bot/apps.py delete mode 100644 bot/bot.py create mode 100644 bot/migrations/0001_initial.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 diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..c2e102d --- /dev/null +++ b/bot.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python +# pylint: disable=unused-argument, wrong-import-position +# This program is dedicated to the public domain under the CC0 license. + +import logging +import io +import telegram.constants +from telegram import __version__ as TG_VER + +try: + from telegram import __version_info__ +except ImportError: + __version_info__ = (0, 0, 0, 0, 0) # type: ignore[assignment] + +if __version_info__ < (20, 0, 0, "alpha", 1): + raise RuntimeError( + f"This example is not compatible with your current PTB version {TG_VER}. To view the " + f"{TG_VER} version of this example, " + f"visit https://docs.python-telegram-bot.org/en/v{TG_VER}/examples.html" + ) + +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ReplyKeyboardMarkup, KeyboardButton +from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes, MessageHandler, filters, ConversationHandler, CallbackContext +from ..config import BOT_TOKEN +from foursquare.poi import query_poi +from dbstore.dbm_store import get_loc +from toot import mastodon_client +from typing import TypedDict, List, cast + +scheduler = None +PRIVACY, TOOT = map(chr, range(8, 10)) + +WAIT_LOC, LOCATION, PHOTO, PROCESS_PHOTO, FINAL, SETTING = range(6) + +# Enable logging +logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO +) +logger = logging.getLogger(__name__) + +MAIN_MENU = ReplyKeyboardMarkup([ + [telegram.KeyboardButton(text="/check in", request_location=True)], + [telegram.KeyboardButton(text="/cancel")], + [telegram.KeyboardButton(text="/setting")] +]) + +SKIP_MENU = ReplyKeyboardMarkup([[telegram.KeyboardButton(text="/skip")]]) +SETTING_MENU = ReplyKeyboardMarkup( + [ + [KeyboardButton(text="/tos")], + [telegram.KeyboardButton(text="/back")], + ] +) + + +async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + hello = "Hello, this is `checkin.bot`. \n\n" \ + "This is a Telegram bot with functionality similar to Foursquare Swarm, " \ + "but check in and post your location to the Fediverse (Mastodon/Pleroma) instead of Twitter.\n\n" \ + "Aware of privacy concerns, this bot will not store your location data." \ + "*Be safe and cautious when sharing your real time location on the web.* \n\n" \ + "Start using this bot by sharing your location using Telegram context menu to it." + + await update.message.reply_text(hello, parse_mode=telegram.constants.ParseMode.MARKDOWN) + await update.message.reply_text("Use bot keyboard to choose an action", reply_markup=MAIN_MENU) + + return LOCATION + + +async def checkin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + keyboard = [] + + for poi in query_poi(update.message.location.latitude, update.message.location.longitude): + keyboard.append([ + InlineKeyboardButton(poi["name"], callback_data=poi["fsq_id"]), + ]) + + reply_markup = InlineKeyboardMarkup(keyboard) + await update.message.reply_text("Where are you?", reply_markup=reply_markup) + + return WAIT_LOC + + +async def process_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + query = update.callback_query + await query.answer() + print(query.data) + context.user_data["fsq_id"] = query.data + await query.delete_message() + + poi = get_loc(context.user_data["fsq_id"]) + media_id = [] + content = f"I'm at {poi['name']} in {poi['locality']}, {poi['region']}, {poi['osm_url']}" + status = mastodon_client.status_post( + content, + visibility="private", + media_ids=media_id) + + context.user_data["status_id"] = status["id"] + context.user_data["status_content"] = content + + print("status_id", context.user_data["status_id"]) + + await query.message.reply_text( + text=f"Selected place: {poi['name']}, `{query.data}`\nPosted to Mastodon: {status['url']}", + parse_mode=telegram.constants.ParseMode.MARKDOWN, + reply_markup=MAIN_MENU + ) + + await query.message.reply_text("You can continue attaching photos, or press skip to finish", reply_markup=SKIP_MENU) + return PHOTO + + +async def tos(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + await update.message.reply_text("TOS", reply_markup=MAIN_MENU) + + +async def setting(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + await update.message.reply_text("Setting", reply_markup=SETTING_MENU) + return SETTING + + +async def setting_process_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): + await update.message.reply_text("Setting Process Callback", reply_markup=SETTING_MENU) + return ConversationHandler.END + + +async def process_location(update: Update, context: ContextTypes.DEFAULT_TYPE): + await update.message.reply_chat_action(telegram.constants.ChatAction.TYPING) + + fsq_id = context.user_data["fsq_id"] + poi = get_loc(context.user_data["fsq_id"]) + media_id = [] + + if context.user_data.get("photo") is not None: + media = mastodon_client.media_post(context.user_data.get("photo"), mime_type="image/jpeg") + media_id = [media["id"]] + # else: + # photo_url = get_poi_top_photo(context.user_data["fsq_id"]) + # if photo_url is not None: + # with urllib.request.urlopen(photo_url) as response: + # data = response.read() + # media = mastodon_client.media_post(data, mime_type="image/jpeg") + # media_id = [media["id"]] + + mastodon_client.status_post( + f"I'm at {poi['name']} in {poi['locality']}, {poi['region']}, {poi['osm_url']}", + visibility="private", + media_ids=media_id) + + await update.message.delete_message() + return ConversationHandler.END + + +async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Displays info on how to use the bot.""" + await update.message.reply_text("Use /start to test this bot.") + + +async def setting_cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + """Cancels and ends the conversation.""" + user = update.message.from_user + logger.info("User %s canceled the conversation.", user.first_name) + await update.message.reply_text( + text="Setting canceled.", + # "Bye! I hope we can talk again some day.", + reply_markup=MAIN_MENU + ) + + return ConversationHandler.END + + +async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + """Cancels and ends the conversation.""" + user = update.message.from_user + logger.info("User %s canceled the conversation.", user.first_name) + await update.message.reply_text( + text="Canceled.", + # "Bye! I hope we can talk again some day.", + reply_markup=MAIN_MENU + ) + + return ConversationHandler.END + + +class MsgDict(TypedDict): + media_id: str + caption: str + status_id: int + content: str + chat_id: int + + +async def media_group_sender(context: CallbackContext): + context.job.data = cast(List[MsgDict], context.job.data) + + media_id = [] + chat_id = context.job.data[0].get("chat_id") + for msg_dict in context.job.data: + if len(media_id) >= 4: + print("Cannot attach more than 4 photos") + break + file = await context.bot.get_file(msg_dict.get("media_id")) + img = io.BytesIO() + await file.download_to_memory(img) + + img.seek(0) + + media = mastodon_client.media_post(img.read(), mime_type="image/jpeg") + media_id.append(media["id"]) + + mastodon_client.status_update( + status=msg_dict.get("content"), + id=msg_dict.get("status_id"), + media_ids=media_id) + + await context.bot.send_message(chat_id=chat_id, text="Done", + reply_markup=MAIN_MENU + ) + + +async def photo(update: Update, context: CallbackContext): + """Stores the photo and asks for a location.""" + global scheduler + await update.message.reply_chat_action(telegram.constants.ChatAction.TYPING) + + status_id = context.user_data["status_id"] + status_content = context.user_data["status_content"] + + message = update.effective_message + context.user_data["media"] = [] + if message.media_group_id: + media_id = message.photo[-1].file_id if message.photo else message.effective_attachment.file_id + msg_dict = { + "media_id": media_id, + "caption": message.caption_html, + "status_id": status_id, + "content": status_content, + "chat_id": message.chat_id, + } + jobs = context.job_queue.get_jobs_by_name(str(message.media_group_id)) + if jobs: + jobs[0].data.append(msg_dict) + else: + context.job_queue.run_once(callback=media_group_sender, when=5, data=[msg_dict], + name=str(message.media_group_id)) + else: + file = await update.message.effective_attachment[-1].get_file() + img = io.BytesIO() + await file.download_to_memory(img) + img.seek(0) + + media = mastodon_client.media_post(img.read(), mime_type="image/jpeg") + mastodon_client.status_update( + status=status_content, + id=status_id, + media_ids=media["id"]) + + await update.message.reply_text(text="Done", + reply_markup=MAIN_MENU + ) + + +async def skip_photo(update: Update, context: ContextTypes.DEFAULT_TYPE): + print(context.user_data) + await update.message.reply_text( + text="Done.", reply_markup=MAIN_MENU + ) + return ConversationHandler.END + + +def main() -> None: + application = Application.builder().token(BOT_TOKEN).build() + + checkin_handler = ConversationHandler( + entry_points=[ + CommandHandler("start", start), + MessageHandler(filters.LOCATION, checkin), + ], + states={ + LOCATION: [ + MessageHandler(filters.LOCATION, checkin), + ], + WAIT_LOC: [CallbackQueryHandler(process_callback)], + PHOTO: [MessageHandler(filters.PHOTO, photo), + CommandHandler("skip", skip_photo)], + }, + fallbacks=[CommandHandler("cancel", cancel)], + per_message=False, + allow_reentry=True, + ) + + setting_conv_handler = ConversationHandler( + entry_points=[CommandHandler("setting", setting)], + states={ + SETTING: [ + CallbackQueryHandler(setting_process_callback), + ], + }, + fallbacks=[CommandHandler("back", setting_cancel)], + per_message=False, + allow_reentry=True, + ) + + application.add_handler(CommandHandler("tos", tos)) + application.add_handler(setting_conv_handler, 2) + application.add_handler(checkin_handler, 1) + + # Run the bot until the user presses Ctrl-C + application.run_polling() + + +if __name__ == "__main__": + main() diff --git a/bot/__init__.py b/bot/__init__.py index 5e3c341..e69de29 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -1,13 +0,0 @@ -from telegram import __version__ as TG_VER - -try: - from telegram import __version_info__ -except ImportError: - __version_info__ = (0, 0, 0, 0, 0) # type: ignore[assignment] - -if __version_info__ < (20, 0, 0, "alpha", 1): - raise RuntimeError( - f"This example is not compatible with your current PTB version {TG_VER}. To view the " - f"{TG_VER} version of this example, " - f"visit https://docs.python-telegram-bot.org/en/v{TG_VER}/examples.html" - ) diff --git a/bot/admin.py b/bot/admin.py new file mode 100644 index 0000000..f91be8f --- /dev/null +++ b/bot/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import User + +admin.site.register(User, UserAdmin) diff --git a/bot/apps.py b/bot/apps.py new file mode 100644 index 0000000..1cd7ff2 --- /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 deleted file mode 100644 index 2710184..0000000 --- a/bot/bot.py +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=unused-argument, wrong-import-position -# This program is dedicated to the public domain under the CC0 license. - -import logging -import io -import telegram.constants -from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ReplyKeyboardMarkup, KeyboardButton -from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes, MessageHandler, filters, ConversationHandler, CallbackContext -from ..config import BOT_TOKEN -from foursquare.poi import query_poi -from dbstore.dbm_store import get_loc -from toot import mastodon_client -from typing import TypedDict, List, cast - -scheduler = None -PRIVACY, TOOT = map(chr, range(8, 10)) - -WAIT_LOC, LOCATION, PHOTO, PROCESS_PHOTO, FINAL, SETTING = range(6) - -# Enable logging -logging.basicConfig( - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO -) -logger = logging.getLogger(__name__) - -MAIN_MENU = ReplyKeyboardMarkup([ - [telegram.KeyboardButton(text="/check in", request_location=True)], - [telegram.KeyboardButton(text="/cancel")], - [telegram.KeyboardButton(text="/setting")] -]) - -SKIP_MENU = ReplyKeyboardMarkup([[telegram.KeyboardButton(text="/skip")]]) -SETTING_MENU = ReplyKeyboardMarkup( - [ - [KeyboardButton(text="/tos")], - [telegram.KeyboardButton(text="/back")], - ] -) - - -async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - hello = "Hello, this is `checkin.bot`. \n\n" \ - "This is a Telegram bot with functionality similar to Foursquare Swarm, " \ - "but check in and post your location to the Fediverse (Mastodon/Pleroma) instead of Twitter.\n\n" \ - "Aware of privacy concerns, this bot will not store your location data." \ - "*Be safe and cautious when sharing your real time location on the web.* \n\n" \ - "Start using this bot by sharing your location using Telegram context menu to it." - - await update.message.reply_text(hello, parse_mode=telegram.constants.ParseMode.MARKDOWN) - await update.message.reply_text("Use bot keyboard to choose an action", reply_markup=MAIN_MENU) - - return LOCATION - - -async def checkin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - keyboard = [] - - for poi in query_poi(update.message.location.latitude, update.message.location.longitude): - keyboard.append([ - InlineKeyboardButton(poi["name"], callback_data=poi["fsq_id"]), - ]) - - reply_markup = InlineKeyboardMarkup(keyboard) - await update.message.reply_text("Where are you?", reply_markup=reply_markup) - - return WAIT_LOC - - -async def process_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - query = update.callback_query - await query.answer() - print(query.data) - context.user_data["fsq_id"] = query.data - await query.delete_message() - - poi = get_loc(context.user_data["fsq_id"]) - media_id = [] - content = f"I'm at {poi['name']} in {poi['locality']}, {poi['region']}, {poi['osm_url']}" - status = mastodon_client.status_post( - content, - visibility="private", - media_ids=media_id) - - context.user_data["status_id"] = status["id"] - context.user_data["status_content"] = content - - print("status_id", context.user_data["status_id"]) - - await query.message.reply_text( - text=f"Selected place: {poi['name']}, `{query.data}`\nPosted to Mastodon: {status['url']}", - parse_mode=telegram.constants.ParseMode.MARKDOWN, - reply_markup=MAIN_MENU - ) - - await query.message.reply_text("You can continue attaching photos, or press skip to finish", reply_markup=SKIP_MENU) - return PHOTO - - -async def tos(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - await update.message.reply_text("TOS", reply_markup=MAIN_MENU) - - -async def setting(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - await update.message.reply_text("Setting", reply_markup=SETTING_MENU) - return SETTING - - -async def setting_process_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): - await update.message.reply_text("Setting Process Callback", reply_markup=SETTING_MENU) - return ConversationHandler.END - - -async def process_location(update: Update, context: ContextTypes.DEFAULT_TYPE): - await update.message.reply_chat_action(telegram.constants.ChatAction.TYPING) - - fsq_id = context.user_data["fsq_id"] - poi = get_loc(context.user_data["fsq_id"]) - media_id = [] - - if context.user_data.get("photo") is not None: - media = mastodon_client.media_post(context.user_data.get("photo"), mime_type="image/jpeg") - media_id = [media["id"]] - # else: - # photo_url = get_poi_top_photo(context.user_data["fsq_id"]) - # if photo_url is not None: - # with urllib.request.urlopen(photo_url) as response: - # data = response.read() - # media = mastodon_client.media_post(data, mime_type="image/jpeg") - # media_id = [media["id"]] - - mastodon_client.status_post( - f"I'm at {poi['name']} in {poi['locality']}, {poi['region']}, {poi['osm_url']}", - visibility="private", - media_ids=media_id) - - await update.message.delete_message() - return ConversationHandler.END - - -async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Displays info on how to use the bot.""" - await update.message.reply_text("Use /start to test this bot.") - - -async def setting_cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """Cancels and ends the conversation.""" - user = update.message.from_user - logger.info("User %s canceled the conversation.", user.first_name) - await update.message.reply_text( - text="Setting canceled.", - # "Bye! I hope we can talk again some day.", - reply_markup=MAIN_MENU - ) - - return ConversationHandler.END - - -async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """Cancels and ends the conversation.""" - user = update.message.from_user - logger.info("User %s canceled the conversation.", user.first_name) - await update.message.reply_text( - text="Canceled.", - # "Bye! I hope we can talk again some day.", - reply_markup=MAIN_MENU - ) - - return ConversationHandler.END - - -class MsgDict(TypedDict): - media_id: str - caption: str - status_id: int - content: str - chat_id: int - - -async def media_group_sender(context: CallbackContext): - context.job.data = cast(List[MsgDict], context.job.data) - - media_id = [] - chat_id = context.job.data[0].get("chat_id") - for msg_dict in context.job.data: - if len(media_id) >= 4: - print("Cannot attach more than 4 photos") - break - file = await context.bot.get_file(msg_dict.get("media_id")) - img = io.BytesIO() - await file.download_to_memory(img) - - img.seek(0) - - media = mastodon_client.media_post(img.read(), mime_type="image/jpeg") - media_id.append(media["id"]) - - mastodon_client.status_update( - status=msg_dict.get("content"), - id=msg_dict.get("status_id"), - media_ids=media_id) - - await context.bot.send_message(chat_id=chat_id, text="Done", - reply_markup=MAIN_MENU - ) - - -async def photo(update: Update, context: CallbackContext): - """Stores the photo and asks for a location.""" - global scheduler - await update.message.reply_chat_action(telegram.constants.ChatAction.TYPING) - - status_id = context.user_data["status_id"] - status_content = context.user_data["status_content"] - - message = update.effective_message - context.user_data["media"] = [] - if message.media_group_id: - media_id = message.photo[-1].file_id if message.photo else message.effective_attachment.file_id - msg_dict = { - "media_id": media_id, - "caption": message.caption_html, - "status_id": status_id, - "content": status_content, - "chat_id": message.chat_id, - } - jobs = context.job_queue.get_jobs_by_name(str(message.media_group_id)) - if jobs: - jobs[0].data.append(msg_dict) - else: - context.job_queue.run_once(callback=media_group_sender, when=5, data=[msg_dict], - name=str(message.media_group_id)) - else: - file = await update.message.effective_attachment[-1].get_file() - img = io.BytesIO() - await file.download_to_memory(img) - img.seek(0) - - media = mastodon_client.media_post(img.read(), mime_type="image/jpeg") - mastodon_client.status_update( - status=status_content, - id=status_id, - media_ids=media["id"]) - - await update.message.reply_text(text="Done", - reply_markup=MAIN_MENU - ) - - -async def skip_photo(update: Update, context: ContextTypes.DEFAULT_TYPE): - print(context.user_data) - await update.message.reply_text( - text="Done.", reply_markup=MAIN_MENU - ) - return ConversationHandler.END - - -def main() -> None: - application = Application.builder().token(BOT_TOKEN).build() - - checkin_handler = ConversationHandler( - entry_points=[ - CommandHandler("start", start), - MessageHandler(filters.LOCATION, checkin), - ], - states={ - LOCATION: [ - MessageHandler(filters.LOCATION, checkin), - ], - WAIT_LOC: [CallbackQueryHandler(process_callback)], - PHOTO: [MessageHandler(filters.PHOTO, photo), - CommandHandler("skip", skip_photo)], - }, - fallbacks=[CommandHandler("cancel", cancel)], - per_message=False, - allow_reentry=True, - ) - - setting_conv_handler = ConversationHandler( - entry_points=[CommandHandler("setting", setting)], - states={ - SETTING: [ - CallbackQueryHandler(setting_process_callback), - ], - }, - fallbacks=[CommandHandler("back", setting_cancel)], - per_message=False, - allow_reentry=True, - ) - - application.add_handler(CommandHandler("tos", tos)) - application.add_handler(setting_conv_handler, 2) - application.add_handler(checkin_handler, 1) - - # Run the bot until the user presses Ctrl-C - application.run_polling() - - -if __name__ == "__main__": - main() diff --git a/bot/migrations/0001_initial.py b/bot/migrations/0001_initial.py new file mode 100644 index 0000000..6b4f210 --- /dev/null +++ b/bot/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 4.1.7 on 2023-02-22 00:44 + +import django.contrib.auth.models +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('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')), + ('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')), + ('name', models.CharField(blank=True, max_length=50)), + ('telegram_id', models.PositiveBigIntegerField(primary_key=True, serialize=False, unique=True)), + ('username', models.CharField(max_length=32)), + ('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': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/bot/migrations/__init__.py b/bot/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/models.py b/bot/models.py new file mode 100644 index 0000000..c6df3e7 --- /dev/null +++ b/bot/models.py @@ -0,0 +1,9 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models + + +class User(AbstractUser): + name = models.CharField(max_length=50, blank=True) + telegram_id = models.PositiveBigIntegerField(unique=True, primary_key=True) + username = models.CharField(max_length=32, blank=False) + USERNAME_FIELD = 'telegram_id' diff --git a/bot/tests.py b/bot/tests.py new file mode 100644 index 0000000..7ce503c --- /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 0000000..88a9cac --- /dev/null +++ b/bot/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path('', views.index, name='index'), +] diff --git a/bot/views.py b/bot/views.py new file mode 100644 index 0000000..963b6f7 --- /dev/null +++ b/bot/views.py @@ -0,0 +1,5 @@ +from django.http import HttpResponse + + +def index(request): + return HttpResponse("Hello, world. You're at the polls index.") diff --git a/checkin/settings.py b/checkin/settings.py index 6fa12a2..8d74770 100644 --- a/checkin/settings.py +++ b/checkin/settings.py @@ -31,6 +31,7 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ + 'bot.apps.BotConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -99,6 +100,7 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +AUTH_USER_MODEL = 'bot.User' # Internationalization # https://docs.djangoproject.com/en/4.1/topics/i18n/ diff --git a/checkin/urls.py b/checkin/urls.py index a173748..4f9cbe8 100644 --- a/checkin/urls.py +++ b/checkin/urls.py @@ -14,8 +14,9 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ + path('bot/', include('bot.urls')), path('admin/', admin.site.urls), ] -- cgit v1.2.3