We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Переписать интеграцию с телеграмом
Сейчас используем какой-то [мутный сервис](https://github.com/f213/education-backend/blob/master/src/app/integrations/tg.py#L4) для отправки сообщения в телегу. Так вышло потому, что когда мы запускались, API телеграма было раскомнадзорено.

Надо отказаться от использования этого сервиса. Заодно, сделать возможность указывать отдельные каналы уведомлений о новых заказах для разных курсов.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/app/integrations/tg.py`
Content:
```
1 import requests
2 
3 
4 def send_happiness_message(text):
5     response = requests.post('https://timepad.f213.in/msg/', json={
6         'text': text,
7     })
8 
9     assert response.status_code == 200, 'TG proxy should return 200'
10     assert response.json()['ok'] is True, 'TG proxy should say msg is ok'
11
```

Path: `src/app/settings.py`
Content:
```
1 import environ
2 import os
3 from celery.schedules import crontab
4 
5 root = environ.Path(__file__) - 2        # three folder back (/a/b/c/ - 3 = /)
6 env = environ.Env(DEBUG=(bool, False))  # set default values and casting
7 environ.Env.read_env()                   # reading .env file
8 SITE_ROOT = root()
9 
10 USE_L10N = True
11 USE_i18N = True
12 
13 LANGUAGE_CODE = 'ru'
14 LOCALE_PATHS = ['locale']
15 
16 INTERNAL_IPS = [
17     '127.0.0.1',
18 ]
19 FRONTEND_URL = 'https://education.borshev.com'
20 
21 USE_TZ = False
22 TIME_ZONE = env('TIME_ZONE', cast=str, default='Europe/Moscow')
23 
24 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
25 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26 TEST_RUNNER = 'app.test.disable_test_command_runner.DisableTestCommandRunner'
27 
28 
29 # Quick-start development settings - unsuitable for production
30 # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
31 
32 # SECURITY WARNING: keep the secret key used in production secret!
33 SECRET_KEY = env('SECRET_KEY', cast=str, default='s3cr3t')
34 
35 # SECURITY WARNING: don't run with debug turned on in production!
36 DEBUG = env('DEBUG', cast=bool, default=False)
37 CI = env('CI', cast=bool, default=False)
38 ANONYMIZE_ENABLED = DEBUG
39 
40 ABSOLUTE_HOST = env('ABSOLUTE_HOST', cast=str, default='https://edu-app.borshev.com')
41 ALLOWED_HOSTS = [
42     'edu-app.borshev.com',
43     'localhost',
44     'localhost:8000',
45     'education.borshev.com',
46     ABSOLUTE_HOST.replace('https://', ''),
47 ]
48 
49 CORS_ORIGIN_WHITELIST = [
50     'https://pmdaily.ru',
51     'https://education.borshev.com',
52 ]
53 
54 CSRF_TRUSTED_ORIGINS = [
55     'pmdaily.ru',
56     'education.borshev.com',
57     'borshev.com',
58 ]
59 
60 
61 # Application definition
62 
63 INSTALLED_APPS = [
64     'app',
65     'users',
66     'orders',
67     'products',
68     'shipping',
69     'tinkoff',
70     'triggers',
71     'magnets',
72     'banking',
73 
74     'corsheaders',
75     'hattori',
76     'anymail',
77     'rest_framework',
78     'rest_framework.authtoken',
79     'drf_recaptcha',
80     'django_filters',
81 
82     'axes',
83     'django.contrib.admin',
84     'django.contrib.auth',
85     'django.contrib.contenttypes',
86     'django.contrib.sessions',
87     'django.contrib.messages',
88     'django.contrib.staticfiles',
89 
90     'debug_toolbar',
91 ]
92 
93 MIDDLEWARE = [
94     'django.middleware.security.SecurityMiddleware',
95 
96     'django.contrib.sessions.middleware.SessionMiddleware',
97     'corsheaders.middleware.CorsMiddleware',
98     'django.middleware.common.CommonMiddleware',
99     # 'django.middleware.csrf.CsrfViewMiddleware',
100     'django.contrib.auth.middleware.AuthenticationMiddleware',
101     'django.contrib.auth.middleware.RemoteUserMiddleware',
102     'django.contrib.messages.middleware.MessageMiddleware',
103     'django.middleware.clickjacking.XFrameOptionsMiddleware',
104     'app.middleware.real_ip.real_ip_middleware',
105     'axes.middleware.AxesMiddleware',
106     'debug_toolbar.middleware.DebugToolbarMiddleware',
107 ]
108 
109 if not DEBUG and not CI:
110     MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
111 
112 
113 REST_FRAMEWORK = {
114     'DEFAULT_PERMISSION_CLASSES': (
115         'rest_framework.permissions.IsAuthenticated',
116     ),
117     'DEFAULT_AUTHENTICATION_CLASSES': (
118         'rest_framework.authentication.TokenAuthentication',
119     ),
120     'DEFAULT_RENDERER_CLASSES': [
121         'app.renderers.AppJSONRenderer',
122     ],
123     'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
124     'DEFAULT_PAGINATION_CLASS': 'app.pagination.AppPagination',
125     'PAGE_SIZE': 20,
126 }
127 
128 ROOT_URLCONF = 'app.urls'
129 
130 TEMPLATES = [
131     {
132         'BACKEND': 'django.template.backends.django.DjangoTemplates',
133         'DIRS': [],
134         'APP_DIRS': True,
135         'OPTIONS': {
136             'context_processors': [
137                 'django.template.context_processors.debug',
138                 'django.template.context_processors.request',
139                 'django.contrib.auth.context_processors.auth',
140                 'django.contrib.messages.context_processors.messages',
141             ],
142         },
143     },
144 ]
145 
146 WSGI_APPLICATION = 'app.wsgi.application'
147 
148 
149 # Database
150 # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
151 DATABASES = {
152     'default': env.db(),    # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
153 }
154 AUTH_USER_MODEL = 'users.User'
155 AUTHENTICATION_BACKENDS = [
156     'axes.backends.AxesBackend',
157     'django.contrib.auth.backends.ModelBackend',
158     'django.contrib.auth.backends.RemoteUserBackend',
159 ]
160 
161 HEALTH_CHECKS_ERROR_CODE = 503
162 HEALTH_CHECKS = {
163     'db': 'django_healthchecks.contrib.check_database',
164 }
165 
166 MEDIA_URL = env('MEDIA_URL', default='/media/')
167 
168 STATIC_URL = env('STATIC_URL', default='/static/')
169 STATIC_ROOT = env('STATIC_ROOT')
170 
171 SENTRY_DSN = env('SENTRY_DSN', cast=str, default='')
172 
173 if not DEBUG and SENTRY_DSN:
174     import sentry_sdk
175     from sentry_sdk.integrations.celery import CeleryIntegration
176     from sentry_sdk.integrations.django import DjangoIntegration
177     from sentry_sdk.integrations.redis import RedisIntegration
178 
179     sentry_sdk.init(
180         dsn=SENTRY_DSN,
181         integrations=[DjangoIntegration(), CeleryIntegration(), RedisIntegration()],
182     )
183 
184 BROKER_URL = env('CELERY_BACKEND')
185 CELERY_ALWAYS_EAGER = env('CELERY_ALWAYS_EAGER', cast=bool, default=DEBUG)  # by default in debug mode we run all celery tasks in foregroud.
186 CELERY_TIMEZONE = TIME_ZONE
187 CELERY_ENABLE_UTC = False
188 CELERYBEAT_SCHEDULE = {
189     'run_started_purchase_trigger': {
190         'task': 'triggers.tasks.check_for_started_purchase_triggers',
191         'schedule': crontab(hour='*', minute=15),
192     },
193     'run_record_feedback_trigger': {
194         'task': 'triggers.tasks.check_for_record_feedback_triggers',
195         'schedule': crontab(hour='*', minute=15),
196     },
197     'ship_unshipped_orders': {
198         'task': 'orders.tasks.ship_unshipped_orders',
199         'schedule': crontab(hour='*', minute='*/2'),
200     },
201 }
202 
203 
204 AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID', default=None)
205 AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY', default=None)
206 AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME', default=None)
207 AWS_S3_REGION_NAME = env('AWS_S3_REGION_NAME', default=None)
208 AWS_S3_ENDPOINT_URL = env('AWS_S3_ENDPOINT_URL', default=None)
209 
210 EMAIL_ENABLED = env('EMAIL_ENABLED', cast=bool, default=False)
211 
212 EMAIL_BACKEND = env('EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend')
213 
214 MAILCHIMP_API_KEY = env('MAILCHIMP_API_KEY', default='')
215 MAILCHIMP_CONTACT_LIST_ID = env('MAILCHIMP_CONTACT_LIST_ID', cast=str, default=None)
216 
217 DEFAULT_FROM_EMAIL = env('EMAIL_FROM', cast=str, default='')
218 ANYMAIL = {
219     'POSTMARK_SERVER_TOKEN': env('POSTMARK_SERVER_TOKEN', cast=str, default=''),
220     'DEBUG_API_REQUESTS': env('DEBUG'),
221 }
222 
223 CLICKMEETING_API_KEY = env('CLICKMEETING_API_KEY', default=None, cast=str)
224 
225 ZOOMUS_API_KEY = env('ZOOMUS_API_KEY', default=None, cast=str)
226 ZOOMUS_API_SECRET = env('ZOOMUS_API_SECRET', default=None, cast=str)
227 
228 TINKOFF_TERMINAL_KEY = env('TINKOFF_TERMINAL_KEY', default=None)
229 TINKOFF_TERMINAL_PASSWORD = env('TINKOFF_TERMINAL_PASSWORD', default=None)
230 TINKOFF_CREDIT_SHOP_ID = env('TINKOFF_CREDIT_SHOP_ID', default=None)
231 TINKOFF_CREDIT_SHOWCASE_ID = env('TINKOFF_CREDIT_SHOWCASE_ID', default=None)
232 
233 TINKOFF_CREDIT_DEMO_MODE = env('TINKOFF_CREDIT_DEMO_MODE', default=DEBUG)
234 
235 SEND_HAPPINESS_MESSAGES = env('SEND_HAPPINESS_MESSAGES', cast=bool, default=False)
236 
237 DRF_RECAPTCHA_SECRET_KEY = env('RECAPTCHA_SECRET_KEY', cast=str, default='')
238 DRF_RECAPTCHA_TESTING = DRF_RECAPTCHA_TESTING_PASS = not env('RECAPTCHA_ENABLED', cast=bool, default=True)
239
```

Path: `src/orders/services/order_shipper.py`
Content:
```
1 from django.conf import settings
2 from django.utils import timezone
3 
4 from app.tasks import send_happiness_message, send_mail
5 from orders.models import Order
6 
7 
8 class Pigwidgeon:
9     """Ship the order (actualy calls item ship() method)"""
10     def __init__(self, order: Order, silent: bool = False):
11         self.order = order
12         self.silent = silent
13 
14     def __call__(self):
15         if self.ship():
16             self.mark_order_as_shipped()
17 
18         if not self.order.notification_to_giver_is_sent:
19             self.send_notification_to_giver()
20 
21         if not self.silent:
22             self.send_happiness_message()
23 
24     def ship(self) -> bool:
25         """Ship the order. Returns true if order is shipped"""
26         desired_date = self.order.desired_shipment_date
27         if desired_date is None or desired_date <= timezone.now():
28             self.order.item.ship(to=self.order.user, order=self.order)
29 
30             return True
31 
32         return False
33 
34     def mark_order_as_shipped(self):
35         self.order.shipped = timezone.now()
36         self.order.save()
37 
38     def send_happiness_message(self):
39         if not settings.SEND_HAPPINESS_MESSAGES:
40             return
41 
42         send_happiness_message.delay(text='💰+{sum} ₽, {user}, {reason}'.format(
43             sum=str(self.order.price).replace('.00', ''),
44             user=str(self.order.user),
45             reason=str(self.order.item) if self.order.giver is None else f'{self.order.item} (подарок)',
46         ))
47 
48     def send_notification_to_giver(self):
49         if self.order.giver is None:
50             return
51 
52         if self.order.desired_shipment_date is None:
53             return
54 
55         send_mail.delay(
56             to=self.order.giver.email,
57             template_id='gift-notification-for-giver',  # postmark
58             disable_antispam=True,
59             ctx={
60                 'item_name': self.order.item.full_name,
61                 'receiver_name': str(self.order.user),
62                 'receiver_email': self.order.user.email,
63                 'desired_shipment_date': self.order.desired_shipment_date.strftime('%d.%m.%Y'),
64             },
65         )
66 
67         self.order.notification_to_giver_is_sent = True
68         self.order.save()
69
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/app/integrations/tg.py b/src/app/integrations/tg.py
--- a/src/app/integrations/tg.py
+++ b/src/app/integrations/tg.py
@@ -1,10 +1,18 @@
 import requests
+from django.conf import settings
 
 
-def send_happiness_message(text):
-    response = requests.post('https://timepad.f213.in/msg/', json={
+def send_message(channel, text):
+    url = f'https://api.telegram.org/bot{settings.BOT_TOKEN}/sendMessage'
+    response = requests.post(url, data={
+        'chat_id': channel,
         'text': text,
+        'parse_mode': 'markdown',
+        'disable_web_page_preview': True,
     })
 
-    assert response.status_code == 200, 'TG proxy should return 200'
-    assert response.json()['ok'] is True, 'TG proxy should say msg is ok'
+    assert response.status_code == 200, 'TG should return 200'
+
+
+def send_happiness_message(text):
+    send_message(settings.HAPPINESS_MESSAGES_CHAT_ID, text)
diff --git a/src/app/settings.py b/src/app/settings.py
--- a/src/app/settings.py
+++ b/src/app/settings.py
@@ -232,7 +232,8 @@
 
 TINKOFF_CREDIT_DEMO_MODE = env('TINKOFF_CREDIT_DEMO_MODE', default=DEBUG)
 
-SEND_HAPPINESS_MESSAGES = env('SEND_HAPPINESS_MESSAGES', cast=bool, default=False)
+BOT_TOKEN = env('BOT_TOKEN', cast=str, default=None)
+HAPPINESS_MESSAGES_CHAT_ID = env('HAPPINESS_MESSAGES_CHAT_ID', cast=str, default=None)
 
 DRF_RECAPTCHA_SECRET_KEY = env('RECAPTCHA_SECRET_KEY', cast=str, default='')
 DRF_RECAPTCHA_TESTING = DRF_RECAPTCHA_TESTING_PASS = not env('RECAPTCHA_ENABLED', cast=bool, default=True)
diff --git a/src/orders/services/order_shipper.py b/src/orders/services/order_shipper.py
--- a/src/orders/services/order_shipper.py
+++ b/src/orders/services/order_shipper.py
@@ -36,7 +36,7 @@
         self.order.save()
 
     def send_happiness_message(self):
-        if not settings.SEND_HAPPINESS_MESSAGES:
+        if not settings.HAPPINESS_MESSAGES_CHAT_ID:
             return
 
         send_happiness_message.delay(text='💰+{sum} ₽, {user}, {reason}'.format(

{"golden_diff": "diff --git a/src/app/integrations/tg.py b/src/app/integrations/tg.py\n--- a/src/app/integrations/tg.py\n+++ b/src/app/integrations/tg.py\n@@ -1,10 +1,18 @@\n import requests\n+from django.conf import settings\n \n \n-def send_happiness_message(text):\n-    response = requests.post('https://timepad.f213.in/msg/', json={\n+def send_message(channel, text):\n+    url = f'https://api.telegram.org/bot{settings.BOT_TOKEN}/sendMessage'\n+    response = requests.post(url, data={\n+        'chat_id': channel,\n         'text': text,\n+        'parse_mode': 'markdown',\n+        'disable_web_page_preview': True,\n     })\n \n-    assert response.status_code == 200, 'TG proxy should return 200'\n-    assert response.json()['ok'] is True, 'TG proxy should say msg is ok'\n+    assert response.status_code == 200, 'TG should return 200'\n+\n+\n+def send_happiness_message(text):\n+    send_message(settings.HAPPINESS_MESSAGES_CHAT_ID, text)\ndiff --git a/src/app/settings.py b/src/app/settings.py\n--- a/src/app/settings.py\n+++ b/src/app/settings.py\n@@ -232,7 +232,8 @@\n \n TINKOFF_CREDIT_DEMO_MODE = env('TINKOFF_CREDIT_DEMO_MODE', default=DEBUG)\n \n-SEND_HAPPINESS_MESSAGES = env('SEND_HAPPINESS_MESSAGES', cast=bool, default=False)\n+BOT_TOKEN = env('BOT_TOKEN', cast=str, default=None)\n+HAPPINESS_MESSAGES_CHAT_ID = env('HAPPINESS_MESSAGES_CHAT_ID', cast=str, default=None)\n \n DRF_RECAPTCHA_SECRET_KEY = env('RECAPTCHA_SECRET_KEY', cast=str, default='')\n DRF_RECAPTCHA_TESTING = DRF_RECAPTCHA_TESTING_PASS = not env('RECAPTCHA_ENABLED', cast=bool, default=True)\ndiff --git a/src/orders/services/order_shipper.py b/src/orders/services/order_shipper.py\n--- a/src/orders/services/order_shipper.py\n+++ b/src/orders/services/order_shipper.py\n@@ -36,7 +36,7 @@\n         self.order.save()\n \n     def send_happiness_message(self):\n-        if not settings.SEND_HAPPINESS_MESSAGES:\n+        if not settings.HAPPINESS_MESSAGES_CHAT_ID:\n             return\n \n         send_happiness_message.delay(text='\ud83d\udcb0+{sum} \u20bd, {user}, {reason}'.format(\n", "issue": "\u041f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0441 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043e\u043c\n\u0421\u0435\u0439\u0447\u0430\u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043a\u0430\u043a\u043e\u0439-\u0442\u043e [\u043c\u0443\u0442\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441](https://github.com/f213/education-backend/blob/master/src/app/integrations/tg.py#L4) \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0442\u0435\u043b\u0435\u0433\u0443. \u0422\u0430\u043a \u0432\u044b\u0448\u043b\u043e \u043f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0438\u0441\u044c, API \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u0430 \u0431\u044b\u043b\u043e \u0440\u0430\u0441\u043a\u043e\u043c\u043d\u0430\u0434\u0437\u043e\u0440\u0435\u043d\u043e.\r\n\r\n\u041d\u0430\u0434\u043e \u043e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u0417\u0430\u043e\u0434\u043d\u043e, \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u0430\u043d\u0430\u043b\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u043e \u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u0430\u0445 \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u043a\u0443\u0440\u0441\u043e\u0432.\n", "before_files": [{"content": "import requests\n\n\ndef send_happiness_message(text):\n    response = requests.post('https://timepad.f213.in/msg/', json={\n        'text': text,\n    })\n\n    assert response.status_code == 200, 'TG proxy should return 200'\n    assert response.json()['ok'] is True, 'TG proxy should say msg is ok'\n", "path": "src/app/integrations/tg.py"}, {"content": "import environ\nimport os\nfrom celery.schedules import crontab\n\nroot = environ.Path(__file__) - 2        # three folder back (/a/b/c/ - 3 = /)\nenv = environ.Env(DEBUG=(bool, False))  # set default values and casting\nenviron.Env.read_env()                   # reading .env file\nSITE_ROOT = root()\n\nUSE_L10N = True\nUSE_i18N = True\n\nLANGUAGE_CODE = 'ru'\nLOCALE_PATHS = ['locale']\n\nINTERNAL_IPS = [\n    '127.0.0.1',\n]\nFRONTEND_URL = 'https://education.borshev.com'\n\nUSE_TZ = False\nTIME_ZONE = env('TIME_ZONE', cast=str, default='Europe/Moscow')\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\nTEST_RUNNER = 'app.test.disable_test_command_runner.DisableTestCommandRunner'\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = env('SECRET_KEY', cast=str, default='s3cr3t')\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = env('DEBUG', cast=bool, default=False)\nCI = env('CI', cast=bool, default=False)\nANONYMIZE_ENABLED = DEBUG\n\nABSOLUTE_HOST = env('ABSOLUTE_HOST', cast=str, default='https://edu-app.borshev.com')\nALLOWED_HOSTS = [\n    'edu-app.borshev.com',\n    'localhost',\n    'localhost:8000',\n    'education.borshev.com',\n    ABSOLUTE_HOST.replace('https://', ''),\n]\n\nCORS_ORIGIN_WHITELIST = [\n    'https://pmdaily.ru',\n    'https://education.borshev.com',\n]\n\nCSRF_TRUSTED_ORIGINS = [\n    'pmdaily.ru',\n    'education.borshev.com',\n    'borshev.com',\n]\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    'app',\n    'users',\n    'orders',\n    'products',\n    'shipping',\n    'tinkoff',\n    'triggers',\n    'magnets',\n    'banking',\n\n    'corsheaders',\n    'hattori',\n    'anymail',\n    'rest_framework',\n    'rest_framework.authtoken',\n    'drf_recaptcha',\n    'django_filters',\n\n    'axes',\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n\n    'debug_toolbar',\n]\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'corsheaders.middleware.CorsMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    # 'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.auth.middleware.RemoteUserMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n    'app.middleware.real_ip.real_ip_middleware',\n    'axes.middleware.AxesMiddleware',\n    'debug_toolbar.middleware.DebugToolbarMiddleware',\n]\n\nif not DEBUG and not CI:\n    MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')\n\n\nREST_FRAMEWORK = {\n    'DEFAULT_PERMISSION_CLASSES': (\n        'rest_framework.permissions.IsAuthenticated',\n    ),\n    'DEFAULT_AUTHENTICATION_CLASSES': (\n        'rest_framework.authentication.TokenAuthentication',\n    ),\n    'DEFAULT_RENDERER_CLASSES': [\n        'app.renderers.AppJSONRenderer',\n    ],\n    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',\n    'DEFAULT_PAGINATION_CLASS': 'app.pagination.AppPagination',\n    'PAGE_SIZE': 20,\n}\n\nROOT_URLCONF = 'app.urls'\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = 'app.wsgi.application'\n\n\n# Database\n# https://docs.djangoproject.com/en/2.2/ref/settings/#databases\nDATABASES = {\n    'default': env.db(),    # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ\n}\nAUTH_USER_MODEL = 'users.User'\nAUTHENTICATION_BACKENDS = [\n    'axes.backends.AxesBackend',\n    'django.contrib.auth.backends.ModelBackend',\n    'django.contrib.auth.backends.RemoteUserBackend',\n]\n\nHEALTH_CHECKS_ERROR_CODE = 503\nHEALTH_CHECKS = {\n    'db': 'django_healthchecks.contrib.check_database',\n}\n\nMEDIA_URL = env('MEDIA_URL', default='/media/')\n\nSTATIC_URL = env('STATIC_URL', default='/static/')\nSTATIC_ROOT = env('STATIC_ROOT')\n\nSENTRY_DSN = env('SENTRY_DSN', cast=str, default='')\n\nif not DEBUG and SENTRY_DSN:\n    import sentry_sdk\n    from sentry_sdk.integrations.celery import CeleryIntegration\n    from sentry_sdk.integrations.django import DjangoIntegration\n    from sentry_sdk.integrations.redis import RedisIntegration\n\n    sentry_sdk.init(\n        dsn=SENTRY_DSN,\n        integrations=[DjangoIntegration(), CeleryIntegration(), RedisIntegration()],\n    )\n\nBROKER_URL = env('CELERY_BACKEND')\nCELERY_ALWAYS_EAGER = env('CELERY_ALWAYS_EAGER', cast=bool, default=DEBUG)  # by default in debug mode we run all celery tasks in foregroud.\nCELERY_TIMEZONE = TIME_ZONE\nCELERY_ENABLE_UTC = False\nCELERYBEAT_SCHEDULE = {\n    'run_started_purchase_trigger': {\n        'task': 'triggers.tasks.check_for_started_purchase_triggers',\n        'schedule': crontab(hour='*', minute=15),\n    },\n    'run_record_feedback_trigger': {\n        'task': 'triggers.tasks.check_for_record_feedback_triggers',\n        'schedule': crontab(hour='*', minute=15),\n    },\n    'ship_unshipped_orders': {\n        'task': 'orders.tasks.ship_unshipped_orders',\n        'schedule': crontab(hour='*', minute='*/2'),\n    },\n}\n\n\nAWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID', default=None)\nAWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY', default=None)\nAWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME', default=None)\nAWS_S3_REGION_NAME = env('AWS_S3_REGION_NAME', default=None)\nAWS_S3_ENDPOINT_URL = env('AWS_S3_ENDPOINT_URL', default=None)\n\nEMAIL_ENABLED = env('EMAIL_ENABLED', cast=bool, default=False)\n\nEMAIL_BACKEND = env('EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend')\n\nMAILCHIMP_API_KEY = env('MAILCHIMP_API_KEY', default='')\nMAILCHIMP_CONTACT_LIST_ID = env('MAILCHIMP_CONTACT_LIST_ID', cast=str, default=None)\n\nDEFAULT_FROM_EMAIL = env('EMAIL_FROM', cast=str, default='')\nANYMAIL = {\n    'POSTMARK_SERVER_TOKEN': env('POSTMARK_SERVER_TOKEN', cast=str, default=''),\n    'DEBUG_API_REQUESTS': env('DEBUG'),\n}\n\nCLICKMEETING_API_KEY = env('CLICKMEETING_API_KEY', default=None, cast=str)\n\nZOOMUS_API_KEY = env('ZOOMUS_API_KEY', default=None, cast=str)\nZOOMUS_API_SECRET = env('ZOOMUS_API_SECRET', default=None, cast=str)\n\nTINKOFF_TERMINAL_KEY = env('TINKOFF_TERMINAL_KEY', default=None)\nTINKOFF_TERMINAL_PASSWORD = env('TINKOFF_TERMINAL_PASSWORD', default=None)\nTINKOFF_CREDIT_SHOP_ID = env('TINKOFF_CREDIT_SHOP_ID', default=None)\nTINKOFF_CREDIT_SHOWCASE_ID = env('TINKOFF_CREDIT_SHOWCASE_ID', default=None)\n\nTINKOFF_CREDIT_DEMO_MODE = env('TINKOFF_CREDIT_DEMO_MODE', default=DEBUG)\n\nSEND_HAPPINESS_MESSAGES = env('SEND_HAPPINESS_MESSAGES', cast=bool, default=False)\n\nDRF_RECAPTCHA_SECRET_KEY = env('RECAPTCHA_SECRET_KEY', cast=str, default='')\nDRF_RECAPTCHA_TESTING = DRF_RECAPTCHA_TESTING_PASS = not env('RECAPTCHA_ENABLED', cast=bool, default=True)\n", "path": "src/app/settings.py"}, {"content": "from django.conf import settings\nfrom django.utils import timezone\n\nfrom app.tasks import send_happiness_message, send_mail\nfrom orders.models import Order\n\n\nclass Pigwidgeon:\n    \"\"\"Ship the order (actualy calls item ship() method)\"\"\"\n    def __init__(self, order: Order, silent: bool = False):\n        self.order = order\n        self.silent = silent\n\n    def __call__(self):\n        if self.ship():\n            self.mark_order_as_shipped()\n\n        if not self.order.notification_to_giver_is_sent:\n            self.send_notification_to_giver()\n\n        if not self.silent:\n            self.send_happiness_message()\n\n    def ship(self) -> bool:\n        \"\"\"Ship the order. Returns true if order is shipped\"\"\"\n        desired_date = self.order.desired_shipment_date\n        if desired_date is None or desired_date <= timezone.now():\n            self.order.item.ship(to=self.order.user, order=self.order)\n\n            return True\n\n        return False\n\n    def mark_order_as_shipped(self):\n        self.order.shipped = timezone.now()\n        self.order.save()\n\n    def send_happiness_message(self):\n        if not settings.SEND_HAPPINESS_MESSAGES:\n            return\n\n        send_happiness_message.delay(text='\ud83d\udcb0+{sum} \u20bd, {user}, {reason}'.format(\n            sum=str(self.order.price).replace('.00', ''),\n            user=str(self.order.user),\n            reason=str(self.order.item) if self.order.giver is None else f'{self.order.item} (\u043f\u043e\u0434\u0430\u0440\u043e\u043a)',\n        ))\n\n    def send_notification_to_giver(self):\n        if self.order.giver is None:\n            return\n\n        if self.order.desired_shipment_date is None:\n            return\n\n        send_mail.delay(\n            to=self.order.giver.email,\n            template_id='gift-notification-for-giver',  # postmark\n            disable_antispam=True,\n            ctx={\n                'item_name': self.order.item.full_name,\n                'receiver_name': str(self.order.user),\n                'receiver_email': self.order.user.email,\n                'desired_shipment_date': self.order.desired_shipment_date.strftime('%d.%m.%Y'),\n            },\n        )\n\n        self.order.notification_to_giver_is_sent = True\n        self.order.save()\n", "path": "src/orders/services/order_shipper.py"}], "after_files": [{"content": "import requests\nfrom django.conf import settings\n\n\ndef send_message(channel, text):\n    url = f'https://api.telegram.org/bot{settings.BOT_TOKEN}/sendMessage'\n    response = requests.post(url, data={\n        'chat_id': channel,\n        'text': text,\n        'parse_mode': 'markdown',\n        'disable_web_page_preview': True,\n    })\n\n    assert response.status_code == 200, 'TG should return 200'\n\n\ndef send_happiness_message(text):\n    send_message(settings.HAPPINESS_MESSAGES_CHAT_ID, text)\n", "path": "src/app/integrations/tg.py"}, {"content": "import environ\nimport os\nfrom celery.schedules import crontab\n\nroot = environ.Path(__file__) - 2        # three folder back (/a/b/c/ - 3 = /)\nenv = environ.Env(DEBUG=(bool, False))  # set default values and casting\nenviron.Env.read_env()                   # reading .env file\nSITE_ROOT = root()\n\nUSE_L10N = True\nUSE_i18N = True\n\nLANGUAGE_CODE = 'ru'\nLOCALE_PATHS = ['locale']\n\nINTERNAL_IPS = [\n    '127.0.0.1',\n]\nFRONTEND_URL = 'https://education.borshev.com'\n\nUSE_TZ = False\nTIME_ZONE = env('TIME_ZONE', cast=str, default='Europe/Moscow')\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\nTEST_RUNNER = 'app.test.disable_test_command_runner.DisableTestCommandRunner'\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = env('SECRET_KEY', cast=str, default='s3cr3t')\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = env('DEBUG', cast=bool, default=False)\nCI = env('CI', cast=bool, default=False)\nANONYMIZE_ENABLED = DEBUG\n\nABSOLUTE_HOST = env('ABSOLUTE_HOST', cast=str, default='https://edu-app.borshev.com')\nALLOWED_HOSTS = [\n    'edu-app.borshev.com',\n    'localhost',\n    'localhost:8000',\n    'education.borshev.com',\n    ABSOLUTE_HOST.replace('https://', ''),\n]\n\nCORS_ORIGIN_WHITELIST = [\n    'https://pmdaily.ru',\n    'https://education.borshev.com',\n]\n\nCSRF_TRUSTED_ORIGINS = [\n    'pmdaily.ru',\n    'education.borshev.com',\n    'borshev.com',\n]\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    'app',\n    'users',\n    'orders',\n    'products',\n    'shipping',\n    'tinkoff',\n    'triggers',\n    'magnets',\n    'banking',\n\n    'corsheaders',\n    'hattori',\n    'anymail',\n    'rest_framework',\n    'rest_framework.authtoken',\n    'drf_recaptcha',\n    'django_filters',\n\n    'axes',\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n\n    'debug_toolbar',\n]\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'corsheaders.middleware.CorsMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    # 'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.auth.middleware.RemoteUserMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n    'app.middleware.real_ip.real_ip_middleware',\n    'axes.middleware.AxesMiddleware',\n    'debug_toolbar.middleware.DebugToolbarMiddleware',\n]\n\nif not DEBUG and not CI:\n    MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')\n\n\nREST_FRAMEWORK = {\n    'DEFAULT_PERMISSION_CLASSES': (\n        'rest_framework.permissions.IsAuthenticated',\n    ),\n    'DEFAULT_AUTHENTICATION_CLASSES': (\n        'rest_framework.authentication.TokenAuthentication',\n    ),\n    'DEFAULT_RENDERER_CLASSES': [\n        'app.renderers.AppJSONRenderer',\n    ],\n    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',\n    'DEFAULT_PAGINATION_CLASS': 'app.pagination.AppPagination',\n    'PAGE_SIZE': 20,\n}\n\nROOT_URLCONF = 'app.urls'\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = 'app.wsgi.application'\n\n\n# Database\n# https://docs.djangoproject.com/en/2.2/ref/settings/#databases\nDATABASES = {\n    'default': env.db(),    # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ\n}\nAUTH_USER_MODEL = 'users.User'\nAUTHENTICATION_BACKENDS = [\n    'axes.backends.AxesBackend',\n    'django.contrib.auth.backends.ModelBackend',\n    'django.contrib.auth.backends.RemoteUserBackend',\n]\n\nHEALTH_CHECKS_ERROR_CODE = 503\nHEALTH_CHECKS = {\n    'db': 'django_healthchecks.contrib.check_database',\n}\n\nMEDIA_URL = env('MEDIA_URL', default='/media/')\n\nSTATIC_URL = env('STATIC_URL', default='/static/')\nSTATIC_ROOT = env('STATIC_ROOT')\n\nSENTRY_DSN = env('SENTRY_DSN', cast=str, default='')\n\nif not DEBUG and SENTRY_DSN:\n    import sentry_sdk\n    from sentry_sdk.integrations.celery import CeleryIntegration\n    from sentry_sdk.integrations.django import DjangoIntegration\n    from sentry_sdk.integrations.redis import RedisIntegration\n\n    sentry_sdk.init(\n        dsn=SENTRY_DSN,\n        integrations=[DjangoIntegration(), CeleryIntegration(), RedisIntegration()],\n    )\n\nBROKER_URL = env('CELERY_BACKEND')\nCELERY_ALWAYS_EAGER = env('CELERY_ALWAYS_EAGER', cast=bool, default=DEBUG)  # by default in debug mode we run all celery tasks in foregroud.\nCELERY_TIMEZONE = TIME_ZONE\nCELERY_ENABLE_UTC = False\nCELERYBEAT_SCHEDULE = {\n    'run_started_purchase_trigger': {\n        'task': 'triggers.tasks.check_for_started_purchase_triggers',\n        'schedule': crontab(hour='*', minute=15),\n    },\n    'run_record_feedback_trigger': {\n        'task': 'triggers.tasks.check_for_record_feedback_triggers',\n        'schedule': crontab(hour='*', minute=15),\n    },\n    'ship_unshipped_orders': {\n        'task': 'orders.tasks.ship_unshipped_orders',\n        'schedule': crontab(hour='*', minute='*/2'),\n    },\n}\n\n\nAWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID', default=None)\nAWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY', default=None)\nAWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME', default=None)\nAWS_S3_REGION_NAME = env('AWS_S3_REGION_NAME', default=None)\nAWS_S3_ENDPOINT_URL = env('AWS_S3_ENDPOINT_URL', default=None)\n\nEMAIL_ENABLED = env('EMAIL_ENABLED', cast=bool, default=False)\n\nEMAIL_BACKEND = env('EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend')\n\nMAILCHIMP_API_KEY = env('MAILCHIMP_API_KEY', default='')\nMAILCHIMP_CONTACT_LIST_ID = env('MAILCHIMP_CONTACT_LIST_ID', cast=str, default=None)\n\nDEFAULT_FROM_EMAIL = env('EMAIL_FROM', cast=str, default='')\nANYMAIL = {\n    'POSTMARK_SERVER_TOKEN': env('POSTMARK_SERVER_TOKEN', cast=str, default=''),\n    'DEBUG_API_REQUESTS': env('DEBUG'),\n}\n\nCLICKMEETING_API_KEY = env('CLICKMEETING_API_KEY', default=None, cast=str)\n\nZOOMUS_API_KEY = env('ZOOMUS_API_KEY', default=None, cast=str)\nZOOMUS_API_SECRET = env('ZOOMUS_API_SECRET', default=None, cast=str)\n\nTINKOFF_TERMINAL_KEY = env('TINKOFF_TERMINAL_KEY', default=None)\nTINKOFF_TERMINAL_PASSWORD = env('TINKOFF_TERMINAL_PASSWORD', default=None)\nTINKOFF_CREDIT_SHOP_ID = env('TINKOFF_CREDIT_SHOP_ID', default=None)\nTINKOFF_CREDIT_SHOWCASE_ID = env('TINKOFF_CREDIT_SHOWCASE_ID', default=None)\n\nTINKOFF_CREDIT_DEMO_MODE = env('TINKOFF_CREDIT_DEMO_MODE', default=DEBUG)\n\nBOT_TOKEN = env('BOT_TOKEN', cast=str, default=None)\nHAPPINESS_MESSAGES_CHAT_ID = env('HAPPINESS_MESSAGES_CHAT_ID', cast=str, default=None)\n\nDRF_RECAPTCHA_SECRET_KEY = env('RECAPTCHA_SECRET_KEY', cast=str, default='')\nDRF_RECAPTCHA_TESTING = DRF_RECAPTCHA_TESTING_PASS = not env('RECAPTCHA_ENABLED', cast=bool, default=True)\n", "path": "src/app/settings.py"}, {"content": "from django.conf import settings\nfrom django.utils import timezone\n\nfrom app.tasks import send_happiness_message, send_mail\nfrom orders.models import Order\n\n\nclass Pigwidgeon:\n    \"\"\"Ship the order (actualy calls item ship() method)\"\"\"\n    def __init__(self, order: Order, silent: bool = False):\n        self.order = order\n        self.silent = silent\n\n    def __call__(self):\n        if self.ship():\n            self.mark_order_as_shipped()\n\n        if not self.order.notification_to_giver_is_sent:\n            self.send_notification_to_giver()\n\n        if not self.silent:\n            self.send_happiness_message()\n\n    def ship(self) -> bool:\n        \"\"\"Ship the order. Returns true if order is shipped\"\"\"\n        desired_date = self.order.desired_shipment_date\n        if desired_date is None or desired_date <= timezone.now():\n            self.order.item.ship(to=self.order.user, order=self.order)\n\n            return True\n\n        return False\n\n    def mark_order_as_shipped(self):\n        self.order.shipped = timezone.now()\n        self.order.save()\n\n    def send_happiness_message(self):\n        if not settings.HAPPINESS_MESSAGES_CHAT_ID:\n            return\n\n        send_happiness_message.delay(text='\ud83d\udcb0+{sum} \u20bd, {user}, {reason}'.format(\n            sum=str(self.order.price).replace('.00', ''),\n            user=str(self.order.user),\n            reason=str(self.order.item) if self.order.giver is None else f'{self.order.item} (\u043f\u043e\u0434\u0430\u0440\u043e\u043a)',\n        ))\n\n    def send_notification_to_giver(self):\n        if self.order.giver is None:\n            return\n\n        if self.order.desired_shipment_date is None:\n            return\n\n        send_mail.delay(\n            to=self.order.giver.email,\n            template_id='gift-notification-for-giver',  # postmark\n            disable_antispam=True,\n            ctx={\n                'item_name': self.order.item.full_name,\n                'receiver_name': str(self.order.user),\n                'receiver_email': self.order.user.email,\n                'desired_shipment_date': self.order.desired_shipment_date.strftime('%d.%m.%Y'),\n            },\n        )\n\n        self.order.notification_to_giver_is_sent = True\n        self.order.save()\n", "path": "src/orders/services/order_shipper.py"}]}