flaskbb-plugin-conversations 3.0.0__tar.gz → 3.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/PKG-INFO +1 -1
  2. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/__init__.py +8 -1
  3. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/forms.py +1 -0
  4. flaskbb_plugin_conversations-3.0.2/conversations/migrations/202602062122_1770409336_update_sqlalchemy_2.py +76 -0
  5. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/conversation.html +2 -2
  6. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/conversation_list.html +2 -2
  7. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/message_layout.html +8 -7
  8. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/views.py +4 -0
  9. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/pyproject.toml +1 -1
  10. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/tests/test_views.py +36 -2
  11. flaskbb_plugin_conversations-3.0.0/conversations/migrations/202602062122_1770409336_update_sqlalchemy_2.py +0 -46
  12. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/.gitignore +0 -0
  13. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/LICENSE +0 -0
  14. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/README.md +0 -0
  15. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/migrations/201802171929_0625bc75ab77_add_conversations.py +0 -0
  16. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/migrations/202607311750_1785520254_move_message_quota_setting.py +0 -0
  17. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/migrations/__init__.py +0 -0
  18. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/models.py +0 -0
  19. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/py.typed +0 -0
  20. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/_inject_navlink.html +0 -0
  21. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/_inject_new_message_button.html +0 -0
  22. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/_inject_new_message_link.html +0 -0
  23. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/drafts.html +0 -0
  24. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/inbox.html +0 -0
  25. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/message_form.html +0 -0
  26. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/sent.html +0 -0
  27. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/templates/conversations/trash.html +0 -0
  28. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/translations/en/LC_MESSAGES/messages.po +0 -0
  29. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/conversations/utils.py +0 -0
  30. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/tests/conftest.py +0 -0
  31. {flaskbb_plugin_conversations-3.0.0 → flaskbb_plugin_conversations-3.0.2}/tests/test_message_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flaskbb-plugin-conversations
3
- Version: 3.0.0
3
+ Version: 3.0.2
4
4
  Summary: A private messaging plugin for FlaskBB
5
5
  Project-URL: Homepage, https://github.com/flaskbb/flaskbb-plugin-conversations
6
6
  Project-URL: Repository, https://github.com/flaskbb/flaskbb-plugin-conversations
@@ -14,7 +14,7 @@ from typing import Any, cast
14
14
  from flask import Flask
15
15
  from flask_login import current_user
16
16
  from flaskbb.forum.models import Post
17
- from flaskbb.settings.definitions import IntSetting, SettingGroup
17
+ from flaskbb.settings.definitions import BoolSetting, IntSetting, SettingGroup
18
18
  from flaskbb.user.models import User
19
19
  from flaskbb.utils.helpers import real, render_template
20
20
  from pluggy import HookimplMarker
