feat: universal rooms (network + chats + topics combined)
This commit is contained in:
parent
a735761eb0
commit
dd10f9fae3
|
@ -45,6 +45,7 @@ INSTALLED_APPS = [
|
|||
"gdpr.apps.GdprConfig",
|
||||
"badges.apps.BadgesConfig",
|
||||
"tags.apps.TagsConfig",
|
||||
"rooms.apps.RoomsConfig",
|
||||
"misc.apps.MiscConfig",
|
||||
"simple_history",
|
||||
"django_q",
|
||||
|
@ -82,7 +83,7 @@ TEMPLATES = [
|
|||
"club.context_processors.data_processor",
|
||||
"club.context_processors.features_processor",
|
||||
"authn.context_processors.users.me",
|
||||
"posts.context_processors.topics.topics",
|
||||
"posts.context_processors.rooms.rooms",
|
||||
]
|
||||
},
|
||||
}
|
||||
|
|
12
club/urls.py
12
club/urls.py
|
@ -21,7 +21,8 @@ from comments.views import create_comment, edit_comment, delete_comment, show_co
|
|||
from common.feature_flags import feature_switch
|
||||
from landing.views import landing, docs, godmode_network_settings, godmode_digest_settings, godmode_settings, \
|
||||
godmode_invite
|
||||
from misc.views import stats, network, robots, generate_ical_invite, generate_google_invite, network_chat
|
||||
from misc.views import stats, network, robots, generate_ical_invite, generate_google_invite
|
||||
from rooms.views import redirect_to_room_chat, list_rooms
|
||||
from notifications.views import render_weekly_digest, email_unsubscribe, email_confirm, render_daily_digest, \
|
||||
email_digest_switch, link_telegram
|
||||
from notifications.webhooks import webhook_event
|
||||
|
@ -156,8 +157,10 @@ urlpatterns = [
|
|||
path("search/users.json", api_search_users, name="api_search_users"),
|
||||
path("search/tags.json", api_search_tags, name="api_search_tags"),
|
||||
|
||||
path("room/<slug:topic_slug>/", feed, name="feed_topic"),
|
||||
path("room/<slug:topic_slug>/<slug:ordering>/", feed, name="feed_topic_ordering"),
|
||||
path("rooms/", list_rooms, name="list_rooms"),
|
||||
path("room/<slug:room_slug>/", feed, name="feed_room"),
|
||||
path("room/<slug:room_slug>/chat/", redirect_to_room_chat, name="redirect_to_room_chat"),
|
||||
path("room/<slug:room_slug>/<slug:ordering>/", feed, name="feed_room_ordering"),
|
||||
path("label/<slug:label_code>/", feed, name="feed_label"),
|
||||
path("label/<slug:label_code>/<slug:ordering>/", feed, name="feed_label_ordering"),
|
||||
|
||||
|
@ -183,7 +186,8 @@ urlpatterns = [
|
|||
path("docs/<slug:doc_slug>/", docs, name="docs"),
|
||||
|
||||
path("network/", network, name="network"),
|
||||
path("network/chat/<slug:chat_id>/", network_chat, name="network_chat"),
|
||||
path("network/chat/<slug:chat_id>/", RedirectView.as_view(url="/room/%(chat_id)s/chat/", permanent=True),
|
||||
name="network_chat"),
|
||||
|
||||
# admin features
|
||||
path("godmode/", godmode_settings, name="godmode_settings"),
|
||||
|
|
|
@ -23,3 +23,13 @@ class ImageUploadField(forms.ImageField):
|
|||
convert_to=self.convert_to,
|
||||
quality=self.quality,
|
||||
)
|
||||
|
||||
|
||||
class ReverseBooleanField(forms.BooleanField):
|
||||
def prepare_value(self, value):
|
||||
return not value
|
||||
|
||||
def to_python(self, value):
|
||||
value = super().to_python(value)
|
||||
return not value
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
{% block title %}
|
||||
{% if post %}
|
||||
{% if post.prefix %}{{ post.prefix }} {% endif %}{{ post.title }}{% if post.topic %} [{{ post.topic.name }}]{% endif %} — {{ block.super }}
|
||||
{% if post.prefix %}{{ post.prefix }} {% endif %}{{ post.title }}{% if post.room %} [{{ post.room.title }}]{% endif %} — {{ block.super }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -5,66 +5,68 @@
|
|||
{% load posts %}
|
||||
|
||||
{% block title %}
|
||||
{% if topic %}{{ topic.name }} — {% endif %}{{ block.super }}
|
||||
{% if room %}{{ room.title }} — {% endif %}{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block og_tags %}
|
||||
<meta property="og:title" content="{% if topic %}{{ topic.name }} — {% endif %}{{ settings.APP_NAME }}">
|
||||
<meta property="og:title" content="{% if room %}{{ room.title }} — {% endif %}{{ settings.APP_NAME }}">
|
||||
<meta property="og:site_name" content="{{ settings.APP_NAME }}">
|
||||
<meta property="og:url" content="{{ settings.APP_HOST }}">
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:description" content="{% if topic %}{{ topic.description }}{% endif %}">
|
||||
<meta property="og:description" content="{% if room %}{{ room.description }}{% endif %}">
|
||||
<meta property="og:image" content="{% static "images/share.png" %}">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="{% if topic %}{{ topic.name }} — {% endif %}{{ settings.APP_NAME }}">
|
||||
<meta name="twitter:description" content="{% if topic %}{{ topic.description }}{% endif %}">
|
||||
<meta name="twitter:title" content="{% if room %}{{ room.title }} — {% endif %}{{ settings.APP_NAME }}">
|
||||
<meta name="twitter:description" content="{% if room %}{{ room.subtitle }}{% endif %}">
|
||||
<meta name="twitter:image" content="{% static "images/share.png" %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block feed_content %}
|
||||
{% if topic %}
|
||||
<div class="feed-topic-header" style="background-color: {{ topic.color }};">
|
||||
<span class="topic-icon feed-topic-header-icon"><img src="{{ topic.icon }}" alt="Иконка комнаты {{ topic.name }}" loading="lazy" /></span>
|
||||
<h1 class="feed-topic-header-name">{{ topic.name }}</h1>
|
||||
{% if room %}
|
||||
<div class="feed-topic-header" style="background-color: {{ room.color }};">
|
||||
<div class="feed-topic-header-title">
|
||||
<span class="room-icon feed-topic-header-icon"><img src="{{ room.image }}" alt="Иконка комнаты {{ room.title }}" loading="lazy" /></span>
|
||||
<a href="{% url "feed_room" room.slug %}" class="feed-topic-header-name">{{ room.title }}</a>
|
||||
</div>
|
||||
<span class="feed-topic-header-desctiption">
|
||||
{{ topic.description | markdown }}
|
||||
{{ room.description | markdown }}
|
||||
</span>
|
||||
{% if topic.chat_url and topic.chat_name %}
|
||||
{% if room.chat_url and room.chat_name %}
|
||||
<span class="feed-topic-header-footer">
|
||||
<i class="fab fa-telegram-plane"></i> <strong>Официальный чат:</strong> <a href="{{ topic.chat_url }}" rel="noreferrer" target="_blank">{{ topic.chat_name }}</a>
|
||||
<i class="fab fa-telegram-plane"></i> <strong>Официальный чат:</strong> <a href="{{ room.chat_url }}" rel="noreferrer" target="_blank">{{ room.chat_name }}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="feed-ordering">
|
||||
<a href="{% feed_ordering_url topic label_code post_type "activity" %}"
|
||||
<a href="{% feed_ordering_url room label_code post_type "activity" %}"
|
||||
class="feed-ordering-item {% if ordering == "activity" %}feed-ordering-item-is-active{% endif %}"
|
||||
>
|
||||
Фид
|
||||
</a>
|
||||
|
||||
<a href="{% feed_ordering_url topic label_code post_type "new" %}"
|
||||
<a href="{% feed_ordering_url room label_code post_type "new" %}"
|
||||
class="feed-ordering-item {% if ordering == "new" %}feed-ordering-item-is-active{% endif %}"
|
||||
>
|
||||
Новое
|
||||
</a>
|
||||
|
||||
<a href="{% feed_ordering_url topic label_code post_type "hot" %}"
|
||||
<a href="{% feed_ordering_url room label_code post_type "hot" %}"
|
||||
class="feed-ordering-item {% if ordering == "hot" %}feed-ordering-item-is-active{% endif %}"
|
||||
>
|
||||
Обсуждаемое
|
||||
</a>
|
||||
|
||||
{% if me and me.created_at < date_month_ago %}
|
||||
<a href="{% feed_ordering_url topic label_code post_type "top_month" %}"
|
||||
<a href="{% feed_ordering_url room label_code post_type "top_month" %}"
|
||||
class="feed-ordering-item {% if ordering == "top" or ordering == "top_week" or ordering == "top_month" %}feed-ordering-item-is-active{% endif %}"
|
||||
>
|
||||
Лучшее
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% feed_ordering_url topic label_code post_type "top" %}"
|
||||
<a href="{% feed_ordering_url room label_code post_type "top" %}"
|
||||
class="feed-ordering-item {% if ordering == "top" or ordering == "top_week" or ordering == "top_month" %}feed-ordering-item-is-active{% endif %}"
|
||||
>
|
||||
Лучшее
|
||||
|
|
|
@ -43,24 +43,24 @@
|
|||
<div class="block network-block" id="{{ group.code }}">
|
||||
<a href="#{{ group.code }}" class="network-header">{{ group.title }}</a>
|
||||
|
||||
{% if group.items %}
|
||||
{% if group.rooms %}
|
||||
<div class="network-channels">
|
||||
{% for item in group.items.all %}
|
||||
<a href="{{ item.get_private_url }}" target="_blank" class="network-channel">
|
||||
{% if item.image %}
|
||||
<span class="avatar network-channel-icon"><img src="{{ item.image }}" alt="{{ item.name }}"></span>
|
||||
{% for room in group.rooms.all %}
|
||||
<a href="{{ room.get_private_url }}" target="_blank" class="network-channel">
|
||||
{% if room.image %}
|
||||
<span class="avatar network-channel-icon"><img src="{{ room.image }}" alt="{{ room.title }}"></span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.icon %}
|
||||
<span class="network-channel-badge">{{ item.icon | safe }}</span>
|
||||
{% if room.icon %}
|
||||
<span class="network-channel-badge">{{ room.icon | safe }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.name %}
|
||||
<span class="network-channel-name">{{ item.name | safe }}</span>
|
||||
{% if room.title %}
|
||||
<span class="network-channel-name">{{ room.title | safe }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.description %}
|
||||
<span class="network-channel-description">{{ item.description | safe }}</span>
|
||||
{% if room.subtitle %}
|
||||
<span class="network-channel-description">{{ room.subtitle | safe }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
|
|
@ -39,11 +39,6 @@
|
|||
<form action="." method="post" class="compose-form" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-row form-row-center">
|
||||
{{ form.topic }}
|
||||
{% if form.topic.errors %}<span class="form-row-errors">{{ form.topic.errors }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="{{ form.side_a.id_for_label }}" class="form-label">Стороны батла</label>
|
||||
<div class="form-row-center">
|
||||
|
|
|
@ -1,24 +1,80 @@
|
|||
<details class="block compose-form-advanced" {% if form.instance.coauthors or form.instance.collectible_tag_code %}open{% endif %}>
|
||||
<summary class="compose-form-advanced-summary">Продвинутые настройки</summary>
|
||||
<details class="block compose-form-advanced" {% if form.instance.room %}open{% endif %}>
|
||||
<summary class="compose-form-advanced-summary">🚪 Запостить в комнату</summary>
|
||||
|
||||
<div class="clearfix50"></div>
|
||||
|
||||
{% if post_type == "post" or post_type == "guide" or post_type == "project" or post_type == "event" %}
|
||||
<div class="block-description-center">
|
||||
Бот Клуба автоматически унесёт ваш пост в чат выбранной вами комнаты.
|
||||
Возможно, там он быстрее найдет заинтересованных темой.
|
||||
</div>
|
||||
|
||||
<div class="clearfix50"></div>
|
||||
|
||||
<div class="form-row compose-form-room">
|
||||
<simple-select
|
||||
id="{{ form.room.html_name }}"
|
||||
initial-value="{{ form.room.value|default_if_none:'' }}"
|
||||
:options="[
|
||||
{% for room in rooms %}{label: '{{ room.emoji }} {{ room.title }}', code: '{{ room.slug }}'},{% endfor %}
|
||||
]"
|
||||
>
|
||||
</simple-select>
|
||||
|
||||
{% if form.room.errors %}
|
||||
<span class="form-row-errors">{{ form.room.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row form-row-checkbox">
|
||||
{{ form.is_visible_in_feeds }}
|
||||
<label for="{{ form.is_visible_in_feeds.id_for_label }}">{{ form.is_visible_in_feeds.label }}</label>
|
||||
|
||||
{% if form.is_visible_in_feeds.errors %}
|
||||
<span class="form-row-errors">{{ form.is_visible_in_feeds.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{% if post_type == "post" or post_type == "guide" or post_type == "project" or post_type == "event" %}
|
||||
<details class="block compose-form-advanced" {% if form.instance.coauthors %}open{% endif %}>
|
||||
<summary class="compose-form-advanced-summary">👨👨👧👧 Добавить соавторов к посту</summary>
|
||||
|
||||
<div class="clearfix50"></div>
|
||||
|
||||
<div class="block-description-center">
|
||||
Соавторы могут редактировать пост и отображаются в списке авторов
|
||||
</div>
|
||||
|
||||
<div class="clearfix50"></div>
|
||||
|
||||
<div class="form-row compose-form-coauthors">
|
||||
<label for="{{ form.coauthors.id_for_label }}" class="form-label">{{ form.coauthors.label }}</label>
|
||||
{{ form.coauthors }}
|
||||
|
||||
{% if form.coauthors.errors %}
|
||||
<span class="form-row-errors">{{ form.coauthors.errors }}</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="form-row-help form-row-help-wide">
|
||||
Список никнеймов через запятую. Они смогут тоже редактировать пост, но лучше не делать этого одновременно — изменения одного автора могут затереть другого.
|
||||
Список никнеймов через запятую
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<details class="block compose-form-advanced" {% if form.instance.collectible_tag_code %}open{% endif %}>
|
||||
<summary class="compose-form-advanced-summary">🏷️ Прикрепить коллекционный тег</summary>
|
||||
|
||||
<div class="clearfix50"></div>
|
||||
|
||||
<div class="block-description-center">
|
||||
Пользователи могут подписываться на коллекционные теги и автоматически получать уведомления, когда кто-то пишет новый пост с интересным им тегом.
|
||||
</div>
|
||||
|
||||
<div class="clearfix50"></div>
|
||||
|
||||
<div class="form-row compose-form-collectible-tag">
|
||||
<label for="{{ form.collectible_tag_code.id_for_label }}" class="form-label">{{ form.collectible_tag_code.label }}</label>
|
||||
<multi-select
|
||||
<tag-select
|
||||
initial-value="{{ form.collectible_tag_code.value|default_if_none:'' }}"
|
||||
id="{{ form.collectible_tag_code.html_name }}"
|
||||
search-url="/search/tags.json?prefix="
|
||||
|
@ -28,43 +84,43 @@
|
|||
label-invalid-input="Каждый тег обязан начинаться с emoji, потом идёт пробел и название."
|
||||
label-valid-input="Вы добавите этот тег первым!"
|
||||
>
|
||||
</multi-select>
|
||||
|
||||
{% if form.collectible_tag_code.errors %}
|
||||
<span class="form-row-errors">{{ form.collectible_tag_code.errors }}</span>
|
||||
{% endif %}
|
||||
</tag-select>
|
||||
|
||||
<span class="form-row-help form-row-help-wide">
|
||||
Каждый тег обязан начинаться с <a href="https://emojipedia.org/" target="_blank">emoji</a>, потом идёт пробел и название.
|
||||
</span>
|
||||
|
||||
{% if form.collectible_tag_code.errors %}
|
||||
<span class="form-row-errors">{{ form.collectible_tag_code.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{% if mode == "create" or form.instance.is_public or form.instance.comment_count < 10 or form.instance.published_at is None or me.is_moderator %}
|
||||
<div class="big-radio compose-visibility">
|
||||
<div class="big-radio-item">
|
||||
{{ form.is_public.0.tag }}
|
||||
<label for="{{ form.is_public.0.id_for_label }}" class="big-radio-label">
|
||||
<i class="fas fa-globe-americas"></i>
|
||||
<span class="big-radio-title">{{ form.is_public.0.choice_label }}</span>
|
||||
<span class="big-radio-description">
|
||||
Пост виден снаружи, его можно пошарить в соцсеточки.
|
||||
Такие посты развивают Клуб и чаще попадают в дайджесты.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="big-radio compose-visibility">
|
||||
<div class="big-radio-item">
|
||||
{{ form.is_public.0.tag }}
|
||||
<label for="{{ form.is_public.0.id_for_label }}" class="big-radio-label">
|
||||
<i class="fas fa-globe-americas"></i>
|
||||
<span class="big-radio-title">{{ form.is_public.0.choice_label }}</span>
|
||||
<span class="big-radio-description">
|
||||
Пост виден снаружи, его можно пошарить в соцсеточки.
|
||||
Такие посты развивают Клуб и чаще попадают в дайджесты.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="big-radio-item">
|
||||
{{ form.is_public.1.tag }}
|
||||
<label for="{{ form.is_public.1.id_for_label }}" class="big-radio-label">
|
||||
<i class="fas fa-lock"></i>
|
||||
<span class="big-radio-title">{{ form.is_public.1.choice_label }}</span>
|
||||
<span class="big-radio-description">
|
||||
Пост для членов Клуба.
|
||||
Для обсуждения чувствительных тем и организации внутренних движух.
|
||||
Сменить тип потом нельзя.
|
||||
</span>
|
||||
</label>
|
||||
<div class="big-radio-item">
|
||||
{{ form.is_public.1.tag }}
|
||||
<label for="{{ form.is_public.1.id_for_label }}" class="big-radio-label">
|
||||
<i class="fas fa-lock"></i>
|
||||
<span class="big-radio-title">{{ form.is_public.1.choice_label }}</span>
|
||||
<span class="big-radio-description">
|
||||
Пост для членов Клуба.
|
||||
Для обсуждения чувствительных тем и организации внутренних движух.
|
||||
Сменить тип потом нельзя.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -26,11 +26,6 @@
|
|||
<form action="." method="post" class="compose-form" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-row form-row-center">
|
||||
{{ form.topic }}
|
||||
{% if form.topic.errors %}<span class="form-row-errors">{{ form.topic.errors }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row compose-form-title">
|
||||
{{ form.title }}
|
||||
{% if form.title.errors %}<span class="form-row-errors">{{ form.title.errors }}</span>{% endif %}
|
||||
|
|
|
@ -26,11 +26,6 @@
|
|||
<form action="." method="post" class="compose-form" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-row form-row-center">
|
||||
{{ form.topic }}
|
||||
{% if form.topic.errors %}<span class="form-row-errors">{{ form.topic.errors }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row compose-form-title">
|
||||
{{ form.title }}
|
||||
{% if form.title.errors %}<span class="form-row-errors">{{ form.title.errors }}</span>{% endif %}
|
||||
|
|
|
@ -40,11 +40,6 @@
|
|||
<form action="." method="post" class="compose-form" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-row form-row-center">
|
||||
{{ form.topic }}
|
||||
{% if form.topic.errors %}<span class="form-row-errors">{{ form.topic.errors }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row compose-form-title">
|
||||
{{ form.url }}
|
||||
{% if form.url.errors %}<span class="form-row-errors">{{ form.url.errors }}</span>{% endif %}
|
||||
|
|
|
@ -40,11 +40,6 @@
|
|||
<form action="." method="post" class="compose-form" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-row form-row-center">
|
||||
{{ form.topic }}
|
||||
{% if form.topic.errors %}<span class="form-row-errors">{{ form.topic.errors }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row compose-form-title">
|
||||
{{ form.title }}
|
||||
{% if form.title.errors %}<span class="form-row-errors">{{ form.title.errors }}</span>{% endif %}
|
||||
|
|
|
@ -48,11 +48,6 @@
|
|||
<form action="." method="post" class="compose-form" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-row form-row-center">
|
||||
{{ form.topic }}
|
||||
{% if form.topic.errors %}<span class="form-row-errors">{{ form.topic.errors }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row compose-form-title">
|
||||
{{ form.title }}
|
||||
{% if form.title.errors %}<span class="form-row-errors">{{ form.title.errors }}</span>{% endif %}
|
||||
|
|
|
@ -30,11 +30,6 @@
|
|||
<form action="." method="post" class="compose-form" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-row form-row-center">
|
||||
{{ form.topic }}
|
||||
{% if form.topic.errors %}<span class="form-row-errors">{{ form.topic.errors }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row compose-form-title">
|
||||
{{ form.title }}
|
||||
{% if form.title.errors %}<span class="form-row-errors">{{ form.title.errors }}</span>{% endif %}
|
||||
|
|
|
@ -32,9 +32,9 @@
|
|||
</post-upvote>
|
||||
</div>
|
||||
<div class="feed-post-footer">
|
||||
{% if post.topic %}
|
||||
<span class="feed-post-topic">
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="inline" %}
|
||||
{% if post.room %}
|
||||
<span class="feed-post-room">
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="inline" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -46,9 +46,9 @@
|
|||
</div>
|
||||
|
||||
<div class="feed-post-footer">
|
||||
{% if post.topic %}
|
||||
<span class="feed-post-topic">
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="inline" %}
|
||||
{% if post.room %}
|
||||
<span class="feed-post-room">
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="inline" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -34,9 +34,9 @@
|
|||
</div>
|
||||
|
||||
<div class="feed-post-footer">
|
||||
{% if post.topic %}
|
||||
<span class="feed-post-topic">
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="inline" %}
|
||||
{% if post.room %}
|
||||
<span class="feed-post-room">
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="inline" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -32,9 +32,9 @@
|
|||
</div>
|
||||
|
||||
<div class="feed-post-footer">
|
||||
{% if post.topic %}
|
||||
<span class="feed-post-topic">
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="inline" %}
|
||||
{% if post.room %}
|
||||
<span class="feed-post-room">
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="inline" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -31,9 +31,9 @@
|
|||
</post-upvote>
|
||||
</div>
|
||||
<div class="feed-post-footer">
|
||||
{% if post.topic %}
|
||||
<span class="feed-post-topic">
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="inline" %}
|
||||
{% if post.room %}
|
||||
<span class="feed-post-room">
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="inline" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
</div>
|
||||
|
||||
<div class="feed-post-footer">
|
||||
{% if post.topic %}
|
||||
<span class="feed-post-topic">
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="inline" %}
|
||||
{% if post.room %}
|
||||
<span class="feed-post-room">
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="inline" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -34,9 +34,9 @@
|
|||
</div>
|
||||
|
||||
<div class="feed-post-footer">
|
||||
{% if post.topic %}
|
||||
<span class="feed-post-topic">
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="inline" %}
|
||||
{% if post.room %}
|
||||
<span class="feed-post-room">
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="inline" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
{% block post %}
|
||||
<div class="battle-header">
|
||||
{% if post.topic %}
|
||||
{% if post.room %}
|
||||
<div class="battle-header-topic">
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="post" %}
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="post" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -109,7 +109,7 @@
|
|||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if comments %}
|
||||
<div class="post-comments-list">
|
||||
{% include "comments/list.html" with comments=comments reply_form=reply_form type="battle" %}
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
{% block post_header %}
|
||||
<header class="post-header">
|
||||
<div class="post-info">
|
||||
{% if post.topic %}
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="post" %}
|
||||
{% if post.room %}
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="post" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<h1 class="post-title">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{% load text_filters %}
|
||||
|
||||
{% block title %}
|
||||
{% if post.prefix %}{{ post.prefix }} {% endif %}{{ post.title }}{% if post.topic %} [{{ post.topic.name }}]{% endif %} — {{ block.super }}
|
||||
{% if post.prefix %}{{ post.prefix }} {% endif %}{{ post.title }}{% if post.room %} [{{ post.room.title }}]{% endif %} — {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block og_tags %}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
{% block post_header %}
|
||||
<header class="post-header">
|
||||
<div class="post-info">
|
||||
{% if post.topic %}
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="post" %}
|
||||
{% if post.room %}
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="post" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -54,8 +54,8 @@
|
|||
</div>
|
||||
|
||||
<div class="post-info">
|
||||
{% if post.topic %}
|
||||
{% include "posts/widgets/topic.html" with topic=post.topic type="inline" %}
|
||||
{% if post.room %}
|
||||
{% include "rooms/widgets/room.html" with room=post.room type="inline" %}
|
||||
{% endif %}
|
||||
<div class="post-actions-line">
|
||||
{% include "posts/widgets/post_actions_line.html" %}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
<a href="{% url "feed_topic" topic.slug %}" class="topic {% if type %}topic-type-{{type}}{% endif %}" style="{% if topic.style %}{{ topic.style }}{% else %}background-color: {{ topic.color }};{% endif %}">
|
||||
<span class="topic-gradient"></span>
|
||||
<span class="topic-icon-wrapper">
|
||||
<span class="topic-icon"><img src="{{ topic.icon }}" alt="Иконка комнаты {{ topic.name }}" loading="lazy" /></span>
|
||||
</span>
|
||||
<span class="topic-name-wrapper">
|
||||
<span class="topic-name">{{ topic.name }}</span>
|
||||
</span>
|
||||
</a>
|
|
@ -0,0 +1,40 @@
|
|||
{% extends "sidebar_layout.html" %}
|
||||
{% load text_filters %}
|
||||
{% load static %}
|
||||
{% load paginator %}
|
||||
{% load posts %}
|
||||
|
||||
{% block title %}
|
||||
Комнаты — {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block og_tags %}
|
||||
<meta property="og:title" content="Комнаты — {{ settings.APP_NAME }}">
|
||||
<meta property="og:site_name" content="{{ settings.APP_NAME }}">
|
||||
<meta property="og:url" content="{{ settings.APP_HOST }}">
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="{% static "images/share.png" %}">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Комнаты — {{ settings.APP_NAME }}">
|
||||
<meta name="twitter:image" content="{% static "images/share.png" %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block feed_content %}
|
||||
{% for room in rooms %}
|
||||
<div class="feed-topic-header" style="background-color: {{ room.color }};">
|
||||
<div class="feed-topic-header-title">
|
||||
<span class="room-icon feed-topic-header-icon"><img src="{{ room.image }}" alt="Иконка комнаты {{ room.title }}" loading="lazy" /></span>
|
||||
<a href="{% url "feed_room" room.slug %}" class="feed-topic-header-name">{{ room.title }}</a>
|
||||
</div>
|
||||
<span class="feed-topic-header-desctiption">
|
||||
{{ room.description | markdown }}
|
||||
</span>
|
||||
{% if room.chat_url and room.chat_name %}
|
||||
<span class="feed-topic-header-footer">
|
||||
<i class="fab fa-telegram-plane"></i> <strong>Официальный чат:</strong> <a href="{{ room.chat_url }}" rel="noreferrer" target="_blank">{{ room.chat_name }}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
|||
<a href="{% url "feed_room" room.slug %}" class="topic {% if type %}topic-type-{{type}}{% endif %}" style="{% if room.style %}{{ room.style }}{% else %}background-color: {{ room.color }};{% endif %}">
|
||||
<span class="topic-gradient"></span>
|
||||
<span class="topic-icon-wrapper">
|
||||
<span class="topic-icon"><img src="{{ room.image }}" alt="Иконка комнаты {{ room.title }}" loading="lazy" /></span>
|
||||
</span>
|
||||
<span class="topic-name-wrapper">
|
||||
<span class="topic-name">{{ room.title }}</span>
|
||||
</span>
|
||||
</a>
|
|
@ -20,7 +20,7 @@
|
|||
</li>
|
||||
<li>
|
||||
<a href="{% url "network" %}">
|
||||
💬 Сеть
|
||||
💬 Чаты
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -82,10 +82,13 @@
|
|||
<div class="feed-sidebar-block">
|
||||
<div class="feed-sidebar-header">Комнаты</div>
|
||||
<div class="feed-sidebar-topics">
|
||||
{% for topic in topics %}
|
||||
{% include "posts/widgets/topic.html" with topic=topic type="card" %}
|
||||
{% for room in rooms|slice:":10" %}
|
||||
{% include "rooms/widgets/room.html" with room=room type="card" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="feed-sidebar-topics-more">
|
||||
<a class="button button-small button-inverted" href="{% url "list_rooms" %}">Все комнаты →</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="feed-main">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
.form-row label input[type="checkbox"] {
|
||||
margin-top: 0;
|
||||
vertical-align: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
form .form-row p {
|
||||
|
|
|
@ -466,7 +466,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||
width: auto;
|
||||
transform: scale(1.5);
|
||||
position: relative;
|
||||
top: -4px;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.form-row-help {
|
||||
|
|
|
@ -154,3 +154,11 @@
|
|||
font-size: 130%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.compose-form-room select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 110%;
|
||||
font-weight: 500;
|
||||
appearance: auto;
|
||||
}
|
||||
|
|
|
@ -99,6 +99,12 @@
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.feed-sidebar-topics-more {
|
||||
text-align: center;
|
||||
padding: 20px 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.feed-main {
|
||||
}
|
||||
|
||||
|
@ -120,20 +126,27 @@
|
|||
color: #FFF;
|
||||
}
|
||||
|
||||
.feed-topic-header-name {
|
||||
margin: 0;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.feed-topic-header-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.feed-topic-header-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-width: 4px;
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
}
|
||||
.feed-topic-header-name {
|
||||
font-size: 210%;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.feed-topic-header-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: solid 2px var(--bg-color);
|
||||
}
|
||||
|
||||
.feed-topic-header-desctiption {
|
||||
display: block;
|
||||
|
@ -326,7 +339,7 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.feed-post-topic {
|
||||
.feed-post-room {
|
||||
padding-right: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="input-select">
|
||||
<input type="hidden" v-model="currentValue.code" :name="id" />
|
||||
<v-select
|
||||
:options="options"
|
||||
:value="currentValue"
|
||||
@input="onChange"
|
||||
>
|
||||
</v-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "SimpleSelect",
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.options.find(x => x.code === this.initialValue) || {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onChange(newValue) {
|
||||
this.currentValue = newValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -26,7 +26,8 @@ Vue.component("friend-button", () => import("./components/FriendButton.vue"));
|
|||
Vue.component("comment-scroll-arrow", () => import("./components/CommentScrollArrow.vue"));
|
||||
Vue.component("comment-markdown-editor", () => import("./components/CommentMarkdownEditor.vue"));
|
||||
Vue.component("v-select", vSelect);
|
||||
Vue.component("multi-select", () => import("./components/MultiSelect.vue"));
|
||||
Vue.component("tag-select", () => import("./components/TagSelect.vue"));
|
||||
Vue.component("simple-select", () => import("./components/SimpleSelect.vue"));
|
||||
|
||||
// Since our pages have user-generated content, any fool can insert "{{" on the page and break it.
|
||||
// We have no other choice but to completely turn off template matching and leave it on only for components.
|
||||
|
|
|
@ -24,7 +24,7 @@ def post_to_json(post: Post) -> dict:
|
|||
"slug": post.slug,
|
||||
"author_id": str(post.author_id),
|
||||
"type": post.type,
|
||||
"topic": post.topic.name if post.topic else None,
|
||||
"room": post.room.title if post.room else None,
|
||||
"label": post.label,
|
||||
"title": post.title,
|
||||
"text": post.text,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from misc.models import ProTip, NetworkGroup, NetworkItem
|
||||
from misc.models import ProTip, NetworkGroup
|
||||
|
||||
|
||||
class ProTipsAdmin(admin.ModelAdmin):
|
||||
|
@ -31,18 +31,3 @@ class NetworkGroupAdmin(admin.ModelAdmin):
|
|||
|
||||
admin.site.register(NetworkGroup, NetworkGroupAdmin)
|
||||
|
||||
|
||||
class NetworkItemAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"name",
|
||||
"description",
|
||||
"group",
|
||||
"url",
|
||||
"index",
|
||||
)
|
||||
ordering = ("index",)
|
||||
search_fields = ["title", "description"]
|
||||
|
||||
|
||||
admin.site.register(NetworkItem, NetworkItemAdmin)
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-31 14:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('misc', '0003_alter_networkitem_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='NetworkItem',
|
||||
),
|
||||
]
|
|
@ -2,7 +2,6 @@ from datetime import datetime
|
|||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class ProTip(models.Model):
|
||||
|
@ -46,28 +45,3 @@ class NetworkGroup(models.Model):
|
|||
@classmethod
|
||||
def visible_objects(cls):
|
||||
return cls.objects.filter(is_visible=True)
|
||||
|
||||
|
||||
class NetworkItem(models.Model):
|
||||
id = models.CharField(primary_key=True, max_length=32, default=uuid4)
|
||||
|
||||
group = models.ForeignKey(NetworkGroup, related_name="items", db_index=True, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
name = models.CharField(max_length=128, null=True, blank=True)
|
||||
description = models.CharField(max_length=256, null=True, blank=True)
|
||||
image = models.URLField(null=False)
|
||||
icon = models.CharField(max_length=256, null=True, blank=True)
|
||||
url = models.URLField(null=False)
|
||||
|
||||
telegram_chat_id = models.CharField(max_length=32, null=True, blank=True)
|
||||
|
||||
index = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
db_table = "network_items"
|
||||
ordering = ["index"]
|
||||
|
||||
def get_private_url(self):
|
||||
if self.url:
|
||||
return reverse("network_chat", kwargs={"chat_id": self.id})
|
||||
return None
|
||||
|
|
|
@ -3,15 +3,14 @@ from urllib.parse import urlencode
|
|||
|
||||
import pytz
|
||||
from django.db.models import Count, Q, Sum
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.views.decorators.http import require_GET
|
||||
from icalendar import Calendar, Event
|
||||
|
||||
from authn.decorators.auth import require_auth
|
||||
from badges.models import UserBadge
|
||||
from landing.models import GodSettings
|
||||
from misc.models import NetworkGroup, NetworkItem
|
||||
from misc.models import NetworkGroup
|
||||
from users.models.achievements import Achievement
|
||||
from users.models.user import User
|
||||
|
||||
|
@ -67,14 +66,6 @@ def network(request):
|
|||
})
|
||||
|
||||
|
||||
@require_auth
|
||||
def network_chat(request, chat_id):
|
||||
network_item = get_object_or_404(NetworkItem, id=chat_id)
|
||||
if not network_item.url:
|
||||
raise Http404()
|
||||
return redirect(network_item.url, permanent=False)
|
||||
|
||||
|
||||
@require_GET
|
||||
def robots(request):
|
||||
lines = [
|
||||
|
|
|
@ -40,18 +40,19 @@ def announce_in_club_chats(post):
|
|||
])
|
||||
|
||||
# announce to public chat
|
||||
send_telegram_message(
|
||||
chat=CLUB_CHAT,
|
||||
text=render_html_message("channel_post_announce.html", post=post),
|
||||
parse_mode=telegram.ParseMode.HTML,
|
||||
disable_preview=True,
|
||||
reply_markup=post_reply_markup,
|
||||
)
|
||||
|
||||
if post.topic and post.topic.chat_id:
|
||||
# announce to the topic chat
|
||||
if post.is_visible_in_feeds or not post.room:
|
||||
send_telegram_message(
|
||||
chat=Chat(id=post.topic.chat_id),
|
||||
chat=CLUB_CHAT,
|
||||
text=render_html_message("channel_post_announce.html", post=post),
|
||||
parse_mode=telegram.ParseMode.HTML,
|
||||
disable_preview=True,
|
||||
reply_markup=post_reply_markup,
|
||||
)
|
||||
|
||||
if post.room and post.room.chat_id:
|
||||
# announce to the room chat
|
||||
send_telegram_message(
|
||||
chat=Chat(id=post.room.chat_id),
|
||||
text=render_html_message("channel_post_announce.html", post=post),
|
||||
parse_mode=telegram.ParseMode.HTML,
|
||||
disable_preview=True,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% if post.emoji %}{{ post.emoji }} {% endif %}<b>{% if post.prefix and post.prefix != post.emoji %}{{ post.prefix }} {% endif %}<a href="{{ settings.APP_HOST }}{% url "show_post" post.type post.slug %}">{{ post.title }}</a> {% if post.topic %} [{{ post.topic.name }}]{% endif %}</b>
|
||||
{% if post.emoji %}{{ post.emoji }} {% endif %}<b>{% if post.prefix and post.prefix != post.emoji %}{{ post.prefix }} {% endif %}<a href="{{ settings.APP_HOST }}{% url "show_post" post.type post.slug %}">{{ post.title }}</a> {% if post.room %} [{{ post.room.title }}]{% endif %}</b>
|
||||
|
||||
{% load posts %}{% render_plain post 350 %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Ваш чувак {{ post.author.full_name }} написал пост 👇
|
||||
|
||||
{% if post.emoji %}{{ post.emoji }} {% endif %}<b>{% if post.prefix %}{{ post.prefix }} {% endif %}<a href="{{ settings.APP_HOST }}{% url "show_post" post.type post.slug %}">{{ post.title }}</a> {% if post.topic %} [{{ post.topic.name }}]{% endif %}</b>
|
||||
{% if post.emoji %}{{ post.emoji }} {% endif %}<b>{% if post.prefix %}{{ post.prefix }} {% endif %}<a href="{{ settings.APP_HOST }}{% url "show_post" post.type post.slug %}">{{ post.title }}</a> {% if post.room %} [{{ post.room.title }}]{% endif %}</b>
|
||||
{% load posts %}{% render_plain post 350 %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
🔥 <b>NEW: {% if post.prefix %}{{ post.prefix }} {% endif %}{{ post.title }}{% if post.topic %} [{{ post.topic.name }}]{% endif %}</b>
|
||||
🔥 <b>NEW: {% if post.prefix %}{{ post.prefix }} {% endif %}{{ post.title }}{% if post.room %} [{{ post.room.title }}]{% endif %}</b>
|
||||
|
||||
<b>Автор:</b> {{ post.author.slug }} {% if post.author.telegram_data %}(tg: @{{ post.author.telegram_data.username }}){% endif %}
|
||||
{% if post.url %}<b>Ссылка:</b> {{ post.url }}{% endif %}{% if post.collectible_tag_code %}🚨 <b>Прикреплён коллекционный тег:</b> {{ post.collectible_tag_code }}{% endif %}
|
||||
{% if post.url %}<b>Ссылка:</b> {{ post.url }}{% endif %}{% if post.collectible_tag_code %}🚨 <b>Прикреплён коллекционный тег:</b> {{ post.collectible_tag_code }}{% endif %}{% if not post.is_visible_in_feeds %}👀 <b>Пост виден только в комнате! Можно модерить без жести.</b>{% endif %}
|
||||
|
||||
{% load posts %}{% render_plain post 350 %}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ from django.contrib import admin
|
|||
|
||||
from posts.models.linked import LinkedPost
|
||||
from posts.models.post import Post
|
||||
from posts.models.topics import Topic
|
||||
|
||||
|
||||
class PostsAdmin(admin.ModelAdmin):
|
||||
|
@ -26,25 +25,6 @@ class PostsAdmin(admin.ModelAdmin):
|
|||
admin.site.register(Post, PostsAdmin)
|
||||
|
||||
|
||||
class TopicsAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"slug",
|
||||
"name",
|
||||
"icon",
|
||||
"color",
|
||||
"style",
|
||||
"chat_name",
|
||||
"last_activity_at",
|
||||
"is_visible",
|
||||
"is_visible_in_feeds",
|
||||
)
|
||||
ordering = ("slug",)
|
||||
search_fields = ["slug", "name"]
|
||||
|
||||
|
||||
admin.site.register(Topic, TopicsAdmin)
|
||||
|
||||
|
||||
class LinkedPostsAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"post_from",
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
from rooms.models import Room
|
||||
|
||||
|
||||
def rooms(request):
|
||||
rooms = Room.objects.filter(is_visible=True, is_open_for_posting=True).order_by("-last_activity_at").all()
|
||||
return {
|
||||
"rooms": rooms,
|
||||
"rooms_map": {room.slug: room for room in rooms},
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
from posts.models.topics import Topic
|
||||
|
||||
|
||||
def topics(request):
|
||||
topics = Topic.objects.filter(is_visible=True).all()
|
||||
return {
|
||||
"topics": Topic.objects.filter(is_visible=True).all(),
|
||||
"topics_map": {t.slug: t for t in topics},
|
||||
}
|
|
@ -58,6 +58,11 @@ class PostCuratorForm(forms.Form):
|
|||
required=False,
|
||||
)
|
||||
|
||||
show_in_feeds = forms.BooleanField(
|
||||
label="Вернуть на главную (или вытащить из комнаты)",
|
||||
required=False,
|
||||
)
|
||||
|
||||
re_ping_collectible_tag_owners = forms.BooleanField(
|
||||
label="Перепингануть подписчиков коллективного тега",
|
||||
required=False,
|
||||
|
|
|
@ -9,8 +9,8 @@ from slugify import slugify_filename
|
|||
from common.regexp import EMOJI_RE
|
||||
from common.url_metadata_parser import parse_url_preview
|
||||
from posts.models.post import Post
|
||||
from posts.models.topics import Topic
|
||||
from common.forms import ImageUploadField
|
||||
from common.forms import ImageUploadField, ReverseBooleanField
|
||||
from rooms.models import Room
|
||||
from tags.models import Tag
|
||||
from users.models.user import User
|
||||
|
||||
|
@ -58,17 +58,22 @@ class CollectibleTagField(forms.CharField):
|
|||
|
||||
|
||||
class PostForm(forms.ModelForm):
|
||||
topic = forms.ModelChoiceField(
|
||||
room = forms.ModelChoiceField(
|
||||
label="Комната",
|
||||
required=False,
|
||||
empty_label="Для всех",
|
||||
queryset=Topic.objects.filter(is_visible=True).all(),
|
||||
queryset=Room.objects.filter(is_visible=True, is_open_for_posting=True).order_by("title").all(),
|
||||
)
|
||||
collectible_tag_code = CollectibleTagField(
|
||||
label="Прикрепить коллекционный тег",
|
||||
max_length=32,
|
||||
required=False,
|
||||
)
|
||||
is_visible_in_feeds = ReverseBooleanField(
|
||||
label="Пост только для этой комнаты (не отображается на главной)",
|
||||
initial=True,
|
||||
required=False
|
||||
)
|
||||
is_public = forms.ChoiceField(
|
||||
label="Виден ли в большой интернет?",
|
||||
choices=((True, "Публичный пост"), (False, "Только для своих")),
|
||||
|
@ -79,15 +84,6 @@ class PostForm(forms.ModelForm):
|
|||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def clean_topic(self):
|
||||
topic = self.cleaned_data["topic"]
|
||||
|
||||
if topic and not topic.is_visible_in_feeds:
|
||||
# topic settings are more important
|
||||
self.instance.is_visible_in_feeds = False
|
||||
|
||||
return topic
|
||||
|
||||
def clean_coauthors(self):
|
||||
coauthors = [coauthor.replace("@", "", 1) for coauthor in self.cleaned_data.get("coauthors")]
|
||||
if not coauthors:
|
||||
|
@ -104,6 +100,17 @@ class PostForm(forms.ModelForm):
|
|||
|
||||
return coauthors
|
||||
|
||||
def clean_is_visible_in_feeds(self):
|
||||
new_value = self.cleaned_data.get("is_visible_in_feeds")
|
||||
|
||||
if new_value is None:
|
||||
return self.instance.is_visible_in_feeds
|
||||
|
||||
if new_value and not self.instance.is_visible_in_feeds:
|
||||
raise ValidationError("Нельзя вытаскивать посты обратно из комнат. Только модератор может это сделать")
|
||||
|
||||
return new_value
|
||||
|
||||
|
||||
class PostTextForm(PostForm):
|
||||
title = forms.CharField(
|
||||
|
@ -136,9 +143,10 @@ class PostTextForm(PostForm):
|
|||
fields = [
|
||||
"title",
|
||||
"text",
|
||||
"topic",
|
||||
"room",
|
||||
"coauthors",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public",
|
||||
]
|
||||
|
||||
|
@ -179,8 +187,9 @@ class PostLinkForm(PostForm):
|
|||
"title",
|
||||
"text",
|
||||
"url",
|
||||
"topic",
|
||||
"room",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public",
|
||||
]
|
||||
|
||||
|
@ -227,8 +236,9 @@ class PostQuestionForm(PostForm):
|
|||
fields = [
|
||||
"title",
|
||||
"text",
|
||||
"topic",
|
||||
"room",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public"
|
||||
]
|
||||
|
||||
|
@ -259,8 +269,9 @@ class PostIdeaForm(PostForm):
|
|||
fields = [
|
||||
"title",
|
||||
"text",
|
||||
"topic",
|
||||
"room",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public",
|
||||
]
|
||||
|
||||
|
@ -370,9 +381,10 @@ class PostEventForm(PostForm):
|
|||
fields = [
|
||||
"title",
|
||||
"text",
|
||||
"topic",
|
||||
"room",
|
||||
"coauthors",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public"
|
||||
]
|
||||
|
||||
|
@ -478,11 +490,12 @@ class PostProjectForm(PostForm):
|
|||
fields = [
|
||||
"title",
|
||||
"text",
|
||||
"topic",
|
||||
"room",
|
||||
"url",
|
||||
"image",
|
||||
"coauthors",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public",
|
||||
]
|
||||
|
||||
|
@ -528,8 +541,9 @@ class PostBattleForm(PostForm):
|
|||
model = Post
|
||||
fields = [
|
||||
"text",
|
||||
"topic",
|
||||
"room",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public",
|
||||
]
|
||||
|
||||
|
@ -641,9 +655,10 @@ class PostGuideForm(PostForm):
|
|||
fields = [
|
||||
"title",
|
||||
"text",
|
||||
"topic",
|
||||
"room",
|
||||
"coauthors",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public",
|
||||
]
|
||||
|
||||
|
@ -692,9 +707,10 @@ class PostThreadForm(PostForm):
|
|||
"title",
|
||||
"text",
|
||||
"comment_template",
|
||||
"topic",
|
||||
"room",
|
||||
"coauthors",
|
||||
"collectible_tag_code",
|
||||
"is_visible_in_feeds",
|
||||
"is_public",
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-31 10:14
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rooms', '0001_initial'),
|
||||
('posts', '0026_auto_20220615_1047'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='post',
|
||||
name='room',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='posts', to='rooms.room'),
|
||||
),
|
||||
migrations.RunSQL(
|
||||
sql="update posts set room_id = topic_id where topic_id is not null",
|
||||
reverse_sql="update posts set topic_id = room_id where room_id is not null"
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='post',
|
||||
name='topic',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Topic',
|
||||
),
|
||||
]
|
|
@ -12,7 +12,7 @@ from simple_history.models import HistoricalRecords
|
|||
|
||||
from common.data.labels import LABELS
|
||||
from common.models import ModelDiffMixin
|
||||
from posts.models.topics import Topic
|
||||
from rooms.models import Room
|
||||
from users.models.user import User
|
||||
from utils.slug import generate_unique_slug
|
||||
|
||||
|
@ -74,7 +74,7 @@ class Post(models.Model, ModelDiffMixin):
|
|||
|
||||
author = models.ForeignKey(User, related_name="posts", db_index=True, on_delete=models.CASCADE)
|
||||
type = models.CharField(max_length=32, choices=TYPES, default=TYPE_POST, db_index=True)
|
||||
topic = models.ForeignKey(Topic, related_name="posts", null=True, db_index=True, on_delete=models.SET_NULL)
|
||||
room = models.ForeignKey(Room, related_name="posts", null=True, db_index=True, on_delete=models.SET_NULL)
|
||||
label_code = models.CharField(max_length=16, null=True, db_index=True)
|
||||
collectible_tag_code = models.CharField(max_length=32, null=True)
|
||||
coauthors = ArrayField(models.CharField(max_length=32), default=list, null=False, db_index=True)
|
||||
|
@ -126,7 +126,7 @@ class Post(models.Model, ModelDiffMixin):
|
|||
"is_visible_in_feeds",
|
||||
"is_pinned_until",
|
||||
"is_shadow_banned",
|
||||
"topic",
|
||||
"room",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -274,7 +274,7 @@ class Post(models.Model, ModelDiffMixin):
|
|||
|
||||
@classmethod
|
||||
def visible_objects(cls):
|
||||
return cls.objects.filter(is_visible=True).select_related("topic", "author")
|
||||
return cls.objects.filter(is_visible=True).select_related("room", "author")
|
||||
|
||||
@classmethod
|
||||
def objects_for_user(cls, user):
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Topic(models.Model):
|
||||
slug = models.CharField(primary_key=True, max_length=32, unique=True)
|
||||
|
||||
name = models.CharField(max_length=64, null=False)
|
||||
icon = models.URLField(null=True)
|
||||
description = models.TextField(null=True)
|
||||
color = models.CharField(max_length=16, null=False)
|
||||
style = models.CharField(max_length=256, default="", null=True)
|
||||
|
||||
chat_name = models.CharField(max_length=128, null=True)
|
||||
chat_url = models.URLField(null=True)
|
||||
chat_id = models.CharField(max_length=64, null=True)
|
||||
|
||||
last_activity_at = models.DateTimeField(auto_now_add=True, null=False)
|
||||
|
||||
is_visible = models.BooleanField(default=True)
|
||||
is_visible_in_feeds = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "topics"
|
||||
ordering = ["-last_activity_at"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def update_last_activity(self):
|
||||
now = datetime.utcnow()
|
||||
if self.last_activity_at < now - timedelta(minutes=5):
|
||||
return Topic.objects.filter(slug=self.slug).update(last_activity_at=now)
|
|
@ -56,9 +56,9 @@ def render_plain(context, post, truncate=None):
|
|||
|
||||
|
||||
@register.simple_tag()
|
||||
def feed_ordering_url(topic, label_code, post_type, ordering_type):
|
||||
if topic:
|
||||
return reverse("feed_topic_ordering", args=[topic.slug, ordering_type])
|
||||
def feed_ordering_url(room, label_code, post_type, ordering_type):
|
||||
if room:
|
||||
return reverse("feed_room_ordering", args=[room.slug, ordering_type])
|
||||
elif label_code:
|
||||
return reverse("feed_label_ordering", args=[label_code, ordering_type])
|
||||
else:
|
||||
|
|
|
@ -166,6 +166,11 @@ def do_common_admin_and_curator_actions(request, post, data):
|
|||
post.is_visible_in_feeds = False
|
||||
post.save()
|
||||
|
||||
# Show back in feeds
|
||||
if data["show_in_feeds"]:
|
||||
post.is_visible_in_feeds = True
|
||||
post.save()
|
||||
|
||||
# Ping collectible tag owners again
|
||||
if data["re_ping_collectible_tag_owners"]:
|
||||
if post.collectible_tag_code:
|
||||
|
|
|
@ -9,12 +9,12 @@ from common.feature_flags import feature_switch, noop
|
|||
from common.pagination import paginate
|
||||
from posts.helpers import POST_TYPE_ALL, ORDERING_ACTIVITY, ORDERING_NEW, sort_feed
|
||||
from posts.models.post import Post
|
||||
from posts.models.topics import Topic
|
||||
from rooms.models import Room
|
||||
from users.models.mute import Muted
|
||||
|
||||
|
||||
@feature_switch(features.PRIVATE_FEED, yes=require_auth, no=noop)
|
||||
def feed(request, post_type=POST_TYPE_ALL, topic_slug=None, label_code=None, ordering=ORDERING_ACTIVITY):
|
||||
def feed(request, post_type=POST_TYPE_ALL, room_slug=None, label_code=None, ordering=ORDERING_ACTIVITY):
|
||||
post_type = post_type or Post
|
||||
|
||||
if request.me:
|
||||
|
@ -27,12 +27,12 @@ def feed(request, post_type=POST_TYPE_ALL, topic_slug=None, label_code=None, ord
|
|||
if post_type != POST_TYPE_ALL:
|
||||
posts = posts.filter(type=post_type)
|
||||
|
||||
# filter by topic
|
||||
if topic_slug:
|
||||
topic = get_object_or_404(Topic, slug=topic_slug)
|
||||
posts = posts.filter(topic=topic)
|
||||
# filter by room
|
||||
if room_slug:
|
||||
room = get_object_or_404(Room, slug=room_slug)
|
||||
posts = posts.filter(room=room)
|
||||
else:
|
||||
topic = None
|
||||
room = None
|
||||
|
||||
# filter by label
|
||||
if label_code:
|
||||
|
@ -56,7 +56,7 @@ def feed(request, post_type=POST_TYPE_ALL, topic_slug=None, label_code=None, ord
|
|||
posts = posts.exclude(is_shadow_banned=True)
|
||||
|
||||
# hide no-feed posts (show only inside rooms and topics)
|
||||
if not topic and not label_code:
|
||||
if not room and not label_code:
|
||||
posts = posts.filter(is_visible_in_feeds=True)
|
||||
|
||||
# order posts by some metric
|
||||
|
@ -71,7 +71,7 @@ def feed(request, post_type=POST_TYPE_ALL, topic_slug=None, label_code=None, ord
|
|||
return render(request, "feed.html", {
|
||||
"post_type": post_type or POST_TYPE_ALL,
|
||||
"ordering": ordering,
|
||||
"topic": topic,
|
||||
"room": room,
|
||||
"label_code": label_code,
|
||||
"posts": paginate(request, posts),
|
||||
"pinned_posts": pinned_posts,
|
||||
|
|
|
@ -241,8 +241,8 @@ def create_or_edit(request, post_type, post=None, mode="create"):
|
|||
PostSubscription.subscribe(request.me, post, type=PostSubscription.TYPE_ALL_COMMENTS)
|
||||
|
||||
if post.is_visible:
|
||||
if post.topic:
|
||||
post.topic.update_last_activity()
|
||||
if post.room:
|
||||
post.room.update_last_activity()
|
||||
|
||||
SearchIndex.update_post_index(post)
|
||||
LinkedPost.create_links_from_text(post, post.text)
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from rooms.models import Room
|
||||
|
||||
|
||||
class RoomsAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"slug",
|
||||
"title",
|
||||
"subtitle",
|
||||
"image",
|
||||
"icon",
|
||||
"color",
|
||||
"chat_name",
|
||||
"network_group",
|
||||
"last_activity_at",
|
||||
"is_visible",
|
||||
"is_open_for_posting",
|
||||
"is_bot_active",
|
||||
"index",
|
||||
)
|
||||
ordering = ("title",)
|
||||
search_fields = ["title", "description", "slug"]
|
||||
|
||||
|
||||
admin.site.register(Room, RoomsAdmin)
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RoomsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'rooms'
|
|
@ -0,0 +1,72 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-31 09:36
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('misc', '0003_alter_networkitem_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Room',
|
||||
fields=[
|
||||
('slug', models.CharField(max_length=32, primary_key=True, serialize=False, unique=True)),
|
||||
('title', models.CharField(max_length=64)),
|
||||
('subtitle', models.CharField(blank=True, max_length=256, null=True)),
|
||||
('image', models.URLField()),
|
||||
('icon', models.URLField(blank=True, null=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('color', models.CharField(max_length=16)),
|
||||
('style', models.CharField(blank=True, default='', max_length=256, null=True)),
|
||||
('url', models.URLField(blank=True, null=True)),
|
||||
('chat_name', models.CharField(blank=True, max_length=128, null=True)),
|
||||
('chat_url', models.URLField(blank=True, null=True)),
|
||||
('chat_id', models.CharField(blank=True, max_length=32, null=True)),
|
||||
('last_activity_at', models.DateTimeField(auto_now_add=True)),
|
||||
('is_visible', models.BooleanField(default=True)),
|
||||
('is_bot_active', models.BooleanField(default=True)),
|
||||
('index', models.PositiveIntegerField(default=0)),
|
||||
('network_group', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rooms', to='misc.networkgroup')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'rooms',
|
||||
'ordering': ['-last_activity_at'],
|
||||
},
|
||||
),
|
||||
migrations.RunSQL("""
|
||||
insert into rooms (slug, title, subtitle, image, icon, description, color, style, url, chat_name,
|
||||
chat_url, chat_id, last_activity_at, is_visible, is_bot_active, "index", network_group_id)
|
||||
select id, name, description, image, icon, '', '#282c35', '', '', name, url, telegram_chat_id, now(),
|
||||
true, true, index, group_id from network_items;
|
||||
"""),
|
||||
migrations.RunSQL("""
|
||||
insert into rooms (slug, title, subtitle, image, icon, description, color, style, url, chat_name,
|
||||
chat_url, chat_id, last_activity_at, is_visible, is_bot_active, "index", network_group_id)
|
||||
select slug, name, '', icon, '', description, color, style, '', chat_name, chat_url, chat_id, now(),
|
||||
true, false, 0, null from topics where slug in ('nepotism', 'productivity', 'hobby', 'dumbasshome');
|
||||
"""),
|
||||
migrations.RunSQL("""
|
||||
update rooms set (image, description, color, chat_id) = (select icon, description, color, chat_id from topics where slug = 'books') where slug = 'books';
|
||||
"""),
|
||||
migrations.RunSQL("""
|
||||
update rooms set (slug, description, color, chat_id) = (select slug, description, color, chat_id from topics where slug = 'chef') where slug = 'cooking';
|
||||
"""),
|
||||
migrations.RunSQL("""
|
||||
update rooms set (slug, image, description, color, chat_id) = (select slug, icon, description, color, chat_id from topics where slug = 'indie') where slug = 'indiehackers';
|
||||
"""),
|
||||
migrations.RunSQL("""
|
||||
update rooms set (slug, title, image, description, color, chat_id) = (select slug, name, icon, description, color, chat_id from topics where slug = 'stonks') where slug = 'fire';
|
||||
"""),
|
||||
migrations.RunSQL("""
|
||||
update rooms set (image, title, description, color, chat_id) = (select icon, name, description, color, chat_id from topics where slug = 'tractor') where slug = 'tractor';
|
||||
"""),
|
||||
migrations.RunSQL("""
|
||||
update rooms set (image, description, color) = (select icon, description, color from topics where slug = 'travel') where slug = 'travel';
|
||||
""")
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-31 14:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rooms', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='room',
|
||||
options={'ordering': ['index']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='room',
|
||||
name='is_open_for_posting',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,56 @@
|
|||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class Room(models.Model):
|
||||
slug = models.CharField(primary_key=True, max_length=32, unique=True)
|
||||
|
||||
title = models.CharField(max_length=64, null=False)
|
||||
subtitle = models.CharField(max_length=256, null=True, blank=True)
|
||||
image = models.URLField(null=False)
|
||||
icon = models.URLField(null=True, blank=True)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
color = models.CharField(max_length=16, null=False)
|
||||
style = models.CharField(max_length=256, default="", null=True, blank=True)
|
||||
url = models.URLField(null=True, blank=True)
|
||||
|
||||
chat_name = models.CharField(max_length=128, null=True, blank=True)
|
||||
chat_url = models.URLField(null=True, blank=True)
|
||||
chat_id = models.CharField(max_length=32, null=True, blank=True)
|
||||
|
||||
network_group = models.ForeignKey(
|
||||
"misc.NetworkGroup",
|
||||
related_name="rooms",
|
||||
db_index=True, null=True,
|
||||
on_delete=models.SET_NULL
|
||||
)
|
||||
last_activity_at = models.DateTimeField(auto_now_add=True, null=False)
|
||||
|
||||
is_visible = models.BooleanField(default=True)
|
||||
is_open_for_posting = models.BooleanField(default=True)
|
||||
is_bot_active = models.BooleanField(default=True)
|
||||
|
||||
index = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
db_table = "rooms"
|
||||
ordering = ["index"]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def emoji(self):
|
||||
return re.sub("<.*?>", "", self.icon) if self.icon else ""
|
||||
|
||||
def update_last_activity(self):
|
||||
now = datetime.utcnow()
|
||||
if self.last_activity_at < now - timedelta(minutes=5):
|
||||
return Room.objects.filter(slug=self.slug).update(last_activity_at=now)
|
||||
|
||||
def get_private_url(self):
|
||||
if self.url or self.chat_url:
|
||||
return reverse("redirect_to_room_chat", kwargs={"room_slug": self.slug})
|
||||
return None
|
|
@ -0,0 +1,21 @@
|
|||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
|
||||
from authn.decorators.auth import require_auth
|
||||
from rooms.models import Room
|
||||
|
||||
|
||||
@require_auth
|
||||
def list_rooms(request):
|
||||
return render(request, "rooms/list_rooms.html")
|
||||
|
||||
|
||||
@require_auth
|
||||
def redirect_to_room_chat(request, room_slug):
|
||||
room = get_object_or_404(Room, slug=room_slug)
|
||||
if room.chat_url:
|
||||
return redirect(room.chat_url, permanent=False)
|
||||
elif room.url:
|
||||
return redirect(room.url, permanent=False)
|
||||
else:
|
||||
raise Http404()
|
|
@ -77,7 +77,7 @@ class SearchIndex(models.Model):
|
|||
vector = _multi_search_vector("title", weight="A") \
|
||||
+ _multi_search_vector("text", weight="B") \
|
||||
+ _multi_search_vector("author__slug", weight="C") \
|
||||
+ _multi_search_vector("topic__name", weight="C")
|
||||
+ _multi_search_vector("room__title", weight="C")
|
||||
|
||||
if post.is_searchable:
|
||||
SearchIndex.objects.update_or_create(
|
||||
|
|
Loading…
Reference in New Issue