{"id":342405,"date":"2022-12-10T21:01:36","date_gmt":"2022-12-10T21:01:36","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=342405"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=342405","title":{"rendered":"<span>\u041f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c autofocus.su<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d15\/fd1\/1db\/d15fd11db38d18152ba83fe5fe10206d.png\" width=\"1080\" height=\"605\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d15\/fd1\/1db\/d15fd11db38d18152ba83fe5fe10206d.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0440\u043e \u0442\u043e, \u043a\u0430\u043a \u0437\u0430 \u043a\u0443\u043b\u0438\u0441\u0430\u043c\u0438 \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430 \u043c\u043e\u0435\u0433\u043e \u043c\u0438\u043d\u0438-\u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043f\u043e \u0432\u0435\u0434\u0435\u043d\u0438\u044e \u0437\u0430\u0434\u0430\u0447 <a href=\"https:\/\/autofocus.su\/iFa\" rel=\"noopener noreferrer nofollow\">autofocus.su<\/a>. \u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 <a href=\"https:\/\/habr.com\/ru\/post\/702668\/\" rel=\"noopener noreferrer nofollow\">\u0437\u0430\u043c\u0435\u0442\u043a\u0435<\/a> \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u043b \u043f\u0440\u043e \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b, \u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0432 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u0410\u0432\u0442\u043e\u0444\u043e\u043a\u0443\u0441\u0430. \u0410 \u0442\u0443\u0442 \u0431\u0443\u0434\u0435\u0442 \u0441\u043a\u043e\u0440\u0435\u0435 \u043d\u0430\u0431\u043e\u0440 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u0441 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u043c\u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f\u043c\u0438 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0438 \u043a\u0430\u043a \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u043c\u0435\u0436\u0434\u0443 \u0441\u043e\u0431\u043e\u0439. \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442\u00a0\u043e\u0442\u043b\u0438\u0447\u0430\u0442\u044c\u0441\u044f \u0432 \u0432\u0430\u0448\u0435\u043c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043d\u043e \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u0432 \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043d\u044f\u0442\u043d\u044b.<\/p>\n<p>\u041b\u0438\u0447\u043d\u043e \u043c\u043d\u0435 \u0447\u0430\u0441\u0442\u043e \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u043a\u0430\u043a\u043e\u0433\u043e-\u0442\u043e \u0441\u043a\u0435\u043b\u0435\u0442\u0430 \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u0441 \u0447\u0435\u0433\u043e \u043d\u0430\u0447\u0430\u0442\u044c. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0447\u0442\u043e \u0431\u0443\u0434\u0443 \u043f\u043e\u043b\u0435\u0437\u0435\u043d.<\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0431\u044d\u043a\u0435\u043d\u0434\u0430.<\/p>\n<h2>Django<\/h2>\n<p>\u0412 \u043e\u0441\u043d\u043e\u0432\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u2014 <a href=\"https:\/\/www.djangoproject.com\" rel=\"noopener noreferrer nofollow\">Django<\/a>. \u041a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u0434\u043b\u044f \u043a\u043e\u0441\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0437\u0432\u0435\u0437\u0434\u043e\u043b\u0435\u0442\u043e\u0432. \u041e\u0447\u0435\u043d\u044c \u043c\u043d\u043e\u0433\u043e \u0432\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u0438\u044f \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0438 \u043d\u044e\u0430\u043d\u0441\u0430\u043c \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 <a class=\"mention\" href=\"\/users\/kesn\">@kesn<\/a> \u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0435\u043c\u0443 \u0437\u0430 \u044d\u0442\u043e!<\/p>\n<p>\u0412 \u0414\u0436\u0430\u043d\u0433\u043e \u0443 \u043c\u0435\u043d\u044f \u0441\u0435\u0439\u0447\u0430\u0441 \u0442\u0440\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2014 \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u0447, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0445 \u0441\u0441\u044b\u043b\u043e\u043a. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u044f \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043b \u0441\u0432\u043e\u0438\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c User \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445:<\/p>\n<pre><code class=\"python\">AUTH_USER_MODEL = 'accounts.User'<\/code><\/pre>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u043f\u0440\u043e\u0447\u0435\u0433\u043e, \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043f\u043e\u0447\u0442\u044b \u0438 \u0443\u0431\u0440\u0430\u043b \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f:<\/p>\n<pre><code class=\"python\">email = models.EmailField(primary_key=True) password = None REQUIRED_FIELDS = [] USERNAME_FIELD = 'email'<\/code><\/pre>\n<p>\u042d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 Django-sesame.<\/p>\n<h2>django-sesame<\/h2>\n<p><a href=\"https:\/\/django-sesame.readthedocs.io\" rel=\"noopener noreferrer nofollow\">\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f<\/a> \u0432 Django \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u044b\u0445 \u0441\u0441\u044b\u043b\u043e\u043a. \u041f\u0440\u043e\u0441\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0435 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442, \u0434\u0435\u043b\u0430\u0435\u0442\u0435 \u043f\u044f\u0442\u043e\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0442\u044c \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435\u043b\u044f\u043c \u0441\u0441\u044b\u043b\u043a\u0438, \u043f\u0435\u0440\u0435\u0439\u0434\u044f \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043e\u043d\u0438 \u0441\u043c\u043e\u0433\u0443\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f:<\/p>\n<pre><code>https:\/\/example.com\/sesame\/login\/?sesame=zxST9d0XT9xgfYLvoa9e2myN<\/code><\/pre>\n<p>\u0412\u0438\u0434 \u0441\u0441\u044b\u043b\u043a\u0438, \u0441\u0440\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043d\u044e\u0430\u043d\u0441\u044b \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c. \u041f\u043e\u0441\u043b\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043c\u0435\u0447\u0430\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043a\u0430\u043a \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0432\u0448\u0438\u0445 \u0441\u0432\u043e\u0439 \u0430\u0434\u0440\u0435\u0441 \u043f\u043e\u0447\u0442\u044b.<\/p>\n<h2>dotenv<\/h2>\n<p><a href=\"https:\/\/saurabh-kumar.com\/python-dotenv\/\" rel=\"noopener noreferrer nofollow\">\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/a> \u0434\u043b\u044f python, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0447\u0438\u0442\u0430\u0435\u0442 \u0444\u0430\u0439\u043b <code>.env<\/code> \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u044b. \u0410 \u0432\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0435 \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0432 <code>.gitignore<\/code> \u0438 \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b. \u0420\u0435\u0448\u0430\u0435\u0442 \u043a\u0443\u0447\u0443 \u0433\u043e\u043b\u043e\u0432\u043d\u043e\u0439 \u0431\u043e\u043b\u0438 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u043e\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044e. \u0412 <code>settings.py<\/code> \u043f\u0438\u0448\u0435\u043c:<\/p>\n<pre><code class=\"python\">import dotenv  dotenv_file = BASE_DIR \/ \".env\" if os.path.isfile(dotenv_file): \u00a0 \u00a0 dotenv.load_dotenv(dotenv_file)<\/code><\/pre>\n<p>\u0410 \u0434\u0430\u043b\u044c\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e:<\/p>\n<pre><code class=\"python\">DEBUG = os.environ.get('DJANGO_DEBUG', default=False) in [ \u00a0 \u00a0 'True', 'true', '1', True]<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 <code>.env<\/code> \u0441\u0442\u0440\u043e\u043a\u0443<\/p>\n<pre><code>DJANGO_DEBUG=1<\/code><\/pre>\n<p>\u0422\u043e <code>DEBUG<\/code> \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0432\u0435\u043d <code>True<\/code><\/p>\n<h2>graphene-django<\/h2>\n<p><a href=\"https:\/\/docs.graphene-python.org\/projects\/django\/en\/latest\/installation\/\" rel=\"noopener noreferrer nofollow\">\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/a> \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0410\u041f\u0418 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u044f\u0437\u044b\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 <a href=\"https:\/\/graphql.org\" rel=\"noopener noreferrer nofollow\">GraphQL<\/a>. \u0415\u0433\u043e \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u043b\u0438 \u0432 Facebook (\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438, \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u043e\u0439 \u043d\u0430 \u0442\u0435\u0440\u0440\u0438\u0442\u043e\u0440\u0438\u0438 \u0420\u043e\u0441\u0441\u0438\u0438). \u041e\u043d\u00a0 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a \u0410\u041f\u0418 \u043d\u0430 \u043e\u0434\u043d\u0443 \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0443\u0436\u043d\u044b, \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044f \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code>query UserItems \u00a0 \u00a0 { \u00a0 \u00a0 \u00a0 \u00a0 user { \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 pages \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 page \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 email \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 isValidated \u00a0 \u00a0 \u00a0 \u00a0 } \u00a0 \u00a0 \u00a0 \u00a0 items { \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 text \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 id \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 repeats \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 state \u00a0 \u00a0 \u00a0 \u00a0 } \u00a0 \u00a0 }<\/code><\/pre>\n<p>\u0418 \u0432 \u043e\u0442\u0432\u0435\u0442 \u043f\u0440\u0438\u0434\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 <code>User<\/code> \u0438 \u043c\u0430\u0441\u0441\u0438\u0432 <code>Items<\/code> \u0441 \u0437\u0430\u0434\u0430\u0447\u043a\u0430\u043c\u0438. \u0410 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0443\u0442\u0430\u0446\u0438\u0439:<\/p>\n<pre><code>mutation addItem($text: String!){ \u00a0 \u00a0 createItem(text: $text){ \u00a0 \u00a0 \u00a0 \u00a0 user { \u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 email \u00a0 \u00a0 \u00a0 \u00a0 } \u00a0 \u00a0 } \u00a0 }<\/code><\/pre>\n<p>\u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u044d\u0442\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0440\u044f\u043c \u0441 \u0434\u0432\u0443\u0445 \u0441\u0442\u0440\u043e\u043a. \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0430\u0448\u0430 \u0442\u043e\u0447\u043a\u0430 graphql \u0438 \u043a\u0430\u043a \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043c\u0443\u0442\u0430\u0446\u0438\u0438. \u0421\u043f\u0435\u0440\u0432\u0430 \u043c\u0435\u043d\u044f graphql \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0443\u0433\u0430\u043b, \u0430 \u043f\u043e\u0442\u043e\u043c \u0440\u0430\u0441\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b.<\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0444\u0440\u043e\u043d\u0442\u0443.<\/p>\n<h2>Vue.js<\/h2>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u0434\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0432\u044b\u0431\u0440\u0430\u043b <a href=\"https:\/\/vuejs.org\" rel=\"noopener noreferrer nofollow\">Vue.js<\/a> \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u043e\u0447\u0435\u043c\u0443 \u0431\u044b \u0438 \u043d\u0435\u0442. \u0421\u0434\u0435\u043b\u0430\u043b \u0432\u0435\u0440\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0441 Typescript. \u041d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043f\u0430\u043f\u043a\u0435 frontend \u0440\u044f\u0434\u043e\u043c \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438 Django:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0d9\/46c\/7a3\/0d946c7a3b3d3469c5ae8c6cb659bcf0.png\" width=\"686\" height=\"682\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0d9\/46c\/7a3\/0d946c7a3b3d3469c5ae8c6cb659bcf0.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0418\u0437 \u0432\u0430\u0436\u043d\u044b\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0443\u043a\u0430\u0437\u0430\u043b \u0440\u0430\u0437\u043d\u044b\u0435 \u043f\u0430\u043f\u043a\u0438 \u0434\u043b\u044f <code>production<\/code> \u0438 <code>development<\/code>:<\/p>\n<pre><code class=\"javascript\">outputDir: process.env.NODE_ENV === \"production\" ? \"dist\" : \"static\"<\/code><\/pre>\n<p>\u0418 \u0442\u043e, \u043a\u0443\u0434\u0430, \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b:<\/p>\n<pre><code class=\"javascript\">configureWebpack: {     output: {       filename:         process.env.NODE_ENV === \"production\"           ? \"..\/..\/static\/js\/[name].js\"           : \"static\/js\/[name].js\",       chunkFilename:         process.env.NODE_ENV === \"production\"           ? \"..\/..\/static\/js\/[name].js\"           : \"static\/js\/[name].js\",     },     plugins: [       new WriteFilePlugin(),       process.env.NODE_ENV === \"production\"         ? new BundleTracker({             filename: \"webpack-stats-prod.json\",             publicPath: \"\/\",           })         : new BundleTracker({             filename: \"webpack-stats.json\",             publicPath: \"http:\/\/localhost:8080\/\",           }),     ],   },<\/code><\/pre>\n<p>\u0410 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0440\u0443\u0436\u0438\u0442\u044c \u0432\u0441\u0435 \u044d\u0442\u043e \u0441 Django \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u0441\u044f:<\/p>\n<h2>webpack_loader<\/h2>\n<p><a href=\"https:\/\/django-webpack-loader.readthedocs.io\/en\/latest\/\" rel=\"noopener noreferrer nofollow\">\u041f\u043b\u0430\u0433\u0438\u043d<\/a> \u043e\u0442 Jazzband, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0430\u043d\u0434\u043b\u044b Webpack \u0432 \u0448\u0430\u0431\u043b\u043e\u043d\u0430\u0445:<\/p>\n<pre><code class=\"xml\">{% extends \"base.html\" %}  {% load render_bundle from webpack_loader %}  {% block title %}   Autofocus {% endblock title %}  {% block head %}   {{ block.super }} {% endblock head %}      {% block body %}   &lt;div id=\"app\">&lt;\/div> {% endblock body %}  {% block script %}   {% render_bundle 'app' %} {% endblock script %}<\/code><\/pre>\n<h2>Vue Apollo<\/h2>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0440\u0443\u0436\u0438\u0442\u044c Vue.JS \u0441 GraphQL \u0435\u0441\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/v4.apollo.vuejs.org\" rel=\"noopener noreferrer nofollow\">Vue Apollo<\/a>. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0435\u043c \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u043a\u0438\u043d\u0443\u0442\u044c x-<code>csrftoken<\/code>. \u0415\u0433\u043e \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u044b\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0435\u043c \u0438\u0437 \u043a\u0443\u043a \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043f\u0430\u043a\u0435\u0442 <code>js-cookie<\/code>:<\/p>\n<pre><code class=\"typescript\">import { createApp, provide, h } from \"vue\"; import App from \".\/App.vue\"; import \".\/registerServiceWorker\"; import { DefaultApolloClient } from \"@vue\/apollo-composable\"; import { ApolloClient, InMemoryCache, createHttpLink } from \"@apollo\/client\/core\"; import Cookies from \"js-cookie\";  const cache = new InMemoryCache();  const link = createHttpLink({ \u00a0 \u00a0 uri: \"\/graphql\", \u00a0 \u00a0 headers: { \u00a0 \u00a0 \u00a0 \u00a0 \"x-csrftoken\": Cookies.get(\"csrftoken\") \u00a0 \u00a0 } });  const apolloClient = new ApolloClient({ \u00a0 \u00a0 cache, \u00a0 \u00a0 link: link, });  const app = createApp({ \u00a0 \u00a0 setup() { \u00a0 \u00a0 \u00a0 \u00a0 provide(DefaultApolloClient, apolloClient); \u00a0 \u00a0 },  \u00a0 \u00a0 render: () => h(App), });  app.mount(\"#app\");<\/code><\/pre>\n<h2>Tailwind CSS<\/h2>\n<p>\u0414\u043b\u044f css \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <a href=\"https:\/\/tailwindcss.com\" rel=\"noopener noreferrer nofollow\">Tailwind css<\/a>. \u0415\u0433\u043e \u043f\u0440\u0435\u043b\u0435\u0441\u0442\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e, \u0432\u043e-\u043f\u0435\u0440\u0432\u044b\u0445,\u00a0\u0442\u0430\u043c \u0435\u0441\u0442\u044c \u0445\u043e\u0440\u043e\u0448\u043e \u043f\u0440\u043e\u0434\u0443\u043c\u0430\u043d\u043d\u0430\u044f \u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043a\u043b\u0430\u0441\u0441\u043e\u0432. \u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0445 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0438 \u0442\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u043d\u0435 \u0432\u0441\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443, \u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435 \u043a\u043b\u0430\u0441\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u0434:<\/p>\n<pre><code class=\"css\">.page__current {     @apply bg-slate-800 dark:bg-slate-400;     @apply text-white dark:text-slate-700;     @apply rounded-full;     @apply cursor-default;   }<\/code><\/pre>\n<p>\u0410 \u043d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f:<\/p>\n<pre><code class=\"css\">.page__current {   --tw-bg-opacity: 1;   background-color: rgb(30 41 59 \/ var(--tw-bg-opacity)); }  @media (prefers-color-scheme: dark) {   .page__current {     --tw-bg-opacity: 1;     background-color: rgb(148 163 184 \/ var(--tw-bg-opacity));   } }  .page__current {   --tw-text-opacity: 1;   color: rgb(255 255 255 \/ var(--tw-text-opacity)); }  @media (prefers-color-scheme: dark) {   .page__current {     --tw-text-opacity: 1;     color: rgb(51 65 85 \/ var(--tw-text-opacity));   } }  .page__current {   border-radius: 9999px;   cursor: default; }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c, \u0435\u0441\u043b\u0438 \u043a\u043b\u0430\u0441\u0441 \u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f, \u0442\u043e \u043e\u043d \u043d\u0435 \u043f\u043e\u043f\u0430\u0434\u0435\u0442 \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0444\u0430\u0439\u043b. \u0423\u043a\u0430\u0437\u0430\u0442\u044c, \u0433\u0434\u0435 \u0438 \u043a\u0430\u043a\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u0432 tailwind.config.js:<\/p>\n<pre><code class=\"typescript\">\/** @type {import('tailwindcss').Config} *\/  module.exports = { \u00a0 content: [ \u00a0 \u00a0 \".\/focus\/**\/*.{html,js}\", \u00a0 \u00a0 \".\/accounts\/**\/*.{html,js}\", \u00a0 \u00a0 \".\/frontend\/src\/**\/*.{html,vue,js}\" \u00a0 ], \u00a0 theme: { \u00a0 \u00a0 extend: {}, \u00a0 \u00a0 fontFamily: { \u00a0 \u00a0 \u00a0 sans: [\"PT Sans\", \"sans-serif\"], \u00a0 \u00a0 }, \u00a0 }, \u00a0 plugins: [require(\"@tailwindcss\/typography\"), require(\"@tailwindcss\/forms\")], };<\/code><\/pre>\n<p>\u0412\u043e\u043e\u0431\u0449\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Tailwind \u043f\u0440\u044f\u043c\u043e \u0432\u043e Vue.js, \u043d\u043e \u043f\u043e\u043a\u0430 \u043d\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0441\u044f, \u043a\u0430\u043a \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c. \u0425\u043e\u0442\u044f \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0443\u0434\u043e\u0431\u043d\u0435\u0435: \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0438\u043b\u0438 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438.<\/p>\n<h2>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h2>\n<p>\u0414\u043b\u044f \u044e\u043d\u0438\u0442-\u0442\u0435\u0441\u0442\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <code>TestCase<\/code> \u0441\u0430\u043c\u043e\u0439 Django. \u0415\u0449\u0435 \u0435\u0441\u0442\u044c \u043f\u0430\u043f\u043a\u0430 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c <code>functional_tests<\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b, \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 Selenium+Chromedriver. \u0412 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u0430\u0445 \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u0432\u0435\u0441\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041f\u043b\u044e\u0441 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043e\u0448\u0438\u0431\u043e\u043a.\u00a0<\/p>\n<p>\u041f\u043e\u043a\u0430 \u043d\u0435 \u043f\u043e\u0434\u0441\u0442\u0443\u043f\u0430\u043b\u0441\u044f \u043a \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u043a\u0430\u0442\u043a\u0438 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440. \u0412\u0440\u043e\u0434\u0435 \u0435\u0441\u0442\u044c <a href=\"https:\/\/mailtrap.io\" rel=\"noopener noreferrer nofollow\">Mailtrap<\/a>, \u043d\u043e \u0440\u0443\u043a\u0438 \u043f\u043e\u043a\u0430 \u043d\u0435 \u0434\u043e\u0448\u043b\u0438. \u041f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u2014 \u043f\u043e\u0434\u043c\u0435\u043d\u044f\u044e \u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0430 <code>console.EmailBackend<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0435\u0447\u0430\u0442\u0430\u0435\u0442 \u043f\u043e\u0447\u0442\u0443 \u043f\u0440\u044f\u043c\u043e \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c:<\/p>\n<pre><code class=\"python\">if DEBUG: \u00a0 \u00a0 EMAIL_BACKEND = \"django.core.mail.backends.console.EmailBackend\" else: \u00a0 \u00a0 EMAIL_USE_TLS = True \u00a0 \u00a0 EMAIL_HOST = \u2018***\u2019 \u00a0 \u00a0 EMAIL_PORT = 587 \u00a0 \u00a0 EMAIL_HOST_USER = \u2018***\u2019 \u00a0 \u00a0 EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '') \u00a0 \u00a0 DEFAULT_FROM_EMAIL = \u2018***\u2019<\/code><\/pre>\n<p>\u0418 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u044e \u0435\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>django.core.mail<\/code>. \u0422\u0430\u043c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0438\u0441\u044c\u043c\u0430 \u0438 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0438\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435:<\/p>\n<pre><code class=\"python\"># \u0410 \u0432 \u043f\u043e\u0447\u0442\u043e\u0432\u043e\u043c \u044f\u0449\u0438\u043a\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0438\u0441\u044c\u043c\u043e self.assertEqual(len(mail.outbox), 1)  # \u0412 \u043f\u0438\u0441\u044c\u043c\u0435 \u0435\u0441\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0432\u0445\u043e\u0434 email_text = mail.outbox[0].body link = re.search(r'(http.*)$', email_text, re.MULTILINE).group(1) self.assertIsNotNone(link)<\/code><\/pre>\n<p>Vue.JS \u043f\u043e\u043a\u0430 \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u044e.<\/p>\n<h2>Dock<\/h2>\n<p>\u0414\u043b\u044f \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <a href=\"https:\/\/dokku.com\" rel=\"noopener noreferrer nofollow\">Dokku<\/a>. \u042d\u0442\u043e \u043e\u043f\u0435\u043d\u0441\u043e\u0440\u0441\u043d\u044b\u0439 \u0430\u043d\u0430\u043b\u043e\u0433 <a href=\"https:\/\/www.heroku.com\" rel=\"noopener noreferrer nofollow\">Heroku<\/a> \u2014 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u0417\u0432\u0443\u0447\u0438\u0442 \u0441\u043b\u043e\u0436\u043d\u043e, \u0430 \u043d\u0430 \u0434\u0435\u043b\u0435 \u0432\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0443\u0448\u0438\u0442\u0435 \u0432\u0435\u0442\u043a\u0443 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043e\u043d\u043e \u0441\u0430\u043c\u043e \u0442\u0430\u043c \u0432\u0441\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f. \u0412 \u043f\u0435\u0440\u0432\u044b\u0439 \u0440\u0430\u0437 \u044f \u0441 \u044d\u0442\u0438\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u043c \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 <a class=\"mention\" href=\"\/users\/ohld\">@ohld<\/a><a href=\"https:\/\/habr.com\/ru\/post\/547488\/\" rel=\"noopener noreferrer nofollow\">\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u044b\u0439 \u041f\u0440\u043e\u0434\u0430\u043a\u0448\u043d-\u0440\u0435\u0434\u0438 \u0422\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u0431\u043e\u0442 \u043d\u0430 Django<\/a>.<\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 Django \u0432 Dokku \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0432 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<br \/><code>.buildpacks<\/code>\u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u043a\u0430\u043a\u043e\u0439 \u0441\u0431\u043e\u0440\u0449\u0438\u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c:<\/p>\n<pre><code>https:\/\/github.com\/heroku\/heroku-buildpack-python.git#v222<\/code><\/pre>\n<p><code>App.json<\/code> \u2014 \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>cron<\/code>: <\/p>\n<pre><code class=\"json\">{   \"formation\": {     \"web\": {       \"quantity\": 1     }   },   \"cron\": [     {       \"command\": \"python manage.py clean_users\",       \"schedule\": \"@daily\"     },     {       \"command\": \"cd af_dbt &amp;&amp; dbt deps &amp;&amp; dbt run\",       \"schedule\": \"@daily\"     }   ] }<\/code><\/pre>\n<p><code>Procfile<\/code> \u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0431\u0438\u043b\u0434\u0430:<\/p>\n<pre><code>release: python manage.py migrate --noinput &amp;&amp; python manage.py collectstatic --no-input web: gunicorn --bind :$PORT --workers 4 --worker-class uvicorn.workers.UvicornWorker autofocus.asgi:application<\/code><\/pre>\n<p><code>Runtime.txt<\/code> \u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e Python: <\/p>\n<pre><code>python-3.10.8<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0443\u0448\u0438\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u0432\u0441\u0435. Dokku \u0448\u0443\u0440\u0448\u0438\u0442 \u0438 \u0431\u0435\u0441\u0448\u043e\u0432\u043d\u043e \u0432\u044b\u043a\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u043f\u043e\u0432\u0435\u0440\u0445 \u0441\u0442\u0430\u0440\u043e\u0439.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/92e\/311\/899\/92e311899a76847d6b15e4d628ecaca8.png\" width=\"1198\" height=\"1083\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/92e\/311\/899\/92e311899a76847d6b15e4d628ecaca8.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>Postrgres<\/h2>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043a\u0430\u043a\u0430\u044f-\u0442\u043e \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445. \u042f \u0432\u044b\u0431\u0440\u0430\u043b Postgres \u0438 \u043f\u043b\u0430\u0433\u0438\u043d \u043a Dokku <a href=\"https:\/\/github.com\/dokku\/dokku-postgres\" rel=\"noopener noreferrer nofollow\"><strong>dokku postgres<\/strong><\/a><strong>.<\/strong> \u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434 <code>create<\/code> \u0438 <code>link<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0435 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0435 \u0435\u0435 \u043a \u0432\u0430\u0448\u0435\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e. \u0422\u0430\u043a\u0436\u0435, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0439 \u0441\u043d\u0430\u0440\u0443\u0436\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b <code>expose<\/code>. \u0410 \u0435\u0449\u0435 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u0430\u0437\u044b, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u0431\u0430\u043a\u0435\u0442 \u0432 \u042f\u043d\u0434\u0435\u043a\u0441.\u041e\u0431\u043b\u0430\u043a\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>backup-auth<\/code>. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0411\u0414, \u0430 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0438 \u0442\u0440\u0435\u0442\u044c\u0435\u0433\u043e \u2014 <code>id<\/code> \u0438 <code>key<\/code> \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0433\u043e \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u043c\u0435\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0430\u043a\u0435\u0442\u0443.<\/p>\n<pre><code class=\"bash\">dokku postgres:backup-auth &lt;service> \\     &lt;aws-access-key-id> &lt;aws-secret-access-key> \\     ru-central1 s3v4 https:\/\/storage.yandexcloud.net<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0433\u043e \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b backup-schedule. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0432 \u0431\u0430\u043a\u0435\u0442\u0435 \u043d\u0430\u0447\u043d\u0443\u0442 \u043f\u043e\u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0431\u044d\u043a\u0430\u043f\u044b:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9fa\/8fa\/5e9\/9fa8fa5e95a4341e2b4d0241ee9c545e.png\" width=\"1900\" height=\"1126\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9fa\/8fa\/5e9\/9fa8fa5e95a4341e2b4d0241ee9c545e.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>dj_database_url<\/h2>\n<p><a href=\"https:\/\/github.com\/kennethreitz\/dj-database-url\" rel=\"noopener noreferrer nofollow\">\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435<\/a> \u0434\u043b\u044f Django, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u0449\u0435\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u0434 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c <code>DATABASE_URL<\/code> \u0438, \u0435\u0441\u043b\u0438 \u043e\u043d\u0430 \u0435\u0441\u0442\u044c, \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043d\u0430 \u0435\u0435 \u043e\u0441\u043d\u043e\u0432\u0435. \u0412 \u043a\u043e\u0434\u0435 Django \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0435:<\/p>\n<pre><code class=\"python\">DATABASES = {     'default': dj_database_url.config(conn_max_age=600,                                       default=\"sqlite:\/\/\/db.sqlite3\"), }<\/code><\/pre>\n<p>\u0418 \u0442\u043e\u0433\u0434\u0430 \u043d\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0431\u0430\u0437\u0430 SQLLite, \u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0433\u0434\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u0432\u0438\u0434\u0430:<\/p>\n<pre><code>DATABASE_URL=postgres:\/\/user:p#ssword!@localhost\/foobar<\/code><\/pre>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0441\u044f \u043a Postgres. \u0410 dokku-postgres \u0435\u0435 \u043a\u0430\u043a \u0440\u0430\u0437 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442, \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0441 \u0431\u0430\u0437\u043e\u0439.<\/p>\n<h2>dokku-letsencrypt<\/h2>\n<p><a href=\"https:\/\/github.com\/dokku\/dokku-letsencrypt\" rel=\"noopener noreferrer nofollow\">\u041f\u043b\u0430\u0433\u0438\u043d<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0432 \u0434\u043b\u044f \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0435 \u0438\u043c\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0434\u043e\u043c\u0435\u043d\u044b, \u0430 \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043b\u0430\u0433\u0438\u043d \u0441\u0434\u0435\u043b\u0430\u0435\u0442 \u0441\u0430\u043c.<\/p>\n<h2>Whitenoise<\/h2>\n<p><a href=\"https:\/\/whitenoise.evans.io\/en\/latest\/\" rel=\"noopener noreferrer nofollow\">\u041c\u043e\u0434\u0443\u043b\u044c<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u043e\u0442\u0434\u0430\u0447\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 Django. \u0421\u0436\u0438\u043c\u0430\u0435\u0442 \u0438\u0445, \u043f\u0440\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 CDN. \u041e\u0442\u0434\u0430\u0447\u0430 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0414\u0436\u0430\u043d\u0433\u043e \u0434\u043b\u044f \u043c\u0435\u043d\u044f \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043f\u043b\u044f\u0441\u043a\u0430 \u0441 \u0431\u0443\u0431\u043d\u043e\u043c \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u0440\u0435\u0436\u0438\u043c\u0435. \u0412 \u0414\u043e\u043a\u043a\u0443 \u043f\u0440\u043e\u0431\u0440\u043e\u0448\u0435\u043d\u0430 \u043f\u0430\u043f\u043a\u0430 <code>staticfiles<\/code> \u043c\u0435\u0436\u0434\u0443 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u043c \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c \u0438 \u043f\u0430\u043f\u043a\u043e\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b <code>storage<\/code>. \u0412 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u0414\u0436\u0430\u043d\u0433\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">STATICFILES_DIRS = ( \u00a0 \u00a0 os.path.join(BASE_DIR, 'static'), )  STATIC_URL = 'static\/'  if not DEBUG: \u00a0 \u00a0 STATICFILES_STORAGE = ('whitenoise.storage.' \u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 'CompressedManifestStaticFilesStorage') \u00a0 \u00a0 STATIC_ROOT = BASE_DIR \/ 'staticfiles'<\/code><\/pre>\n<h2>Rollbar<\/h2>\n<p>\u041a\u043e\u0433\u0434\u0430-\u0442\u043e \u0434\u0430\u0432\u043d\u043e \u043d\u0430\u0442\u043a\u043d\u0443\u043b\u0441\u044f \u043d\u0430 <a href=\"https:\/\/rollbar.com\" rel=\"noopener noreferrer nofollow\">rollbar<\/a> \u0432 <a href=\"https:\/\/bureau.ru\/soviet\/20170615\/\" rel=\"noopener noreferrer nofollow\">\u0441\u043e\u0432\u0435\u0442\u0435<\/a> \u0431\u044e\u0440\u043e \u0438 \u0441 \u0442\u0435\u0445 \u043f\u043e\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0432 \u0441\u0432\u043e\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445. \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u0432\u0443\u043c\u044f (\u043d\u0443 \u043b\u0430\u0434\u043d\u043e, \u0441\u0435\u043c\u044c\u044e) \u0441\u0442\u0440\u043e\u0447\u043a\u0430\u043c\u0438 \u043a\u043e\u0434\u0430. \u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432\u044b\u044f\u0432\u043b\u044f\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u043d\u0430 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/bc3\/34b\/e36\/bc334be360f3d39ff6e0122489286d0a.png\" width=\"2060\" height=\"870\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bc3\/34b\/e36\/bc334be360f3d39ff6e0122489286d0a.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>Django Management Command<\/h2>\n<p>\u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0437\u0430\u0434\u0430\u0447\u0438 \u0434\u043b\u044f \u043e\u043d\u0431\u043e\u0440\u0434\u0438\u043d\u0433\u0430. \u042d\u0442\u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0441 \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043e\u0442\u043c\u0435\u0447\u0430\u043b\u0438, \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043c\u043d\u043e\u0433\u043e \u0438 \u043d\u0430\u0434\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438\u0437\u0431\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043e\u0442 \u043c\u0435\u0440\u0442\u0432\u044b\u0445 \u0434\u0443\u0448. \u042d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/docs.djangoproject.com\/en\/4.1\/howto\/custom-management-commands\/\" rel=\"noopener noreferrer nofollow\">\u043a\u043e\u043c\u0430\u043d\u0434\u044b<\/a> Django, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432 Dokku \u043f\u043e \u043a\u0440\u043e\u043d\u0443, \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u043c\u0443 \u0432 <code>app.json<\/code>:<\/p>\n<pre><code class=\"json\">\"cron\": [     {       \"command\": \"python manage.py clean_users\",       \"schedule\": \"@daily\"     },     ...   ]<\/code><\/pre>\n<h2>\u0420\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435<\/h2>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u043c bash \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u043f\u0430\u043a\u0438 vuejs, css, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u044e\u043d\u0438\u0442-\u0442\u0435\u0441\u0442\u044b \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0438, \u0435\u0441\u043b\u0438 \u0432\u0441\u0435 \u0445\u043e\u0440\u043e\u0448\u043e, \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u043f\u0440\u043e\u0435\u043a\u0442 \u0432 Dokku. \u0412 \u0434\u043e\u043a\u043a\u0443 \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f <code>autofocus-stg<\/code> \u0438 <code>autofocus<\/code>. \u0421\u043f\u0435\u0440\u0432\u0430 \u0438\u0434\u0435\u0442 \u0434\u0435\u043f\u043b\u043e\u0439 \u0432 \u043f\u0435\u0440\u0432\u044b\u0439. \u0415\u0441\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u043e\u0442\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442, \u0442\u043e \u0440\u0443\u043a\u0430\u043c\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e \u0434\u0435\u043f\u043b\u043e\u0439 \u0432 \u0440\u0430\u0431\u043e\u0447\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e.\u00a0<\/p>\n<p>\u0412\u043e\u043e\u0431\u0449\u0435 dokku \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/github.com\/features\/actions\" rel=\"noopener noreferrer nofollow\">GitHub Actions<\/a>. \u041d\u043e \u0440\u0443\u043a\u0438 \u043f\u043e\u043a\u0430 \u043d\u0435 \u0434\u043e\u0448\u043b\u0438. \u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/547488\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c-\u0431\u043e\u0442\u0430<\/a>.<\/p>\n<h2>\u0410\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430<\/h2>\n<p>\u0414\u043b\u044f \u043f\u043e\u0434\u0441\u0447\u0435\u0442\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432\u043e\u0439 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <a href=\"https:\/\/www.getdbt.com\" rel=\"noopener noreferrer nofollow\">dbt<\/a> \u0441 \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u043e\u043c Postgres. \u0421 \u0435\u0433\u043e \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u043a\u043e\u043d\u0432\u0435\u0435\u0440, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u0432\u0430\u0448\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0446\u0435\u043f\u043e\u0447\u0435\u043a SQL-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. Dbt \u0442\u043e\u0436\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043d\u044c \u043f\u043e \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044e. \u0410 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u0432 \u0441\u0445\u0435\u043c\u0443 <code>dbt<\/code> \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/54e\/f06\/b57\/54ef06b579a18533d60b5b90f92dff17.png\" width=\"608\" height=\"556\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/54e\/f06\/b57\/54ef06b579a18533d60b5b90f92dff17.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0410 \u043f\u043e\u0442\u043e\u043c \u0438\u0437 \u044d\u0442\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u044f \u0437\u0430\u0431\u0438\u0440\u0430\u044e \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 <a href=\"https:\/\/datalens.yandex.ru\" rel=\"noopener noreferrer nofollow\">DataLens<\/a> \u0434\u043b\u044f \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/571\/cb1\/b58\/571cb1b5809fe96b43c858cd83efd34d.png\" width=\"2738\" height=\"726\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/571\/cb1\/b58\/571cb1b5809fe96b43c858cd83efd34d.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435 \u0441\u043b\u0435\u0434\u0443\u0435\u0442<\/h2>\n<p>\u041d\u0430 \u0434\u0430\u043d\u043d\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u044d\u0442\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0432\u0441\u0435, \u0447\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0438. \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u2014 \u0431\u0443\u0434\u0443 \u0440\u0430\u0434 \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c. \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u044f, \u043a\u0430\u043a \u043b\u0443\u0447\u0448\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u2014\u00a0\u0431\u0443\u0434\u0443 \u0440\u0430\u0434 \u0432\u044b\u0441\u043b\u0443\u0448\u0430\u0442\u044c. ? \u0421\u043f\u0430\u0441\u0438\u0431\u043e, \u0447\u0442\u043e \u0434\u043e\u0447\u0438\u0442\u0430\u043b\u0438.<\/p>\n<p>\u0412\u0441\u0435\u043c \u043c\u0438\u0440 \u2665\ufe0f<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p> <!----> <!----><\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/704672\/\"> https:\/\/habr.com\/ru\/post\/704672\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0440\u043e \u0442\u043e, \u043a\u0430\u043a \u0437\u0430 \u043a\u0443\u043b\u0438\u0441\u0430\u043c\u0438 \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430 \u043c\u043e\u0435\u0433\u043e \u043c\u0438\u043d\u0438-\u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043f\u043e \u0432\u0435\u0434\u0435\u043d\u0438\u044e \u0437\u0430\u0434\u0430\u0447 <a href=\"https:\/\/autofocus.su\/iFa\" rel=\"noopener noreferrer nofollow\">autofocus.su<\/a>. \u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 <a href=\"https:\/\/habr.com\/ru\/post\/702668\/\" rel=\"noopener noreferrer nofollow\">\u0437\u0430\u043c\u0435\u0442\u043a\u0435<\/a> \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u043b \u043f\u0440\u043e \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b, \u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0432 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u0410\u0432\u0442\u043e\u0444\u043e\u043a\u0443\u0441\u0430. \u0410 \u0442\u0443\u0442 \u0431\u0443\u0434\u0435\u0442 \u0441\u043a\u043e\u0440\u0435\u0435 \u043d\u0430\u0431\u043e\u0440 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u0441 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u043c\u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f\u043c\u0438 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0438 \u043a\u0430\u043a \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u043c\u0435\u0436\u0434\u0443 \u0441\u043e\u0431\u043e\u0439. \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442\u00a0\u043e\u0442\u043b\u0438\u0447\u0430\u0442\u044c\u0441\u044f \u0432 \u0432\u0430\u0448\u0435\u043c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043d\u043e \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u0432 \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043d\u044f\u0442\u043d\u044b.<\/p>\n<p>\u041b\u0438\u0447\u043d\u043e \u043c\u043d\u0435 \u0447\u0430\u0441\u0442\u043e \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u043a\u0430\u043a\u043e\u0433\u043e-\u0442\u043e \u0441\u043a\u0435\u043b\u0435\u0442\u0430 \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u0441 \u0447\u0435\u0433\u043e \u043d\u0430\u0447\u0430\u0442\u044c. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0447\u0442\u043e \u0431\u0443\u0434\u0443 \u043f\u043e\u043b\u0435\u0437\u0435\u043d.<\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0431\u044d\u043a\u0435\u043d\u0434\u0430.<\/p>\n<h2>Django<\/h2>\n<p>\u0412 \u043e\u0441\u043d\u043e\u0432\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u2014 <a href=\"https:\/\/www.djangoproject.com\" rel=\"noopener noreferrer nofollow\">Django<\/a>. \u041a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u0434\u043b\u044f \u043a\u043e\u0441\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0437\u0432\u0435\u0437\u0434\u043e\u043b\u0435\u0442\u043e\u0432. \u041e\u0447\u0435\u043d\u044c \u043c\u043d\u043e\u0433\u043e \u0432\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u0438\u044f \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0438 \u043d\u044e\u0430\u043d\u0441\u0430\u043c \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 <a class=\"mention\" href=\"\/users\/kesn\">@kesn<\/a> \u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0435\u043c\u0443 \u0437\u0430 \u044d\u0442\u043e!<\/p>\n<p>\u0412 \u0414\u0436\u0430\u043d\u0433\u043e \u0443 \u043c\u0435\u043d\u044f \u0441\u0435\u0439\u0447\u0430\u0441 \u0442\u0440\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2014 \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u0447, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0445 \u0441\u0441\u044b\u043b\u043e\u043a. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u044f \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043b \u0441\u0432\u043e\u0438\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c User \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445:<\/p>\n<pre><code class=\"python\">AUTH_USER_MODEL = 'accounts.User'<\/code><\/pre>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u043f\u0440\u043e\u0447\u0435\u0433\u043e, \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043f\u043e\u0447\u0442\u044b \u0438 \u0443\u0431\u0440\u0430\u043b \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f:<\/p>\n<pre><code class=\"python\">email = models.EmailField(primary_key=True) password = None REQUIRED_FIELDS = [] USERNAME_FIELD = 'email'<\/code><\/pre>\n<p>\u042d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 Django-sesame.<\/p>\n<h2>django-sesame<\/h2>\n<p><a href=\"https:\/\/django-sesame.readthedocs.io\" rel=\"noopener noreferrer nofollow\">\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f<\/a> \u0432 Django \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u044b\u0445 \u0441\u0441\u044b\u043b\u043e\u043a. \u041f\u0440\u043e\u0441\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0435 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442, \u0434\u0435\u043b\u0430\u0435\u0442\u0435 \u043f\u044f\u0442\u043e\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0442\u044c \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435\u043b\u044f\u043c \u0441\u0441\u044b\u043b\u043a\u0438, \u043f\u0435\u0440\u0435\u0439\u0434\u044f \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043e\u043d\u0438 \u0441\u043c\u043e\u0433\u0443\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f:<\/p>\n<pre><code>https:\/\/example.com\/sesame\/login\/?sesame=zxST9d0XT9xgfYLvoa9e2myN<\/code><\/pre>\n<p>\u0412\u0438\u0434 \u0441\u0441\u044b\u043b\u043a\u0438, \u0441\u0440\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043d\u044e\u0430\u043d\u0441\u044b \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c. \u041f\u043e\u0441\u043b\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043c\u0435\u0447\u0430\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043a\u0430\u043a \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0432\u0448\u0438\u0445 \u0441\u0432\u043e\u0439 \u0430\u0434\u0440\u0435\u0441 \u043f\u043e\u0447\u0442\u044b.<\/p>\n<h2>dotenv<\/h2>\n<p><a href=\"https:\/\/saurabh-kumar.com\/python-dotenv\/\" rel=\"noopener noreferrer nofollow\">\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/a> \u0434\u043b\u044f python, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0447\u0438\u0442\u0430\u0435\u0442 \u0444\u0430\u0439\u043b <code>.env<\/code> \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u044b. \u0410 \u0432\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0435 \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0432 <code>.gitignore<\/code> \u0438 \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b. \u0420\u0435\u0448\u0430\u0435\u0442 \u043a\u0443\u0447\u0443 \u0433\u043e\u043b\u043e\u0432\u043d\u043e\u0439 \u0431\u043e\u043b\u0438 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u043e\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044e. \u0412 <code>settings.py<\/code> \u043f\u0438\u0448\u0435\u043c:<\/p>\n<pre><code class=\"python\">import dotenv  dotenv_file = BASE_DIR \/ \".env\" if os.path.isfile(dotenv_file): \u00a0 \u00a0 dotenv.load_dotenv(dotenv_file)<\/code><\/pre>\n<p>\u0410 \u0434\u0430\u043b\u044c\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e:<\/p>\n<pre><code class=\"python\">DEBUG = os.environ.get('DJANGO_DEBUG', default=False) in [ \u00a0 \u00a0 'True', 'true', '1', True]<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 <code>.env<\/code> \u0441\u0442\u0440\u043e\u043a\u0443<\/p>\n<pre><code>DJANGO_DEBUG=1<\/code><\/pre>\n<p>\u0422\u043e <code>DEBUG<\/code> \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0432\u0435\u043d <code>True<\/code><\/p>\n<h2>graphene-django<\/h2>\n<p><a href=\"https:\/\/docs.graphene-python.org\/projects\/django\/en\/latest\/installation\/\" rel=\"noopener noreferrer nofollow\">\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/a> \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0410\u041f\u0418 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u044f\u0437\u044b\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 <a href=\"https:\/\/graphql.org\" rel=\"noopener noreferrer nofollow\">GraphQL<\/a>. \u0415\u0433\u043e \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u043b\u0438 \u0432 Facebook (\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438, \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u043e\u0439 \u043d\u0430 \u0442\u0435\u0440\u0440\u0438\u0442\u043e\u0440\u0438\u0438 \u0420\u043e\u0441\u0441\u0438\u0438). \u041e\u043d\u00a0 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a \u0410\u041f\u0418 \u043d\u0430 \u043e\u0434\u043d\u0443 \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0443\u0436\u043d\u044b, \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044f \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code>query UserItems \u00a0 \u00a0 { \u00a0 \u00a0 \u00a0 \u00a0 user { \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 pages \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 page \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 email \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 isValidated \u00a0 \u00a0 \u00a0 \u00a0 } \u00a0 \u00a0 \u00a0 \u00a0 items { \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 text \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 id \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 repeats \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 state \u00a0 \u00a0 \u00a0 \u00a0 } \u00a0 \u00a0 }<\/code><\/pre>\n<p>\u0418 \u0432 \u043e\u0442\u0432\u0435\u0442 \u043f\u0440\u0438\u0434\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 <code>User<\/code> \u0438 \u043c\u0430\u0441\u0441\u0438\u0432 <code>Items<\/code> \u0441 \u0437\u0430\u0434\u0430\u0447\u043a\u0430\u043c\u0438. \u0410 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0443\u0442\u0430\u0446\u0438\u0439:<\/p>\n<pre><code>mutation addItem($text: String!){ \u00a0 \u00a0 createItem(text: $text){ \u00a0 \u00a0 \u00a0 \u00a0 user { \u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 email \u00a0 \u00a0 \u00a0 \u00a0 } \u00a0 \u00a0 } \u00a0 }<\/code><\/pre>\n<p>\u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u044d\u0442\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0440\u044f\u043c \u0441 \u0434\u0432\u0443\u0445 \u0441\u0442\u0440\u043e\u043a. \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0430\u0448\u0430 \u0442\u043e\u0447\u043a\u0430 graphql \u0438 \u043a\u0430\u043a \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043c\u0443\u0442\u0430\u0446\u0438\u0438. \u0421\u043f\u0435\u0440\u0432\u0430 \u043c\u0435\u043d\u044f graphql \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0443\u0433\u0430\u043b, \u0430 \u043f\u043e\u0442\u043e\u043c \u0440\u0430\u0441\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b.<\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0444\u0440\u043e\u043d\u0442\u0443.<\/p>\n<h2>Vue.js<\/h2>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u0434\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0432\u044b\u0431\u0440\u0430\u043b <a href=\"https:\/\/vuejs.org\" rel=\"noopener noreferrer nofollow\">Vue.js<\/a> \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u043e\u0447\u0435\u043c\u0443 \u0431\u044b \u0438 \u043d\u0435\u0442. \u0421\u0434\u0435\u043b\u0430\u043b \u0432\u0435\u0440\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0441 Typescript. \u041d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043f\u0430\u043f\u043a\u0435 frontend \u0440\u044f\u0434\u043e\u043c \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438 Django:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0418\u0437 \u0432\u0430\u0436\u043d\u044b\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0443\u043a\u0430\u0437\u0430\u043b \u0440\u0430\u0437\u043d\u044b\u0435 \u043f\u0430\u043f\u043a\u0438 \u0434\u043b\u044f <code>production<\/code> \u0438 <code>development<\/code>:<\/p>\n<pre><code class=\"javascript\">outputDir: process.env.NODE_ENV === \"production\" ? \"dist\" : \"static\"<\/code><\/pre>\n<p>\u0418 \u0442\u043e, \u043a\u0443\u0434\u0430, \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b:<\/p>\n<pre><code class=\"javascript\">configureWebpack: {     output: {       filename:         process.env.NODE_ENV === \"production\"           ? \"..\/..\/static\/js\/[name].js\"           : \"static\/js\/[name].js\",       chunkFilename:         process.env.NODE_ENV === \"production\"           ? \"..\/..\/static\/js\/[name].js\"           : \"static\/js\/[name].js\",     },     plugins: [       new WriteFilePlugin(),       process.env.NODE_ENV === \"production\"         ? new BundleTracker({             filename: \"webpack-stats-prod.json\",             publicPath: \"\/\",           })         : new BundleTracker({             filename: \"webpack-stats.json\",             publicPath: \"http:\/\/localhost:8080\/\",           }),     ],   },<\/code><\/pre>\n<p>\u0410 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0440\u0443\u0436\u0438\u0442\u044c \u0432\u0441\u0435 \u044d\u0442\u043e \u0441 Django \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u0441\u044f:<\/p>\n<h2>webpack_loader<\/h2>\n<p><a href=\"https:\/\/django-webpack-loader.readthedocs.io\/en\/latest\/\" rel=\"noopener noreferrer nofollow\">\u041f\u043b\u0430\u0433\u0438\u043d<\/a> \u043e\u0442 Jazzband, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0430\u043d\u0434\u043b\u044b Webpack \u0432 \u0448\u0430\u0431\u043b\u043e\u043d\u0430\u0445:<\/p>\n<pre><code class=\"xml\">{% extends \"base.html\" %}  {% load render_bundle from webpack_loader %}  {% block title %}   Autofocus {% endblock title %}  {% block head %}   {{ block.super }} {% endblock head %}      {% block body %}   &lt;div id=\"app\">&lt;\/div> {% endblock body %}  {% block script %}   {% render_bundle 'app' %} {% endblock script %}<\/code><\/pre>\n<h2>Vue Apollo<\/h2>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0440\u0443\u0436\u0438\u0442\u044c Vue.JS \u0441 GraphQL \u0435\u0441\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/v4.apollo.vuejs.org\" rel=\"noopener noreferrer nofollow\">Vue Apollo<\/a>. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0435\u043c \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u043a\u0438\u043d\u0443\u0442\u044c x-<code>csrftoken<\/code>. \u0415\u0433\u043e \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u044b\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0435\u043c \u0438\u0437 \u043a\u0443\u043a \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043f\u0430\u043a\u0435\u0442 <code>js-cookie<\/code>:<\/p>\n<pre><code class=\"typescript\">import { createApp, provide, h } from \"vue\"; import App from \".\/App.vue\"; import \".\/registerServiceWorker\"; import { DefaultApolloClient } from \"@vue\/apollo-composable\"; import { ApolloClient, InMemoryCache, createHttpLink } from \"@apollo\/client\/core\"; import Cookies from \"js-cookie\";  const cache = new InMemoryCache();  const link = createHttpLink({ \u00a0 \u00a0 uri: \"\/graphql\", \u00a0 \u00a0 headers: { \u00a0 \u00a0 \u00a0 \u00a0 \"x-csrftoken\": Cookies.get(\"csrftoken\") \u00a0 \u00a0 } });  const apolloClient = new ApolloClient({ \u00a0 \u00a0 cache, \u00a0 \u00a0 link: link, });  const app = createApp({ \u00a0 \u00a0 setup() { \u00a0 \u00a0 \u00a0 \u00a0 provide(DefaultApolloClient, apolloClient); \u00a0 \u00a0 },  \u00a0 \u00a0 render: () => h(App), });  app.mount(\"#app\");<\/code><\/pre>\n<h2>Tailwind CSS<\/h2>\n<p>\u0414\u043b\u044f css \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <a href=\"https:\/\/tailwindcss.com\" rel=\"noopener noreferrer nofollow\">Tailwind css<\/a>. \u0415\u0433\u043e \u043f\u0440\u0435\u043b\u0435\u0441\u0442\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e, \u0432\u043e-\u043f\u0435\u0440\u0432\u044b\u0445,\u00a0\u0442\u0430\u043c \u0435\u0441\u0442\u044c \u0445\u043e\u0440\u043e\u0448\u043e \u043f\u0440\u043e\u0434\u0443\u043c\u0430\u043d\u043d\u0430\u044f \u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043a\u043b\u0430\u0441\u0441\u043e\u0432. \u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0445 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0438 \u0442\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u043d\u0435 \u0432\u0441\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443, \u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435 \u043a\u043b\u0430\u0441\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u0434:<\/p>\n<pre><code class=\"css\">.page__current {     @apply bg-slate-800 dark:bg-slate-400;     @apply text-white dark:text-slate-700;     @apply rounded-full;     @apply cursor-default;   }<\/code><\/pre>\n<p>\u0410 \u043d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f:<\/p>\n<pre><code class=\"css\">.page__current {   --tw-bg-opacity: 1;   background-color: rgb(30 41 59 \/ var(--tw-bg-opacity)); }  @media (prefers-color-scheme: dark) {   .page__current {     --tw-bg-opacity: 1;     background-color: rgb(148 163 184 \/ var(--tw-bg-opacity));   } }  .page__current {   --tw-text-opacity: 1;   color: rgb(255 255 255 \/ var(--tw-text-opacity)); }  @media (prefers-color-scheme: dark) {   .page__current {     --tw-text-opacity: 1;     color: rgb(51 65 85 \/ var(--tw-text-opacity));   } }  .page__current {   border-radius: 9999px;   cursor: default; }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c, \u0435\u0441\u043b\u0438 \u043a\u043b\u0430\u0441\u0441 \u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f, \u0442\u043e \u043e\u043d \u043d\u0435 \u043f\u043e\u043f\u0430\u0434\u0435\u0442 \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0444\u0430\u0439\u043b. \u0423\u043a\u0430\u0437\u0430\u0442\u044c, \u0433\u0434\u0435 \u0438 \u043a\u0430\u043a\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u0432 tailwind.config.js:<\/p>\n<pre><code class=\"typescript\">\/** @type {import('tailwindcss').Config} *\/  module.exports = { \u00a0 content: [ \u00a0 \u00a0 \".\/focus\/**\/*.{html,js}\", \u00a0 \u00a0 \".\/accounts\/**\/*.{html,js}\", \u00a0 \u00a0 \".\/frontend\/src\/**\/*.{html,vue,js}\" \u00a0 ], \u00a0 theme: { \u00a0 \u00a0 extend: {}, \u00a0 \u00a0 fontFamily: { \u00a0 \u00a0 \u00a0 sans: [\"PT Sans\", \"sans-serif\"], \u00a0 \u00a0 }, \u00a0 }, \u00a0 plugins: [require(\"@tailwindcss\/typography\"), require(\"@tailwindcss\/forms\")], };<\/code><\/pre>\n<p>\u0412\u043e\u043e\u0431\u0449\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Tailwind \u043f\u0440\u044f\u043c\u043e \u0432\u043e Vue.js, \u043d\u043e \u043f\u043e\u043a\u0430 \u043d\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0441\u044f, \u043a\u0430\u043a \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c. \u0425\u043e\u0442\u044f \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0443\u0434\u043e\u0431\u043d\u0435\u0435: \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0438\u043b\u0438 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438.<\/p>\n<h2>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h2>\n<p>\u0414\u043b\u044f \u044e\u043d\u0438\u0442-\u0442\u0435\u0441\u0442\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <code>TestCase<\/code> \u0441\u0430\u043c\u043e\u0439 Django. \u0415\u0449\u0435 \u0435\u0441\u0442\u044c \u043f\u0430\u043f\u043a\u0430 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c <code>functional_tests<\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b, \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 Selenium+Chromedriver. \u0412 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u0430\u0445 \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u0432\u0435\u0441\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041f\u043b\u044e\u0441 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043e\u0448\u0438\u0431\u043e\u043a.\u00a0<\/p>\n<p>\u041f\u043e\u043a\u0430 \u043d\u0435 \u043f\u043e\u0434\u0441\u0442\u0443\u043f\u0430\u043b\u0441\u044f \u043a \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u043a\u0430\u0442\u043a\u0438 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440. \u0412\u0440\u043e\u0434\u0435 \u0435\u0441\u0442\u044c <a href=\"https:\/\/mailtrap.io\" rel=\"noopener noreferrer nofollow\">Mailtrap<\/a>, \u043d\u043e \u0440\u0443\u043a\u0438 \u043f\u043e\u043a\u0430 \u043d\u0435 \u0434\u043e\u0448\u043b\u0438. \u041f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u2014 \u043f\u043e\u0434\u043c\u0435\u043d\u044f\u044e \u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0430 <code>console.EmailBackend<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0435\u0447\u0430\u0442\u0430\u0435\u0442 \u043f\u043e\u0447\u0442\u0443 \u043f\u0440\u044f\u043c\u043e \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c:<\/p>\n<pre><code class=\"python\">if DEBUG: \u00a0 \u00a0 EMAIL_BACKEND = \"django.core.mail.backends.console.EmailBackend\" else: \u00a0 \u00a0 EMAIL_USE_TLS = True \u00a0 \u00a0 EMAIL_HOST = \u2018***\u2019 \u00a0 \u00a0 EMAIL_PORT = 587 \u00a0 \u00a0 EMAIL_HOST_USER = \u2018***\u2019 \u00a0 \u00a0 EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '') \u00a0 \u00a0 DEFAULT_FROM_EMAIL = \u2018***\u2019<\/code><\/pre>\n<p>\u0418 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u044e \u0435\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>django.core.mail<\/code>. \u0422\u0430\u043c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0438\u0441\u044c\u043c\u0430 \u0438 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0438\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435:<\/p>\n<pre><code class=\"python\"># \u0410 \u0432 \u043f\u043e\u0447\u0442\u043e\u0432\u043e\u043c \u044f\u0449\u0438\u043a\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0438\u0441\u044c\u043c\u043e self.assertEqual(len(mail.outbox), 1)  # \u0412 \u043f\u0438\u0441\u044c\u043c\u0435 \u0435\u0441\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0432\u0445\u043e\u0434 email_text = mail.outbox[0].body link = re.search(r'(http.*)$', email_text, re.MULTILINE).group(1) self.assertIsNotNone(link)<\/code><\/pre>\n<p>Vue.JS \u043f\u043e\u043a\u0430 \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u044e.<\/p>\n<h2>Dock<\/h2>\n<p>\u0414\u043b\u044f \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <a href=\"https:\/\/dokku.com\" rel=\"noopener noreferrer nofollow\">Dokku<\/a>. \u042d\u0442\u043e \u043e\u043f\u0435\u043d\u0441\u043e\u0440\u0441\u043d\u044b\u0439 \u0430\u043d\u0430\u043b\u043e\u0433 <a href=\"https:\/\/www.heroku.com\" rel=\"noopener noreferrer nofollow\">Heroku<\/a> \u2014 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u0417\u0432\u0443\u0447\u0438\u0442 \u0441\u043b\u043e\u0436\u043d\u043e, \u0430 \u043d\u0430 \u0434\u0435\u043b\u0435 \u0432\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0443\u0448\u0438\u0442\u0435 \u0432\u0435\u0442\u043a\u0443 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043e\u043d\u043e \u0441\u0430\u043c\u043e \u0442\u0430\u043c \u0432\u0441\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f. \u0412 \u043f\u0435\u0440\u0432\u044b\u0439 \u0440\u0430\u0437 \u044f \u0441 \u044d\u0442\u0438\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u043c \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 <a class=\"mention\" href=\"\/users\/ohld\">@ohld<\/a><a href=\"https:\/\/habr.com\/ru\/post\/547488\/\" rel=\"noopener noreferrer nofollow\">\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u044b\u0439 \u041f\u0440\u043e\u0434\u0430\u043a\u0448\u043d-\u0440\u0435\u0434\u0438 \u0422\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u0431\u043e\u0442 \u043d\u0430 Django<\/a>.<\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 Django \u0432 Dokku \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0432 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<br \/><code>.buildpacks<\/code>\u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u043a\u0430\u043a\u043e\u0439 \u0441\u0431\u043e\u0440\u0449\u0438\u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c:<\/p>\n<pre><code>https:\/\/github.com\/heroku\/heroku-buildpack-python.git#v222<\/code><\/pre>\n<p><code>App.json<\/code> \u2014 \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>cron<\/code>: <\/p>\n<pre><code class=\"json\">{   \"formation\": {     \"web\": {       \"quantity\": 1     }   },   \"cron\": [     {       \"command\": \"python manage.py clean_users\",       \"schedule\": \"@daily\"     },     {       \"command\": \"cd af_dbt &amp;&amp; dbt deps &amp;&amp; dbt run\",       \"schedule\": \"@daily\"     }   ] }<\/code><\/pre>\n<p><code>Procfile<\/code> \u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0431\u0438\u043b\u0434\u0430:<\/p>\n<pre><code>release: python manage.py migrate --noinput &amp;&amp; python manage.py collectstatic --no-input web: gunicorn --bind :$PORT --workers 4 --worker-class uvicorn.workers.UvicornWorker autofocus.asgi:application<\/code><\/pre>\n<p><code>Runtime.txt<\/code> \u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e Python: <\/p>\n<pre><code>python-3.10.8<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0443\u0448\u0438\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u0432\u0441\u0435. Dokku \u0448\u0443\u0440\u0448\u0438\u0442 \u0438 \u0431\u0435\u0441\u0448\u043e\u0432\u043d\u043e \u0432\u044b\u043a\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u043f\u043e\u0432\u0435\u0440\u0445 \u0441\u0442\u0430\u0440\u043e\u0439.<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h2>Postrgres<\/h2>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043a\u0430\u043a\u0430\u044f-\u0442\u043e \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445. \u042f \u0432\u044b\u0431\u0440\u0430\u043b Postgres \u0438 \u043f\u043b\u0430\u0433\u0438\u043d \u043a Dokku <a href=\"https:\/\/github.com\/dokku\/dokku-postgres\" rel=\"noopener noreferrer nofollow\"><strong>dokku postgres<\/strong><\/a><strong>.<\/strong> \u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434 <code>create<\/code> \u0438 <code>link<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0435 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-342405","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/342405","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=342405"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/342405\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=342405"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=342405"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=342405"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}