@@ -29,6 +29,13 @@ SETTINGS = SettingGroup(
29
29
  name="Conversations Settings",
30
30
  description="Settings for the conversations plugin.",
31
31
  settings=(
32
+ BoolSetting(
33
+ key="MESSAGE_QUOTA_ENABLED",
34
+ value=True,
35
+ name="Enable Private Message Quota",
36
+ description="Limit the amount of messages a user can have. "
37
+ "If disabled, users can have unlimited messages.",
38
+ ),
32
39
  IntSetting(
33
40
  key="MESSAGE_QUOTA",
34
41
  value=50,
@@ -29,6 +29,7 @@ class ConversationForm(FlaskForm):
29
29
  to_user = StringField(
30
30
  _("Recipient"),
31
31
  validators=[DataRequired(message=_("A valid username is required."))],
32
+ render_kw={"data-user-lookup": "", "autocomplete": "off"},
32
33
  )
33
34
 
34
35
  subject = StringField(
@@ -0,0 +1,76 @@
1
+ """Update SQLAlchemy 2
2
+
3
+ Revision ID: 1770409336
4
+ Revises: 0625bc75ab77
5
+ Create Date: 2026-02-06 21:22:16.464657
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "1770409336"
14
+ down_revision = "0625bc75ab77"
15
+ branch_labels = ()
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # Version 1.x stored the 16 UUID bytes, sa.Uuid() stores 32 hex characters
21
+ # on MySQL and SQLite
22
+ bind = op.get_bind()
23
+ if bind.dialect.name == "sqlite":
24
+ # the column has NUMERIC affinity until the rebuild, which would turn
25
+ # hex strings like "1234e567..." into numbers - the "x" prevents that
26
+ op.execute(
27
+ "UPDATE conversations SET shared_id = 'x' || lower(hex(shared_id)) "
28
+ "WHERE typeof(shared_id) = 'blob'"
29
+ )
30
+ elif bind.dialect.name == "mysql":
31
+ shared_id = next(
32
+ column
33
+ for column in sa.inspect(bind).get_columns("conversations")
34
+ if column["name"] == "shared_id"
35
+ )
36
+ if isinstance(shared_id["type"], sa.BINARY):
37
+ op.alter_column(
38
+ "conversations",
39
+ "shared_id",
40
+ existing_type=shared_id["type"],
41
+ type_=sa.VARBINARY(32),
42
+ existing_nullable=False,
43
+ )
44
+ op.execute("UPDATE conversations SET shared_id = LOWER(HEX(shared_id))")
45
+
46
+ with op.batch_alter_table("conversations", schema=None) as batch_op:
47
+ batch_op.alter_column(
48
+ "shared_id",
49
+ existing_type=sa.NUMERIC(precision=16),
50
+ type_=sa.Uuid(),
51
+ existing_nullable=False,
52
+ )
53
+
54
+ if bind.dialect.name == "sqlite":
55
+ op.execute(
56
+ "UPDATE conversations SET shared_id = substr(shared_id, 2) WHERE shared_id LIKE 'x%'"
57
+ )
58
+
59
+ with op.batch_alter_table("messages", schema=None) as batch_op:
60
+ batch_op.alter_column("message", existing_type=sa.TEXT(), nullable=True)
61
+ # ### end Alembic commands ###
62
+
63
+
64
+ def downgrade():
65
+ with op.batch_alter_table("messages", schema=None) as batch_op:
66
+ batch_op.alter_column("message", existing_type=sa.TEXT(), nullable=False)
67
+
68
+ with op.batch_alter_table("conversations", schema=None) as batch_op:
69
+ batch_op.alter_column(
70
+ "shared_id",
71
+ existing_type=sa.Uuid(),
72
+ type_=sa.NUMERIC(precision=16),
73
+ existing_nullable=False,
74
+ )
75
+
76
+ # ### end Alembic commands ###
@@ -33,7 +33,7 @@
33
33
  <div class="author-title"><h5>{{ message.user.primary_group.name }}</h5></div>
34
34
 
35
35
  {% if message.user.avatar %}
36
- <div class="author-avatar"><img src="{{ message.user.avatar }}" alt="avatar"></div>
36
+ <div class="author-avatar"><img src="{{ message.user.avatar_url }}" alt="avatar"></div>
37
37
  {% endif %}
38
38
 
39
39
  <div class="author-registered">{% trans %}Joined{% endtrans %}: {{ message.user.date_joined|format_date }}</div>
@@ -84,7 +84,7 @@
84
84
  <div class="author-title"><h5>{{ message.user.primary_group.name }}</h5></div>
85
85
 
86
86
  {% if message.user.avatar %}
87
- <div class="author-avatar"><img src="{{ message.user.avatar }}" alt="avatar"></div>
87
+ <div class="author-avatar"><img src="{{ message.user.avatar_url }}" alt="avatar"></div>
88
88
  {% endif %}
89
89
 
90
90
  <div class="author-registered">{% trans %}Joined{% endtrans %}: {{ message.user.date_joined|format_date }}</div>
@@ -9,7 +9,7 @@
9
9
 
10
10
  <div class="col-auto">
11
11
  <span class="badge bg-info" title="The amount of all conversations" data-bs-toggle="tooltip">
12
- {{ current_user.message_count or 0 }}/{{ flaskbb_config["CONVERSATIONS_MESSAGE_QUOTA"] }}
12
+ {{ current_user.message_count or 0 }}{% if flaskbb_config["CONVERSATIONS_MESSAGE_QUOTA_ENABLED"] is not sameas false %}/{{ flaskbb_config["CONVERSATIONS_MESSAGE_QUOTA"] }}{% endif %}
13
13
  </span>
14
14
  </div>
15
15
  </div>
@@ -21,7 +21,7 @@
21
21
  <!-- avatar -->
22
22
  <div class="col-md-1 col-sm-2 col-2 conversation-avatar">
23
23
  {% if conversation.from_user and conversation.from_user.avatar %}
24
- <img src="{{ conversation.from_user.avatar }}" class="rounded" alt="avatar" width="65px" height="65px" />
24
+ <img src="{{ conversation.from_user.avatar_url }}" class="rounded" alt="avatar" width="65px" height="65px" />
25
25
  {% else %}
26
26
  <img src="{{ url_for('static', filename='avatar80x80.png') }}" class="rounded" alt="avatar" width="65px" height="65px" />
27
27
  {% endif %}
@@ -40,15 +40,16 @@
40
40
 
41
41
  const editor = document.querySelector(".flaskbb-editor");
42
42
  fetch(button.dataset.rawUrl)
43
- .then((response) => response.text())
44
- .then((data) => {
45
- editor.value = data;
46
- editor.selectionStart = editor.selectionEnd = editor.value.length;
47
- editor.scrollTop = editor.scrollHeight;
48
- window.location.href = "#content";
43
+ .then((response) => {
44
+ // an expired session redirects to the login page
45
+ if (response.redirected || !response.ok) {
46
+ throw new Error(`could not load ${button.dataset.rawUrl}`);
47
+ }
48
+ return response.text();
49
49
  })
50
+ .then((quote) => window.app.insertQuote(editor, quote))
50
51
  .catch((error) => {
51
- console.error("something bad happened", error);
52
+ console.error("could not quote the message", error);
52
53
  });
53
54
  });
54
55
  </script>
@@ -50,6 +50,10 @@ def check_message_box_space(redirect_to: str | None = None):
50
50
  will redirect to the ``conversations_bp.inbox``
51
51
  endpoint.
52
52
  """
53
+ # an unset value (settings not yet upgraded) keeps the quota enforced
54
+ if flaskbb_config["CONVERSATIONS_MESSAGE_QUOTA_ENABLED"] is False:
55
+ return None
56
+
53
57
  quota = flaskbb_config["CONVERSATIONS_MESSAGE_QUOTA"]
54
58
  if get_message_count(real(current_user).id) >= quota:
55
59
  flash(
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flaskbb-plugin-conversations"
3
- version = "3.0.0"
3
+ version = "3.0.2"
4
4
  dependencies = [
5
5
  "FlaskBB>=3.0.0",
6
6
  ]
@@ -12,6 +12,7 @@ from flask_login import login_user, logout_user
12
12
  from flaskbb.extensions import db
13
13
 
14
14
  from conversations import views
15
+ from conversations.forms import ConversationForm
15
16
  from conversations.models import Conversation
16
17
 
17
18
 
@@ -63,7 +64,11 @@ def test_delete_returns_to_the_page_it_was_sent_from(application, conversation,
63
64
 
64
65
 
65
66
  def test_reply_over_the_quota_loads_the_inbox(application, conversation, user, monkeypatch):
66
- monkeypatch.setattr(views, "flaskbb_config", {"CONVERSATIONS_MESSAGE_QUOTA": 0})
67
+ monkeypatch.setattr(
68
+ views,
69
+ "flaskbb_config",
70
+ {"CONVERSATIONS_MESSAGE_QUOTA_ENABLED": True, "CONVERSATIONS_MESSAGE_QUOTA": 0},
71
+ )
67
72
 
68
73
  response, messages = _htmx_post(
69
74
  application,
@@ -113,7 +118,11 @@ def no_csrf(application):
113
118
 
114
119
  @pytest.fixture
115
120
  def no_quota(monkeypatch):
116
- monkeypatch.setattr(views, "flaskbb_config", {"CONVERSATIONS_MESSAGE_QUOTA": 0})
121
+ monkeypatch.setattr(
122
+ views,
123
+ "flaskbb_config",
124
+ {"CONVERSATIONS_MESSAGE_QUOTA_ENABLED": True, "CONVERSATIONS_MESSAGE_QUOTA": 0},
125
+ )
117
126
 
118
127
 
119
128
  def _send(application, view, actor, recipient, **kwargs):
@@ -164,3 +173,28 @@ def test_draft_over_the_quota_is_not_sent(
164
173
  assert conversation_msgs.draft
165
174
  # sending would have put a copy into the recipient's inbox
166
175
  assert Conversation.count() == 1
176
+
177
+
178
+ def test_disabled_quota_allows_sending_over_the_limit(
179
+ application, default_settings, no_csrf, user, admin_user, monkeypatch
180
+ ):
181
+ monkeypatch.setattr(
182
+ views,
183
+ "flaskbb_config",
184
+ {"CONVERSATIONS_MESSAGE_QUOTA_ENABLED": False, "CONVERSATIONS_MESSAGE_QUOTA": 0},
185
+ )
186
+
187
+ _response, messages = _send(
188
+ application, views.NewConversation.as_view("new_conversation"), user, admin_user
189
+ )
190
+
191
+ assert QUOTA_EXCEEDED not in messages
192
+ assert Conversation.count() == 2
193
+
194
+
195
+ def test_recipient_field_opts_into_the_user_lookup(application):
196
+ with application.test_request_context():
197
+ field = ConversationForm(meta={"csrf": False}).to_user()
198
+
199
+ assert "data-user-lookup" in field
200
+ assert 'autocomplete="off"' in field
@@ -1,46 +0,0 @@
1
- """Update SQLAlchemy 2
2
-
3
- Revision ID: 1770409336
4
- Revises: 0625bc75ab77
5
- Create Date: 2026-02-06 21:22:16.464657
6
-
7
- """
8
-
9
- import sqlalchemy as sa
10
- from alembic import op
11
-
12
- # revision identifiers, used by Alembic.
13
- revision = "1770409336"
14
- down_revision = "0625bc75ab77"
15
- branch_labels = ()
16
- depends_on = None
17
-
18
-
19
- def upgrade():
20
- # ### commands auto generated by Alembic - please adjust! ###
21
- with op.batch_alter_table("conversations", schema=None) as batch_op:
22
- batch_op.alter_column(
23
- "shared_id",
24
- existing_type=sa.NUMERIC(precision=16),
25
- type_=sa.Uuid(),
26
- existing_nullable=False,
27
- )
28
-
29
- with op.batch_alter_table("messages", schema=None) as batch_op:
30
- batch_op.alter_column("message", existing_type=sa.TEXT(), nullable=True)
31
- # ### end Alembic commands ###
32
-
33
-
34
- def downgrade():
35
- with op.batch_alter_table("messages", schema=None) as batch_op:
36
- batch_op.alter_column("message", existing_type=sa.TEXT(), nullable=False)
37
-
38
- with op.batch_alter_table("conversations", schema=None) as batch_op:
39
- batch_op.alter_column(
40
- "shared_id",
41
- existing_type=sa.Uuid(),
42
- type_=sa.NUMERIC(precision=16),
43
- existing_nullable=False,
44
- )
45
-
46
- # ### end Alembic commands ###