<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Гимаев Наиль</title>
    <description>The latest articles on DEV Community by Гимаев Наиль (@gimntut).</description>
    <link>https://dev.to/gimntut</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F459529%2F359805f4-353e-4563-953b-c0afa079f4a7.jpg</url>
      <title>DEV Community: Гимаев Наиль</title>
      <link>https://dev.to/gimntut</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gimntut"/>
    <language>en</language>
    <item>
      <title>2. Celery</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Fri, 08 Aug 2025 07:19:29 +0000</pubDate>
      <link>https://dev.to/gimntut/2-celery-3d6g</link>
      <guid>https://dev.to/gimntut/2-celery-3d6g</guid>
      <description>&lt;h2&gt;
  
  
  Используйте transaction.on_commit
&lt;/h2&gt;

&lt;p&gt;В &lt;a href="https://github.com/tough-dev-school/education-backend/blob/71ec48e7f5285b6992d87a157fa2c88850f430c0/src/apps/users/tasks.py#L10" rel="noopener noreferrer"&gt;одном из файлов&lt;/a&gt; встречается использование &lt;code&gt;sleep&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@celery.task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users.rebuild_tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rebuild_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# preventing race condition when task looks for user which isn't saved into db yet
&lt;/span&gt;    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ❌
&lt;/span&gt;    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users.User&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;generate_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Нормально использовать &lt;code&gt;sleep&lt;/code&gt; после операции, которая требует ожидания, но здесь такого нет, а это уже плохо пахнет.&lt;/p&gt;

&lt;p&gt;Причина появления &lt;code&gt;sleep&lt;/code&gt; указана в комментарии. Как же так получается, что &lt;code&gt;student_id&lt;/code&gt; уже есть, а пользователя связанного с этим id в БД ещё нет? Всё дело в транзакциях. Пока транзакция не завершится, созданные ею изменения не видны.&lt;/p&gt;

&lt;p&gt;Рассмотрим следующий пример&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rebuild_tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ❌
&lt;/span&gt;    &lt;span class="nf"&gt;long_time_db_operation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;По умолчанию в django все http-запросы заворачиваются в транзакции. Это значит, что созданный пользователь виден только в представлении (view), которое его создала. Код за пределами представления не видит этого пользователя, пока не завершится транзакция. Для простоты, будем считать, что транзакция завершается, когда выполниться &lt;code&gt;return&lt;/code&gt;.&lt;br&gt;
Задача &lt;code&gt;rebuild_tags&lt;/code&gt; ставится в очередь раньше, чем будет выполнен &lt;code&gt;return&lt;/code&gt;, а значит возможна ситуация, когда задача выполнится раньше завершения транзакции. &lt;code&gt;sleep&lt;/code&gt; должен был решить эту проблему, но представим себе, что &lt;code&gt;long_time_db_operation&lt;/code&gt; может иметь переменную длительность в зависимости от загруженности базы данных, тогда в некоторых случаях длительность может быть больше одной секунды и тогда &lt;code&gt;sleep&lt;/code&gt; не поможет.&lt;/p&gt;

&lt;p&gt;Есть надёжный вариант&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rebuild_tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;
    &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ✅
&lt;/span&gt;    &lt;span class="nf"&gt;long_time_db_operation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;transaction.on_commit&lt;/code&gt; - принимает на вход функцию, которую нужно выполнить после завершения транзакции, поэтому у &lt;code&gt;.delay&lt;/code&gt; нет скобок&lt;br&gt;
Параметры задачи переезжают в метод &lt;code&gt;.s&lt;/code&gt;, это позволяет задаче запуститься с нужными параметрами.&lt;br&gt;
Таким образом, не важно сколько времени занимает транзакция, фоновая задача будет запущена, только после успешного завершения транзакции.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Offtop. С точки зрения TDD вариант со sleep правильный, т.к. TDD требует писать минимальный код, который будет проходить тесты. Но если написать тест, который проверяет, что задача не ставится в очередь при падении транзакции, то тогда без &lt;code&gt;on_commit&lt;/code&gt; не обойтись.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Используйте именованные аргументы
&lt;/h2&gt;

&lt;p&gt;Ещё посмотреть на события &lt;code&gt;celery&lt;/code&gt; во &lt;code&gt;flower&lt;/code&gt;, то информация о задаче может выглядеть &lt;code&gt;rebuild_tags args=(1) kwargs={}&lt;/code&gt;. Когда параметров много, это могло выглядеть так &lt;code&gt;args=(10, 1000, true, 1, 100)&lt;/code&gt;. Неизвестно что означают эти числа. Когда перед глазами сотни событий celery, это не очень хорошо. Исправить это очень легко, достаточно добавить &lt;code&gt;*,&lt;/code&gt; перед аргументами задачи. В нашем примере это будет так:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@celery.task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users.rebuild_tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rebuild_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;student_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# ✅
&lt;/span&gt;  &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Во flower это будет отображаться так &lt;code&gt;rebuild_tags args=() kwargs={"student_id": 1}&lt;/code&gt; - так намного понятней.&lt;/p&gt;

&lt;p&gt;Рассмотрим ещё один пример. Пусть в celery beat добавлена задача, которая должна отправить письма тем пользователям у которых сейчас 8 утра по их местному времени.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@celery.task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Рассылка&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_mails&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# ❌
&lt;/span&gt;  &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Найти пользователей, у сейчас которых подходящее локальное время
&lt;/span&gt;  &lt;span class="c1"&gt;# Странная, но необходимая проверка, которая мешает тестированию
&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Такую задачу можно тестировать вручную лишь одну минуту в сутки, в остальное время она не срабатывает. Перепишем задачу так, чтобы сохранить её функционал.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@celery.task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Рассылка&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_mails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# ✅
&lt;/span&gt;  &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Найти пользователей, у сейчас которых подходящее локальное время
&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Странная проверка, которая мешает тестированию
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Теперь, можно задавать именованные аргументы через админку &lt;code&gt;celery beat&lt;/code&gt; в блоке Arguments конкретной задачи в формате json: &lt;code&gt;{"hour":17, "minute":47}&lt;/code&gt;. Так можно тестировать задачу в любое время.&lt;/p&gt;

</description>
      <category>django</category>
      <category>celery</category>
    </item>
    <item>
      <title>1. Тесты</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Fri, 08 Aug 2025 07:19:16 +0000</pubDate>
      <link>https://dev.to/gimntut/1-tiesty-1679</link>
      <guid>https://dev.to/gimntut/1-tiesty-1679</guid>
      <description>&lt;h2&gt;
  
  
  Быстрая обратная связь от тестов
