Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 6 additions & 18 deletions docs/modules/feed.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,12 @@ View выбирает подходящие `news.News`, сериализует

### 2. В ленту попадает обычная новость

Если `news.News` содержит текст и относится к пользователю, проекту или
партнерской программе, лента возвращает ее как новость.
Если `news.News` содержит текст и относится к пользователю или проекту, лента
возвращает ее как новость.

Проектная новость с текстом возвращается как `type_model = "news"`, даже если
ее `content_object` - проект.

Новость партнерской программы с текстом тоже возвращается как
`type_model = "news"`. Отдельный `type_model = "partner_program"` пока не
вводится.

Служебные feed-записи партнерских программ сейчас не создаются. Если такой
сценарий понадобится, для него нужно отдельно согласовать `type_model` и
frontend-контракт.

### 3. В ленту попадает служебная запись

Служебные feed-записи создаются через `feed.services.create_news_for_model()`.
Expand All @@ -91,17 +83,15 @@ frontend-контракт.
- `GET /feed/?type=news` - новости пользователей.
- `GET /feed/?type=project` - проектные новости и проектные feed-записи.
- `GET /feed/?type=vacancy` - служебные feed-записи вакансий.
- `GET /feed/?type=partnerprogram` - новости партнерских программ.
- `GET /feed/?type=project|vacancy|news|partnerprogram` - комбинированная
выдача по нескольким типам.
- `GET /feed/?type=project|vacancy|news` - комбинированная выдача по нескольким
типам.

## Ограничения и правила

- Feed читает данные из `news.News`, но не отвечает за создание обычных
project/user/program news.
project/user news.
- Служебная feed-запись определяется через пустой `text`.
- Новости партнерских программ с текстом отображаются как обычные новости;
отдельные служебные карточки программ в ленте пока не поддерживаются.
- Новости партнерских программ не отображаются в `/feed/`.
- Signals `feed` создают или удаляют служебные feed-записи для проектов и
вакансий. Более широкие сценарии публикации проекта остаются в модуле
`projects`.
Expand All @@ -112,8 +102,6 @@ frontend-контракт.

- `/feed/?type=news` возвращает пользовательские новости;
- `/feed/?type=project` возвращает проектные новости в frontend-формате;
- `/feed/?type=partnerprogram` возвращает новости программ как
`type_model = "news"`;
- `/feed/?type=project` возвращает служебную feed-запись проекта как
`type_model = "project"`;
- `/feed/?type=vacancy` возвращает служебную feed-запись вакансии как
Expand Down
4 changes: 0 additions & 4 deletions docs/modules/partner-programs.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,6 @@
`PartnerProgramProject`.
- `users` - участники и менеджеры программы.
- `news` - новости программы создаются и читаются через общий news API.
- `feed` - текстовые новости программы могут попадать в общую ленту как
`type_model = "news"`.
- `courses` - курс может быть связан с программой и доступен участникам
программы.
- `project_rates` - оценки проектов используются в выгрузке результатов.
Expand All @@ -176,8 +174,6 @@
- Значения дополнительных полей хранятся в `PartnerProgramFieldValue`.
- После сдачи проекта в конкурсной программе значения полей редактировать
нельзя.
- Служебные feed-карточки программ пока не поддерживаются; новости программ в
feed отображаются как обычные новости.
- Основной API-код пока сосредоточен во `views.py`; перед крупным рефакторингом
нужно зафиксировать больше regression-тестов.

Expand Down
6 changes: 0 additions & 6 deletions feed/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from news.mapping import NewsMapping
from news.models import News
from news.services import is_content_news
from partner_programs.models import PartnerProgram
from projects.models import Project
from users.models import CustomUser

Expand All @@ -24,11 +23,6 @@ class FeedNewsContentSerializer(serializers.ModelSerializer):
def get_type_model(self, obj) -> str | None:
content_model = obj.content_type.model

if content_model == PartnerProgram.__name__.lower():
# Новости программ сейчас отображаются как обычные новости.
# Отдельная служебная карточка программы в ленте пока не согласована.
return "news" if is_content_news(obj) else None

if is_content_news(obj) and content_model == Project.__name__.lower():
return "news"

Expand Down
18 changes: 6 additions & 12 deletions feed/tests/test_feed_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,15 @@ def test_feed_returns_project_news_as_news_content(self):
self.assertEqual(item["content"]["id"], news.id)
self.assertEqual(item["content"]["text"], "Project feed news")

def test_feed_returns_program_news_as_news_content(self):
def test_feed_ignores_program_news_filter(self):
program = create_partner_program(name="Feed program")
news = create_news_for(program, text="Program feed news")
create_news_for(program, text="Program feed news")
create_news_for(program, text="")

response = self.client.get("/feed/?type=partnerprogram")

self.assertEqual(response.status_code, 200)
item = response.data["results"][0]
self.assertEqual(set(item.keys()), {"type_model", "content"})
self.assertEqual(item["type_model"], "news")
self.assertEqual(item["content"]["id"], news.id)
self.assertEqual(item["content"]["text"], "Program feed news")
self.assertEqual(response.data["results"], [])

def test_feed_returns_project_feed_record_as_project_content(self):
project = create_project(name="Feed record project")
Expand All @@ -80,7 +77,7 @@ def test_feed_returns_vacancy_feed_record_as_vacancy_content(self):
self.assertEqual(item["content"]["id"], vacancy.id)
self.assertEqual(item["content"]["role"], "Backend developer")

def test_feed_combines_all_supported_filters(self):
def test_feed_combines_supported_filters_and_ignores_program_news(self):
project_news = create_news_for(
create_project(name="Combined project news"),
text="Combined project news",
Expand Down Expand Up @@ -117,14 +114,11 @@ def test_feed_combines_all_supported_filters(self):
items_by_text[project_news.text]["content"]["id"],
project_news.id,
)
self.assertEqual(
items_by_text[program_news.text]["content"]["id"],
program_news.id,
)
self.assertEqual(
items_by_text[user_news.text]["content"]["id"],
user_news.id,
)
self.assertNotIn(program_news.text, items_by_text)
self.assertIn(project.id, content_ids_by_type["project"])
self.assertIn(vacancy.id, content_ids_by_type["vacancy"])

Expand Down
6 changes: 5 additions & 1 deletion feed/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ def _get_filter_data(self) -> list[str]:
news_types: list[str] = filter_queries.split("|")
if "news" in news_types:
news_types.append("customuser")
return news_types
return [
news_type
for news_type in news_types
if news_type != "partnerprogram"
]

def get_queryset(self) -> QuerySet[News]:
filters = self._get_filter_data()
Expand Down
9 changes: 9 additions & 0 deletions news/tests/test_news_user_program_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ def test_program_news_list_orders_pinned_news_first(self):
news_ids = [item["id"] for item in response.data["results"]]
self.assertEqual(news_ids, [pinned_news.id, regular_news.id])

def test_program_news_list_returns_program_news(self):
news = create_news_for(self.program, text="Visible program news")

response = self.client.get(f"/programs/{self.program.id}/news/")

self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["results"][0]["id"], news.id)
self.assertEqual(response.data["results"][0]["text"], "Visible program news")

def test_missing_program_context_returns_not_found(self):
response = self.client.get("/programs/999999/news/")

Expand Down
Loading