&lt;/h2&gt;

&lt;p&gt;Начнём с &lt;a href="https://github.com/tough-dev-school/education-backend/blob/71ec48e7f5285b6992d87a157fa2c88850f430c0/Makefile" rel="noopener noreferrer"&gt;Makefile&lt;/a&gt;.&lt;br&gt;
Часть касающаяся тестов выглядит так&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;manage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; poetry run python src/manage.py
&lt;span class="nv"&gt;SIMULTANEOUS_TEST_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4

&lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;cd &lt;/span&gt;src &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; poetry run pytest &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="p"&gt;${&lt;/span&gt;SIMULTANEOUS_TEST_JOBS&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'not single_thread'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;make test&lt;/code&gt; - Запустит все тесты в 4 потока.&lt;br&gt;
Тут не ошибок, но этот код можно улучшить. &lt;br&gt;
Во-первых, сделаем &lt;code&gt;SIMULTANEOUS_TEST_JOBS=auto&lt;/code&gt; - это позволит ускорится тестам на компьютерах с большим числом процессоров, при этом однопроцессорные машины не будут захлёбываться от четырёх параллельных процессов.&lt;br&gt;
Во-вторых, стоит добавить ключ &lt;code&gt;--ff&lt;/code&gt;. Если тесты падают, то следующий прогон начнётся с упавших тестов. Не придётся ждать, чтобы понять исправились они или нет.&lt;br&gt;
В третьих, стоит добавить библиотеку &lt;a href="https://pypi.org/project/pytest-testmon/" rel="noopener noreferrer"&gt;pytest-testmon&lt;/a&gt;, с ней окончательный вариант будет выглядеть так:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;manage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; poetry run python src/manage.py
&lt;span class="nv"&gt;SIMULTANEOUS_TEST_JOBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto

&lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;cd &lt;/span&gt;src &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; poetry run pytest &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="p"&gt;${&lt;/span&gt;SIMULTANEOUS_TEST_JOBS&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'not single_thread'&lt;/span&gt; &lt;span class="nt"&gt;--ff&lt;/span&gt; &lt;span class="nt"&gt;--testmon&lt;/span&gt;

&lt;span class="nl"&gt;testmon&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;cd &lt;/span&gt;src &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; poetry run pytest &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="p"&gt;${&lt;/span&gt;SIMULTANEOUS_TEST_JOBS&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--ff&lt;/span&gt; &lt;span class="nt"&gt;--testmon&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;testmon&lt;/code&gt; собирает в БД связи между кодом и тестами, которые его покрывают, в локальную БД. Не забудьте добавить БД в &lt;code&gt;.gitignore&lt;/code&gt;.&lt;br&gt;
Если запустить команду &lt;code&gt;pytest --testmon&lt;/code&gt;, то будут запущены только те тесты, которые относятся к изменившемуся коду. Это быстрее, чем запускать весь код. И надёжнее чем запускать единственный тест, который, как вам кажется, относится к вносимым вами изменениям.&lt;br&gt;
Нужно учитывать одну особенность &lt;code&gt;testmon&lt;/code&gt;, если задан ключ &lt;code&gt;-m&lt;/code&gt;, то выполняются тесты подходящие под условие ключа, а не тесты текущих изменений. Именно поэтому в &lt;code&gt;Makefile&lt;/code&gt; теперь 2 команды.&lt;br&gt;
&lt;code&gt;make test&lt;/code&gt; - нужно запускать перед началом работы, чтобы наполнить БД testmon связями. И после окончания работы, чтобы проверить, что все тесты отрабатывают.&lt;br&gt;
&lt;code&gt;make testmon&lt;/code&gt; - можно запускать после каждого изменения кода. Это можно делать часто, так как запускаться будет, только малая часть тестов.&lt;br&gt;
Ключ &lt;code&gt;-x&lt;/code&gt;, нужен чтобы не ждать окончания тестов, если найдена проблема&lt;br&gt;
Ключ &lt;code&gt;-l&lt;/code&gt;, нужен чтобы в консоли отображались значения переменных, что упрощает исправление ошибки без отладчика.&lt;/p&gt;
&lt;h2&gt;
  
  
  Не держите фикстуры в файле тестов
&lt;/h2&gt;

&lt;p&gt;В одном файле расположена &lt;a href="https://github.com/tough-dev-school/education-backend/blob/master/src/apps/orders/tests/orders/services/tests_order_refunder.py#L116" rel="noopener noreferrer"&gt;фикстура&lt;/a&gt; &lt;code&gt;paid_order&lt;/code&gt; и &lt;a href="https://github.com/tough-dev-school/education-backend/blob/master/src/apps/orders/tests/orders/services/tests_order_refunder.py#L278" rel="noopener noreferrer"&gt;тест&lt;/a&gt; &lt;code&gt;test_break_if_current_user_could_not_be_captured&lt;/code&gt;, который её использует.&lt;br&gt;
Взглянем на тест поближе&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_break_if_current_user_could_not_be_captured&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# ❌
&lt;/span&gt;    &lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps.orders.services.order_refunder.get_current_user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid_order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paid_order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тест проверяет, что код падает с ошибкой, если пользователь которому нужно сделать возврат равен &lt;code&gt;None&lt;/code&gt;.&lt;br&gt;
Действительно, если добавить фикстуру &lt;code&gt;paid_order&lt;/code&gt; в аргументы теста, то произойдёт ошибка. Только это будет другая ошибка. Откуда же взялся &lt;code&gt;AttributeError&lt;/code&gt;? Он возникает здесь: &lt;code&gt;paid_order.price&lt;/code&gt; - это попытка обратиться к полю &lt;code&gt;price&lt;/code&gt; функции (а не фикстуры) &lt;code&gt;paid_order&lt;/code&gt;. До вызова &lt;code&gt;refund&lt;/code&gt; дело даже не доходит, а значит тест не проверяет то, что должен был. А всё потому, что IDE видит эту функцию и не подсвечивает проблему.&lt;br&gt;
Чтобы такого не происходило, нужно держать все фикстуры в файле &lt;code&gt;conftest.py&lt;/code&gt; рядом с файлом теста. Так как фикстуры не импортируются, то они не видны IDE, пока не будут переданы в тест.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Offtop. В TDD такая ошибка в принципе не могла возникнуть, т.к. нельзя подглядеть как будет работать код и скопировать ошибку в тест. Ожидаемый результат приходится записывать заранее, до того как он появится в коде.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Держите фикстуры ближе к тестам
&lt;/h2&gt;

&lt;p&gt;Хотя django создаёт для каждого приложения свой файл &lt;code&gt;test.py&lt;/code&gt;, но лучше делать отдельную папку с тестами, так как желательно, чтобы на рабочие сервера попадали только необходимые файлы, а файлы тестов и документации к таким не относятся.&lt;br&gt;
Допустим у вас все тесты лежат в одной папке tests, в ней лежат папки для приложений django, а уже в них папки с тестами разбитыми по функциональности. В каждой из папок могут находится файлы &lt;code&gt;conftest.py&lt;/code&gt;.&lt;br&gt;
Когда pytest ищет фикстуру, сначала она ищет фикстуру в том же классе, где  находится тест, если класс есть, потом в том же файле, потом в &lt;code&gt;conftest.py&lt;/code&gt; в той же папке, потом &lt;code&gt;conftest.py&lt;/code&gt; родительских папок до корня тестов.&lt;br&gt;
Где расположить фикстуру &lt;code&gt;user&lt;/code&gt;, которая нужна во всех тестах? Можно сделать вывод, что если писать на уровне приложений или уровне функциональности, то нарушается принцип DRY, и лучше расположить фикстуру в корень тестов. В реальности, общая для всех тестов фикстура, начинает со временем приносить проблемы, так как её будет очень сложно менять. Другими словами, корень тестов хорошее место только для фикстур, которые не меняются, вроде подключения к БД.&lt;/p&gt;

&lt;p&gt;Мой алгоритм поиска места для фикстуры.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Сначала фикстуру можно поместить в &lt;code&gt;conftest.py&lt;/code&gt; в той же папке, где находится тест. Это лучшее место для неё. Не важно, что для соседней функциональности или приложения написана точно такая же фикстура.&lt;/li&gt;
&lt;li&gt;Если мне нужно, чтобы в разных файлах одной папки или в разных тестах одного файла фикстура с одним именем обозначала разное, то оборачиваю тесты в классы и добавляю фикстуру в них.&lt;/li&gt;
&lt;li&gt;Если тестов слишком много, то выношу их в отдельную папку со своим &lt;code&gt;conftest.py&lt;/code&gt;
Таким образом, фикстура может находится в одном из 2х мест или в одном классе с тестом, или в &lt;code&gt;conftest.py&lt;/code&gt; в одной папке с тестом.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Не пользуйтесь одноимённым наследованием фикстур
&lt;/h2&gt;

&lt;p&gt;В последних версиях Pycharm сломалась навигация по одноименным фикстурам с наследованием, а инструменты вроде поиска не удобны, так как из-за предыдущего совета одноимённые фикстуры встречаются часто.&lt;/p&gt;

&lt;p&gt;Наследованием фикстур будем называть фикстуру, которая на отдаёт на выходе ту же фикстуру, которую получает на вход. Одноимённое наследование, это когда фикстура на вход получает фикстуру с таким же именем. Такое часто можно встретить в статьях по pytest.&lt;br&gt;
Вот так выглядит пример на django:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLastLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# ❌
&lt;/span&gt;    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Можно было бы назвать дочернюю фикстуру по другому, это упростило бы чтение кода, но само по себе наследование фикстур опасно, так как приводит к сайд эффектам&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLastLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;  &lt;span class="c1"&gt;# ❌
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Этот тест упадёт, т.к. фикстура user_no_staff повлияла на фикстуру user, и это поломало тест.&lt;/p&gt;

&lt;p&gt;Можно воспользоваться трюком&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLastLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user_no_staff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;  &lt;span class="c1"&gt;# ✅
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Это уже не наследование, а значит сайд-эффекта уже не будет.&lt;br&gt;
Но лучше вообще не использовать наследование. Так тест читается максимально легко.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLastLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;  &lt;span class="c1"&gt;# ✅
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user_no_staff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;  &lt;span class="c1"&gt;# ✅ 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>pytest</category>
    </item>
    <item>
      <title>0. Вводная</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Thu, 07 Aug 2025 15:27:57 +0000</pubDate>
      <link>https://dev.to/gimntut/0-vvodnaia-1mek</link>
      <guid>https://dev.to/gimntut/0-vvodnaia-1mek</guid>
      <description>&lt;p&gt;В начале своей карьеры я ориентировался на проект &lt;a href="https://github.com/tough-dev-school/education-backend" rel="noopener noreferrer"&gt;education-backend&lt;/a&gt;, как на образец best practis. Проект коммерческий, т.е. приносит автору деньги. &lt;a href="https://borshev.com/" rel="noopener noreferrer"&gt;Фёдор Борщёв&lt;/a&gt;, автор проекта, часто выступает с докладами о том как правильно делать проекты на django. Так что код достаточно качественный.&lt;/p&gt;

&lt;p&gt;Мне захотелось понять, насколько я стал опытнее и провёл ревью проекта. Чем дальше я анализирую код, тем больше вижу ошибок, которые могли бы не возникнуть, если бы соблюдались некоторые правила.&lt;br&gt;
В общем, я решил записать для себя и для коллег правила, соблюдение которых позволит совершать меньше подобных ошибок.&lt;br&gt;
Заметки будут пополняться и корректироваться по мере обнаружения новых интересных моментов.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Прим.: Все фрагменты с правильными решениям написаны прямо в редакторе dev.to, по памяти, поэтому их работоспособность не гарантируется&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>DRF: SerializerMethodField vs DataSerializerField</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Tue, 04 Feb 2025 14:21:26 +0000</pubDate>
      <link>https://dev.to/gimntut/serializermethodfield-vs-dataserializerfield-2moh</link>
      <guid>https://dev.to/gimntut/serializermethodfield-vs-dataserializerfield-2moh</guid>
      <description>&lt;p&gt;Оставлю это здесь, как черновик.&lt;br&gt;
drf-spectacular плохо дружит с SerializerMethodField. Поэтому я написал свой класс поля.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeVar&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid4&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.fields&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.serializers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ListSerializer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Serializer&lt;/span&gt;

&lt;span class="n"&gt;SerializerType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TypeVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SerializerType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bound&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataSerializerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A serializer for calculated data&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__new__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SerializerType&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SerializerType&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetAttributeMixin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Field&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;TYPE_CHECKING&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_attrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;default_method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                    &lt;span class="n"&gt;attr_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;method_name&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;default_method_name&lt;/span&gt;
                    &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Method &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attr_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found in class &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

        &lt;span class="n"&gt;class_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Serializer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;DataSerializer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;list_serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DataListSerializer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetAttributeMixin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ListSerializer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="n"&gt;parent_meta_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Meta&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ref_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;meta_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Meta&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_meta_class&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;list_serializer_class&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;list_serializer_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ref_name&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;data_serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetAttributeMixin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Meta&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;meta_class&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;read_only&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;data_serializer_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Пример использования:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParentSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;serializer_field_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataSerializerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChildSerializer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;serializer_field_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataSerializerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChildSerializer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;many&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_serializer_field_object_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ChildObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ChildObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_serializer_field_list_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ChildObject&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ChildObject&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ChildObject&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Аннотацию результатов методов можно не писать, она влияет только на работу линтеров.&lt;/p&gt;

&lt;p&gt;Этот же код с SerializerMethodField выглядел бы так&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParentSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;serializer_field_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SerializerMethodField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;serializer_field_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SerializerMethodField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_serializer_field_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ChildSerializer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ChildSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChildObject&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_serializer_field_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ChildSerializer&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ChildSerializer&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;ChildObject&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ChildObject&lt;/span&gt;&lt;span class="p"&gt;()]).&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Обратите внимание на аннотацию результатов функций. Она нужна для того, чтобы spectacular-swagger мог правильно отобразить схему. При этом аннотация не соответствует реальному типу возвращаемых данных.&lt;/p&gt;

</description>
      <category>drf</category>
      <category>django</category>
    </item>
    <item>
      <title>Мои отношения с клавиатурой и типографикой</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Sat, 14 Dec 2024 07:48:01 +0000</pubDate>
      <link>https://dev.to/gimntut/moi-otnoshieniia-s-klaviaturoi-i-tipoghrafikoi-3jkh</link>
      <guid>https://dev.to/gimntut/moi-otnoshieniia-s-klaviaturoi-i-tipoghrafikoi-3jkh</guid>
      <description>&lt;p&gt;&lt;em&gt;Данный пост родился после прочтения комментариев к другому посту: &lt;a href="https://t.me/t0digital/1001" rel="noopener noreferrer"&gt;https://t.me/t0digital/1001&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Начну с маленькой новости. &lt;br&gt;
В стандартной раскладке есть символ ₽ — Right Alt + 8&lt;/p&gt;

&lt;p&gt;Я всю жизнь пользуюсь виндой, слепой печатью не владею. В основном работаю аккордами, в IDE — это Ctrl + Пробел. В текстовых редакторах — это стрелки в сочетании с клавишами Ctrl и Shift в разных комбинациях.&lt;/p&gt;

&lt;p&gt;Не могу работать на коротких клавиатурах. Даже если клавиатура длинная, но на ней сдвинута хотя бы одна одна клавиша, то работать не могу. Я использую все 100% клавиш. Даже Scroll Lock.&lt;br&gt;
Удивительно, но на столе шириной 1 метр хватает места для широкой клавиатуры и для мыши.&lt;/p&gt;

&lt;p&gt;В прошлом я был эникеем. Это значит, что я проводил 10% времени за чужими компьютерами. Это научило меня работать на дефолтных настройках и приложениях. Никаких vim-сочетаний или раскладок Бирмана на своём компьютере, иначе будешь страдать на чужих компьютерах.&lt;br&gt;
Ctrl+C и Ctrl+V работают везде. Ctrl+Shift+6 и Ctrl+NumPlus работают в любом проводнике.&lt;/p&gt;

&lt;p&gt;Как работать с типографикой, если всё дефолтное? Очень просто — Word. В Word очень не плохо с типографикой. А если чего-то не хватало, то можно было подкрутить функцию автозамены.&lt;/p&gt;

&lt;p&gt;А что, если хочется не Word, а блокнот? Тогда пользуемся функцией автозамены в &lt;a href="https://yandex.ru/soft/punto/win/" rel="noopener noreferrer"&gt;Punto Switcher&lt;/a&gt;.&lt;br&gt;
В Punto Switcher обязательно нужно отключить автозамену. Тогда он превращается из врага программиста в друга.&lt;br&gt;
Если нужно корректировать раскладку последнего слова жмём Pause, а для выделенного текста — Shift+Pause.&lt;/p&gt;

&lt;p&gt;А если нужна типографика для текстов от других людей, у которых нет раскладки Бирмана, то тут приходит на помощь «&lt;a href="https://www.artlebedev.ru/typograf/" rel="noopener noreferrer"&gt;Типограф от Артемия Лебедева&lt;/a&gt;»&lt;/p&gt;

&lt;p&gt;А если не хочется каждый раз ходить в типографику руками, то Артемий Лебедев предоставляет SOAP.&lt;br&gt;
Мыжпрограммисты. Пишем программу, которая берёт текст из буфера обмена, прогоняет через типографику и кладёт обратно в буфер обмена.&lt;br&gt;
Далее создаём ярлык на рабочем столе или в Пуск. Открываем его нажатием Alt+Enter. Задаём хоткей в поле «Быстрый запуск».&lt;br&gt;
И всё, жмём последовательно Ctrl+C, ваш-хоткей, Ctrl+V.&lt;/p&gt;

&lt;p&gt;Для совсем программистов есть &lt;a href="https://github.com/typograf/typograf" rel="noopener noreferrer"&gt;open source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Когда я только начинал работать в Word, я всегда начинал с оформления.&lt;br&gt;
В результате, получал идеально оформленный документ, в котором не было текста.&lt;br&gt;
Я успевал потерять мысль задолго до того, как успевал дописать.&lt;br&gt;
Поэтому мой любимый инструмент — это Sublime Text. Главное контент, а не оформление и не типографика.&lt;br&gt;
Когда текст завершён, можно будет думать о товарном виде.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Типизация в drf-spectacular</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Sat, 28 Sep 2024 11:15:05 +0000</pubDate>
      <link>https://dev.to/gimntut/tipizatsiia-v-drf-spectacular-3aad</link>
      <guid>https://dev.to/gimntut/tipizatsiia-v-drf-spectacular-3aad</guid>
      <description>&lt;p&gt;&lt;em&gt;Заранее прошу прощения, мне лень писать статью с скриншотами сваггера и с реальным кодом, поэтому пишу сразу без правок и вычиток. Если будет интерес, возможно, когда-нибудь оформлю в виде статьи на Хабре.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Этот пост - ответ на статью &lt;a href="https://habr.com/ru/companies/amvera/articles/843232/" rel="noopener noreferrer"&gt;https://habr.com/ru/companies/amvera/articles/843232/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Статья хороша. Вот только есть одно очень большое НО, и это - декораторы: extend_schema и т.п.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Декораторы - это плохо 🟧
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;drf-spectacular&lt;/code&gt; - это уже третья библиотека для swagger в проекте, который я веду. Сначала был &lt;a href="https://github.com/marcgibbons/django-rest-swagger" rel="noopener noreferrer"&gt;django-rest-swagger&lt;/a&gt; - в нём документация велась с помощь &lt;code&gt;doc-string&lt;/code&gt;. В какой-то момент эта библиотека передала эстафету библиотеке &lt;a href="https://github.com/axnsan12/drf-yasg" rel="noopener noreferrer"&gt;drf-yasg&lt;/a&gt; в ней уже были декораторы. Счастье было долгим, но нужна была поддержка OpenAPI v3. Всем желающим &lt;code&gt;drf-yasg&lt;/code&gt; предложил перейти на &lt;a href="https://pypi.org/project/drf-spectacular/" rel="noopener noreferrer"&gt;drf-spectacular&lt;/a&gt;. &lt;br&gt;
Мне дважды пришлось переписывать документацию к swagger и мне это не понравилось, т.к. проект большой.&lt;br&gt;
К счастью, &lt;code&gt;drf-spectacular&lt;/code&gt; (далее spectacular) спроектирован так, что если API написан правильно, то декораторы вообще не нужны.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. &lt;code&gt;GenericAPIView&lt;/code&gt; и &lt;code&gt;GenericViewSet&lt;/code&gt; - это хорошо ✅
&lt;/h2&gt;

&lt;p&gt;Достаточно унаследовать свои вьюхи от указанных классов и spectacular сам извлечёт необходимые сведения из &lt;code&gt;get_queryset&lt;/code&gt; и &lt;code&gt;get_serializer_class&lt;/code&gt;.&lt;br&gt;
Т.е. можно написать такой код и &lt;code&gt;swagger&lt;/code&gt; сформирует правильную документацию&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_serializer_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CreateSerializer&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retrive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RetriveSerializer&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_serializer_class&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Иногда декораторы - это необходимость ✅
&lt;/h2&gt;

&lt;p&gt;Возникает вопрос, зачем нужны декораторы, если &lt;code&gt;spectacular&lt;/code&gt; справляется сам. Иногда нужно писать API, которое берёт данные не из БД. Разного рода API-калькуляторы, или API-посредники, которые возвращают вычисленные данные или полученные из вне. В этом случае, приходится наследовать вьюху от &lt;code&gt;APIView&lt;/code&gt;. Тут без декоратора не обойтись.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;SerializerMethodField&lt;/code&gt; с простыми типами - это хорошо ✅
&lt;/h2&gt;

&lt;p&gt;Если поля имеют простые типы: &lt;code&gt;IntergerField&lt;/code&gt;, &lt;code&gt;CharField&lt;/code&gt;, то spectacular справляется очень хорошо. Но если используется &lt;code&gt;SerializerMethodField&lt;/code&gt;, то ему уже нужны подсказки. Для простых типов это просто, достаточно указать тип возвращаемой функции.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MySerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nb"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SerializerMethodField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Возвращаемый тип: int
&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Этот способ хорошо работает, даже если нужно вернуть список простых объектов, например &lt;code&gt;List[str]&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. SerializerMethodField для объектов - это плохо 🟧
&lt;/h2&gt;

&lt;p&gt;Со сложными объектами, не так всё просто. К примеру, у нас есть такой код&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 🟧
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExperimentSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DummySerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SerializerMethodField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Как spectacular должен догадаться, как называются поля и какого типа у них значения? Без выполнения кода это не возможно.&lt;br&gt;
Опытные разработчики могут догадаться, как ему подсказать&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ⁉
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EntityDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypedDict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExperimentSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SerializerMethodField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EntityDict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Такое работает, в swagger появится правильное описание. Но иногда нужно получить данные из другого сериализатора:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 🟧
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EntityDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypedDict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EntitySerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExperimentSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SerializerMethodField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EntityDict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EntitySerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Работает, но тут есть нарушение DRY. Попробуем по другому:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 🟧
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EntitySerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExperimentSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SerializerMethodField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EntitySerializer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EntitySerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;От дублирования избавились, swagger всё ещё работает, но тип метода &lt;code&gt;get_entity&lt;/code&gt; не соответствует возвращаемым данным.&lt;/p&gt;

&lt;p&gt;В общем, для своего проекта я &lt;a href="https://gist.github.com/gimntut/6e9241b7b4b92ee79de7fa8b3f88a8f4" rel="noopener noreferrer"&gt;написал класс DataSerializerField&lt;/a&gt;, который решает эту проблему.&lt;/p&gt;

&lt;p&gt;Пример использования:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ✅
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EntitySerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExperimentSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataSerializerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EntitySerializer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_entity_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Я мог бы написать ещё много о возможностях spectacular, но это как-нибудь в следующий раз.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Форматер black и миграции</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Mon, 04 Oct 2021 05:19:52 +0000</pubDate>
      <link>https://dev.to/gimntut/black-2dde</link>
      <guid>https://dev.to/gimntut/black-2dde</guid>
      <description>&lt;h3&gt;
  
  
  Описание проблемы
&lt;/h3&gt;

&lt;p&gt;С недавних пор PyCharm перестал ругаться на форматирование, которые получается при использовании форматёра black и я полностью отказался от встроенного форматера. Сделал всё как описано в &lt;a href="https://black.readthedocs.io/en/latest/integrations/editors.html#pycharm-intellij-idea"&gt;этой инструкции&lt;/a&gt;. Потом настроил CI, чтобы он не пропускал код не соответствующий формату black. А чтобы плохой код не доходил до CI, настроил precommit hook в git. Всё хорошо и замечательно до тех пор, пока дело не касается миграций. В обычном коде я постоянно жмякую хоткей, который приведёт код в порядок. Исправил строку - жмякнул. Но в автоматически созданных миграциях нет необходимости открывать файл и из-за них комиты не проходили. Сгенерировал миграцию, попытался сделать коммит, переформатировал код, сделал коммит. И эта последовательность действий повторялась снова и снова. Хотелось этого избежать.&lt;/p&gt;

&lt;h3&gt;
  
  
  Решение
&lt;/h3&gt;

&lt;p&gt;Я работаю в Windows. Естественно, мой код работает в docker'е. Каждый раз запуская контейнер приложения из консоли я монтирую домашнюю папку. В результате, &lt;strong&gt;сохраняется история&lt;/strong&gt; команд bash и django shell_plus между запусками контейнера.&lt;br&gt;
А раз есть домашняя папка, то можно сделать инициализацию псевдонимов (алиасов). Вот содержимое моего файла &lt;code&gt;.bashrc&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;BASHRC
m &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; ./manage.py &lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
m1 &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;MDO_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LOCAL m makemigrations &lt;span class="nv"&gt;$@&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; black .&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;m2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'MDO_DB=LOCAL m migrate'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;mm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'m1 &amp;amp;&amp;amp; m2'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;plus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'m shell_plus --print-sql'&lt;/span&gt;

&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;m1 - создание миграции&lt;/li&gt;
&lt;li&gt;m2 - применение миграций&lt;/li&gt;
&lt;li&gt;mm - создание и применение&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Кстати, о переменной MDO_DB. Я могу переключаться между разными БД: локальной, тестовой и даже продом. За это отвечает упомянутая переменная. К проду подключаться могу, но никогда этого не делаю, а вот тестовому серверу подключаюсь часто, когда нужно пройти отладчиком на данных тестовой среды. И чтобы миграция применялась &lt;strong&gt;только на локальной базе&lt;/strong&gt;, используется явное указание &lt;code&gt;MDO_DB=LOCAL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;После использования псевдонимов мне не нужно отвлекаться на форматирования, т.к. все миграции и остальные файлы проекта полностью отформатированы.&lt;/p&gt;

&lt;h3&gt;
  
  
  Глоссарий
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Хоткей - горячая клавиша&lt;/li&gt;
&lt;li&gt;Жмякнуть - нажать&lt;/li&gt;
&lt;li&gt;Псевдоним, алиас - alias&lt;/li&gt;
&lt;li&gt;Прод - production-сервер&lt;/li&gt;
&lt;li&gt;Глоссарий - словарик&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>django</category>
      <category>pycharm</category>
      <category>black</category>
      <category>docker</category>
    </item>
    <item>
      <title>Аннотация типов в миксинах</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Sun, 25 Jul 2021 10:10:28 +0000</pubDate>
      <link>https://dev.to/gimntut/-1ohb</link>
      <guid>https://dev.to/gimntut/-1ohb</guid>
      <description>&lt;p&gt;При написании кода я стараюсь устранить все замечания линтера PyCharm. Но с миксинами сделать это не так просто&lt;br&gt;
Возьмём для примера, вот такой миксин:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehyls1wcjgxx66s0093p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehyls1wcjgxx66s0093p.png" alt="Image description" width="800" height="336"&gt;&lt;/a&gt;&lt;br&gt;
Линтер не доволен, т.к. не понимает при чём тут &lt;code&gt;self.request&lt;/code&gt;. Этот вопрос я решал просто, я просто затыкал рот линтеру.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dedtxrzmfjod9a9ueg3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dedtxrzmfjod9a9ueg3.png" alt="image" width="651" height="170"&gt;&lt;/a&gt;&lt;br&gt;
Линтер доволен и молчит, даже если есть явная проблема.&lt;br&gt;
В конце концов, безмолвный линтер стал проблемой, и я решил разобраться в вопросе. Я заглянул в &lt;a href="https://mypy.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;документацию mypy&lt;/a&gt; из которой узнал, как можно решить множество замечаний линтера связанные с типами и аннотацией, но не для миксинов. К счастью, я не первый кто задумался об этом, и хоть не с первого раза, я нашёл ответ на StackOverflow, но вам я его не покажу, т.к. не могу найти его снова. Зато я могу показать, как выглядит то, что в итоге получилось:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TypeVar&lt;/span&gt;

&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TypeVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;T&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mixin_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Функция &lt;code&gt;mixin_for&lt;/code&gt; позволяет сказать линтеру к какому классу будет подмешиваться миксин.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhhqv9x9dygpc2gbmqym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhhqv9x9dygpc2gbmqym.png" alt="image" width="800" height="465"&gt;&lt;/a&gt;&lt;br&gt;
Заодно получаем автодополнение кода.&lt;/p&gt;

&lt;p&gt;На этом всё.&lt;/p&gt;

&lt;h2&gt;
  
  
  Глоссарий
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;миксин - mixin, примесь&lt;/li&gt;
&lt;li&gt;линтер - linter, статический анализатор кода&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>pycharm</category>
    </item>
    <item>
      <title>Настройки приложений Django</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Mon, 07 Sep 2020 09:26:04 +0000</pubDate>
      <link>https://dev.to/gimntut/django-1d66</link>
      <guid>https://dev.to/gimntut/django-1d66</guid>
      <description>&lt;h2&gt;
  
  
  Вводная
&lt;/h2&gt;

&lt;p&gt;Для всех последующих примеров кода предполагается, что выполнен импорт &lt;code&gt;settings&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Проблема
&lt;/h2&gt;

&lt;p&gt;Если мы захотим использовать какую-то из настроек django в своём коде без упоминания его в settings.py, то проблем не возникнет.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Обычно никто не задаёт значение для CSRF_COOKIE_AGE, 
&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CSRF_COOKIE_AGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# никаких ошибок, всё работает
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Часто для своего приложения нужны свои настройки&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. Чтобы приложение не упало из-за отсутствия настроек в settings.py нужны проверки и подстановка значения по умолчанию.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'MY_APP_PARAM_1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_value_of_param_1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Выглядит как-то не очень, но если учесть, что одна и та же настройка может использоваться в нескольких местах, то код преображается в нечто такое:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my_app/conf.py
&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;'MY_APP_PARAM_1'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;default_value_of_param_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# Представьте, что здесь перечислены остальные настройки
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_param_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_value_of_param_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# my_app/any_place.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_param_value&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_param_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'MY_APP_PARAM_1'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# выглядит не очень, но работает
&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_param_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'MY_APP_PARAM_L'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# IDE не помешает нам ошибиться
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Хочется, чтобы работа с настройками была одинаковой для всех приложений, чтобы вид настройки был похож на &lt;code&gt;settings.MY_APP_PARAM_1&lt;/code&gt; и чтобы работало автодополнение.&lt;/p&gt;

&lt;h2&gt;
  
  
  Решение
&lt;/h2&gt;

&lt;p&gt;Я не стал придумывать велосипед, а взял чужой. Подозреваю, что на просторах PyPi можно найти ещё с десяток, поэтому свой туда забрасывать не стал, хотя найти аналогичный мне там не удалось.&lt;br&gt;
Скачать результат можно &lt;a href="https://gist.github.com/gimntut/c1d61029e44461d7510f786557bb92c3#file-django_conf-py"&gt;на github gist&lt;/a&gt;&lt;br&gt;
Код в основном взят из django-rest-framework, из него выкинуто всё, что мешает переиспользованию. Файлик кладётся в папку доступную для всех приложений django. У меня это папка &lt;code&gt;utils&lt;/code&gt;.&lt;br&gt;
Посмотрим как это работает&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my_app\settings.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;utils.django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AppSettings&lt;/span&gt;

&lt;span class="c1"&gt;# Описание настроек приложения.
# 1. Все поля находятся в одном месте
# 2. Видны все значения по умолчанию
# 3. При желании можно добавить типизацию
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAppSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;
    &lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;
    &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;

&lt;span class="c1"&gt;# Объект с инициализированными данными 
# 1. Достаточно написать "conf." в любом IDE
#    и редактор сам предложит название настройки
# 2. Все обращения приложения к настройкам должны
#    происходить через этот объект, тогда не будет риска,
#    что приложение "сломается" из-за внешних факторов
&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyAppSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'MY_APP_CONF'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django_project\settings.py
# ...
&lt;/span&gt;
&lt;span class="c1"&gt;# Настройки Django-проекта
# 1. Могут быть заданны частично
# 2. Могут быть совсем не заданы
# 3. Могут быть заданы явно, без использования .env
&lt;/span&gt;&lt;span class="n"&gt;MY_APP_CONF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;'URL'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'MY_APP_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s"&gt;'USER'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'MY_APP_USER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s"&gt;'PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'MY_APP_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my_app\using.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt;
&lt;span class="c1"&gt;# Здесь создаётся условный клиент с подключением к некому сервису
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PASSWORD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;На что следует обратить внимание:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Описание настроек приложения и значений по умолчанию делается очень просто.&lt;/li&gt;
&lt;li&gt;Все настройки приложения задаются в одном словаре, т.е. не будут размазаны по коду и не нужны префиксы к каждой настройке.&lt;/li&gt;
&lt;li&gt;Не нужны проверки на существование при использовании настроек. В худшем случае будет значение по умолчанию.&lt;/li&gt;
&lt;/ol&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Я называю настройкой константу, которая влияет на поведение программы и значение которой можно задать в settings.py ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>django</category>
    </item>
    <item>
      <title>Отладчик Pycharm против проблемы 1+N</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Mon, 07 Sep 2020 08:15:18 +0000</pubDate>
      <link>https://dev.to/gimntut/pycharm-1-n-olc</link>
      <guid>https://dev.to/gimntut/pycharm-1-n-olc</guid>
      <description>&lt;h2&gt;
  
  
  Лирика
&lt;/h2&gt;

&lt;p&gt;К примеру, требуется вывести в API список пользователей, но не все данные, а только их &lt;code&gt;username&lt;/code&gt;, и поэтому &lt;code&gt;queryset=User.objects.only('username')&lt;/code&gt;. Допустим, что за один запрос выводится список из 20 пользователей. Теперь мы решили добавить поля &lt;code&gt;first_name&lt;/code&gt; и &lt;code&gt;last_name&lt;/code&gt; (в сериализатор, но не в queryset). Django Rest Framework перебирает все объекты по очереди. Не обнаружив в текущем объекте поля &lt;code&gt;first_name&lt;/code&gt;, django делает запрос в БД за данными этого поля, потом повторяет то же самое для &lt;code&gt;last_name&lt;/code&gt;. И так для каждого объекта. В результате имеем один запрос к БД для получения списка объектов и ещё 20 раз по 2 запроса. Итого 41 запрос.&lt;br&gt;
В данном случае достаточно добавить недостающие поля в &lt;code&gt;only&lt;/code&gt; или не использовать метод &lt;code&gt;only&lt;/code&gt; вовсе. Даже тот, кто знает о коварстве метода &lt;code&gt;only&lt;/code&gt;, может не знать об его участии в данном фрагменте кода. А всё потому, что подготовка queryset происходит во view, а использование в serializator. &lt;br&gt;
Описанный случай, является достаточно экзотичным, но хорошо показывает, как на пустом месте вместо одного запроса можно получить множество. Гораздо чаще приходится прописывать &lt;code&gt;prefetch_related&lt;/code&gt; для борьбы с 1+N.&lt;/p&gt;

&lt;h2&gt;
  
  
  Задействуем мощь отладчика Pycharm
&lt;/h2&gt;

&lt;p&gt;Если при ручном тестировании своего кода мне нужно делать обращение к API, то я всегда включаю вывод логов отладчика, чтобы видеть обращение к БД. Я покажу как это делается, но перед этим вам нужно прочитать &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/gimntut" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Fju9e_eM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--radfD02Y--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/459529/359805f4-353e-4563-953b-c0afa079f4a7.jpg" alt="gimntut image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/gimntut/pycharm-2c1o" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Ссылки в Pycharm&lt;/h2&gt;
      &lt;h3&gt;Гимаев Наиль ・ Aug 30 ・ 1 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
Прочитали? Тогда начинаем.

&lt;ol&gt;
&lt;li&gt;Откройте свой проект django и перейдите по ссылке &lt;code&gt;CursorDebugWrapper.execute&lt;/code&gt; или &lt;code&gt;CursorDebugWrapper.debug_sql&lt;/code&gt; если у вас &lt;code&gt;Django 3+&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Поставьте breakpoint на &lt;code&gt;logger.debug&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Сделайте правый клик на красном кружке&lt;/li&gt;
&lt;li&gt;В появившимся диалоге отключите &lt;code&gt;Suspend&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Включите &lt;code&gt;Evalute and log&lt;/code&gt; и напишите &lt;code&gt;'(%.3f) %s; args=%s'%(duration, sql, params)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Должно получится так:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_VW39f5N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qy63aguyxasyb11bzflg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_VW39f5N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qy63aguyxasyb11bzflg.png" alt="Screenshot of Pycharm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Теперь, если вы запустите проект под отладчиком, то увидите в консоли все запросы к БД и время их выполнения. В моём проекте запросов в БД от вызова одного API не очень много и 1+N сразу бросается в глаза, если не включен cacheops. Отключайте cacheops на машине разработки, тогда неправильные запросы к БД вы будете буквально чувствовать на собственной шкуре без всякого логирования и не допустите плохой код в проект.&lt;br&gt;
Главная прелесть логирования через отладчик в том, что не требуется вносить изменения в код, и не придётся убирать за собой забытые print'ы. При этом я не удаляю breakpoint, когда он мне не нужен, а просто выключаю (Ctrl+Shift+F8). А когда становится нужен, снова включаю. Т.е. настроив breakpoint один раз, пользуюсь им всё время.&lt;/p&gt;

&lt;p&gt;Но нужно знать 2 вещи. Во-первых, без отладчика эта история не работает. Во-вторых, даже с отладчиком эта история не работает, если у вас mssql aka SQL Server.&lt;/p&gt;

&lt;p&gt;Надеюсь, внимательные читатели заметили, что breakpoint стоит не абы где, а на программном логировании, а значит можно просто включить соответствующий уровень логирования в консоль или в файл и получить похожий результат, просто поменяв пару строк кода. Но как это делается вы, наверное, и без меня знаете.&lt;/p&gt;

</description>
      <category>django</category>
    </item>
    <item>
      <title>Перенос docker wsl2 на соседний ssd</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Wed, 02 Sep 2020 18:01:18 +0000</pubDate>
      <link>https://dev.to/gimntut/docker-wsl2-ssd-1fgb</link>
      <guid>https://dev.to/gimntut/docker-wsl2-ssd-1fgb</guid>
      <description>&lt;h2&gt;
  
  
  Лирика
&lt;/h2&gt;

&lt;p&gt;Так уж получилось, что мой компьютер развивался эволюционно, сначала был только один ssd объёмом 120GB. Со временем появился появился второй SSD объёмом 240GB, на который переехала виртуальная машина huper-v с docker внутри. Для этого было достаточно сменить соответствующую настройку в Docker Desktop. Совсем недавно мой Windows обновился до версии 2004. И у меня появилась возможность задействовать WSL2. Включив WSL2 в Docker Desktop обнаружил, остатки свободного места заняты, а системе поплохело. Настроить средствами Docker Desktop расположение wsl-подсистемы нельзя. Зато можно это сделать средствами windows через командную строку.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Update 1.&lt;/strong&gt; Перенос docker автоматически
&lt;/h2&gt;

&lt;p&gt;После того, как я написал статью, github предложил посмотреть проект, который делает тоже самое. Смотреть я не стал, но возможно он сэкономит время вам. &lt;a href="https://github.com/pxlrbt/move-wsl"&gt;https://github.com/pxlrbt/move-wsl&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Перенос docker
&lt;/h2&gt;

&lt;p&gt;Сначала нужно закрыть Docker Desktop, чтобы его значка в трее не было.&lt;br&gt;
Нам потребуются права администратора. Нажмём Win+X и выберем "Командная строка (администратор)"&lt;br&gt;
&lt;em&gt;Данный код можно &lt;a href="https://gist.github.com/gimntut/0ca607fff95bd2a6871a1942d53fa17a#file-move-wsl2-docker-cmd"&gt;скачать&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:: С двойного двоеточия начинаются комментарии, 
:: поэтому можно весь код скопировать и вставить 
:: в командную строку или в батник.

:: 1. Окончательно усыпим Docker Desktop, чтобы он не нервничал, когда wsl пропадёт

   sc stop com.docker.service

:: 2. Остановим все wsl-машины, чтобы все файловые кэши сбросить на диск

   wsl --shutdown

:: 3. Сделаем дампы подсистем докера

   wsl --export docker-desktop-data d:\dumps\docker-desktop-data.tar
   wsl --export docker-desktop d:\dumps\docker-desktop.tar

:: 4. Удалим подсистемы докера

   wsl --unregister docker-desktop-data
   wsl --unregister docker-desktop

:: 5. Восстановим подсистемы докера в новом расположении

   wsl --import docker-desktop-data d:\wsl\docker-desktop-data d:\dumps\docker-desktop-data.tar --version 2
   wsl --import docker-desktop d:\wsl\docker-desktop d:\dumps\docker-desktop.tar --version 2

:: 6. Запустим службу Docker Desktop

  sc start com.docker.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Теперь можно запускать Docker Desktop и пользоваться.&lt;/p&gt;

&lt;p&gt;Примечание №1. Команды писал по памяти и мог где-то ошибиться. Напишите в комментариях, если найдёте ошибку.&lt;br&gt;
Примечание №2. Чтобы освободить ещё немного места, можно заменить на символические ссылки базовые образы докера в "Program Files", а сами образы положить туда, где места много.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Ссылки в Pycharm</title>
      <dc:creator>Гимаев Наиль</dc:creator>
      <pubDate>Sun, 30 Aug 2020 09:42:08 +0000</pubDate>
      <link>https://dev.to/gimntut/pycharm-2c1o</link>
      <guid>https://dev.to/gimntut/pycharm-2c1o</guid>
      <description>&lt;p&gt;Если вы и ваш коллега при работе над одним проектом используете PyCharm, то вы можете отправить ему ссылку на строку кода воспользовавшись "Copy reference" Ctrl+Alt+Shift+C&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;br&gt;
Ваш коллега должен дважды нажать Shift и вставить в открывшееся поле полученную ссылку.&lt;br&gt;
В большинстве случаев, ссылки бывают рабочими, в некоторых случаях нужно менять &lt;code&gt;#&lt;/code&gt; на &lt;code&gt;.&lt;/code&gt;. Поэтому отправляя ссылку желательно проверить, что она открывается, коллеги вам будут благодарны.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Здесь и далее указаны клавиши для Windows ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
    </item>
  </channel>
</rss>
