Skip to content

Commit

Permalink
Add ability to see and recycle sent messages (#3353)
Browse files Browse the repository at this point in the history
- Fix #3246: After the message is sent, it gets stored to the DB and displayed in the Sent panel.
- Fix #3247: Add ability to edit sent messages as new.
- Compose and Sent panels can now be linked to.
- After the message is sent, show it in the Sent panel.

Also included are some under the hood changes that will make code more maintainable:
- Create MessageForm from the Message model
- Use select element instead of input in multiple item selector widget
- Use form tags in the template
  • Loading branch information
mathjazz authored Oct 3, 2024
1 parent b8a2030 commit 138be1b
Show file tree
Hide file tree
Showing 21 changed files with 987 additions and 399 deletions.
4 changes: 4 additions & 0 deletions pontoon/base/static/css/check-box.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

.check-box {
padding: 4px 0;

[type='checkbox'] {
display: none;
}
}

.check-box:last-child {
Expand Down
11 changes: 8 additions & 3 deletions pontoon/base/static/css/sidebar_menu.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
.menu.left-column {
background: var(--background-hover-1);
border-radius: 6px;
box-sizing: border-box;
float: left;
padding: 10px;
width: 300px;
}

Expand All @@ -8,7 +12,8 @@
}

.menu.left-column li.selected {
background: var(--background-hover-1);
background: var(--background-hover-2);
border-radius: 6px;
}

.menu.left-column li.selected a {
Expand All @@ -21,8 +26,8 @@

.menu.left-column li a {
display: block;
font-size: 15px;
padding: 5px;
font-size: 16px;
padding: 10px;
}

.menu.left-column .count {
Expand Down
4 changes: 2 additions & 2 deletions pontoon/base/static/js/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ $(function () {
'click',
'#middle .links a, #main .contributors .links a',
function (e) {
// Keep default middle-, control- and command-click behaviour (open in new tab)
if (e.which === 2 || e.metaKey || e.ctrlKey) {
// Keep default middle-, shift-, control- and command-click behaviour
if (e.which === 2 || e.metaKey || e.shiftKey || e.ctrlKey) {
return;
}

Expand Down
5 changes: 4 additions & 1 deletion pontoon/base/templates/widgets/checkbox.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% macro checkbox(label, class, attribute, is_enabled=False, title=False, help=False) %}
{% macro checkbox(label, class, attribute, is_enabled=False, title=False, help=False, form_field=None) %}
<li class="check-box {{ class }}{% if is_enabled %} enabled{% endif %}" data-attribute="{{ attribute }}"{% if title %} title="{{title}}"{% endif %}>
<div class="check-box-wrapper">
<span class="label">{{ label }}</span>
Expand All @@ -7,5 +7,8 @@
{% if help %}
<p class="help">{{ help|safe }}</p>
{% endif %}
{% if form_field %}
{{ form_field }}
{% endif %}
</li>
{% endmacro %}
6 changes: 1 addition & 5 deletions pontoon/base/templates/widgets/item_selector_list.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% macro list(id, title, items, left_shortcut=None, right_shortcut=None, sortable=False, form_field=None) %}
{% macro list(id, title, items, left_shortcut=None, right_shortcut=None, sortable=False) %}
<div class="item select {{ id }}">
<label for="{{ id }}">
{% if left_shortcut %}
Expand All @@ -25,9 +25,5 @@
<li class="no-match">No results</li>
</ul>
</div>

{% if form_field %}
<input type="hidden" name="{{ form_field }}" value="{{ items|map(attribute='pk')|join(',') }}">
{% endif %}
</div>
{% endmacro %}
23 changes: 23 additions & 0 deletions pontoon/base/templatetags/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import html
import json

from datetime import timedelta

import markupsafe

from allauth.socialaccount import providers
Expand All @@ -15,6 +17,7 @@
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.serializers.json import DjangoJSONEncoder
from django.urls import reverse
from django.utils import timezone
from django.utils.http import url_has_allowed_host_and_scheme

from pontoon.base.fluent import get_simple_preview
Expand Down Expand Up @@ -177,6 +180,26 @@ def format_timedelta(value):
return "---"


@library.filter
def format_for_inbox(date):
# Localize the date to the current timezone
date = timezone.localtime(date)
now = timezone.now()

if date.date() == now.date():
# Same day: Only show time, e.g., "13:34"
return date.strftime("%H:%M")
elif date.date() == (now - timedelta(days=1)).date():
# Yesterday: Yesterday and time, e.g., "Yesterday, 13:34"
return date.strftime("Yesterday, %H:%M")
elif (now - date).days < 7:
# Within a week: Day and time, e.g., "Monday, 13:34"
return date.strftime("%A, %H:%M")
else:
# Older than a week: Full date and time, e.g., "Sep 12, 13:34"
return date.strftime("%b %d, %H:%M")


@register.filter
@library.filter
def nospam(self):
Expand Down
4 changes: 2 additions & 2 deletions pontoon/localizations/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
views.localization,
name="pontoon.localizations.contributors",
),
# Project insights
# Localization insights
path(
"insights/",
views.localization,
Expand Down Expand Up @@ -70,7 +70,7 @@
views.LocalizationContributorsView.as_view(),
name="pontoon.localizations.ajax.contributors",
),
# Project insights
# Localization insights
path(
"insights/",
views.ajax_insights,
Expand Down
11 changes: 11 additions & 0 deletions pontoon/messaging/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.contrib import admin

from pontoon.messaging import models


class MessageAdmin(admin.ModelAdmin):
list_display = ("pk", "sent_at", "sender", "subject", "body")
autocomplete_fields = ["sender", "recipients"]


admin.site.register(models.Message, MessageAdmin)
82 changes: 58 additions & 24 deletions pontoon/messaging/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,72 @@
from django.core import validators

from pontoon.base.forms import HtmlField
from pontoon.base.models import Project

from .models import Message

class MessageForm(forms.Form):
notification = forms.BooleanField(required=False)
email = forms.BooleanField(required=False)
transactional = forms.BooleanField(required=False)

subject = forms.CharField()
class MessageForm(forms.ModelForm):
body = HtmlField()

managers = forms.BooleanField(required=False)
translators = forms.BooleanField(required=False)
contributors = forms.BooleanField(required=False)
send_to_myself = forms.BooleanField(required=False)

locales = forms.CharField(
validators=[validators.validate_comma_separated_integer_list]
)
projects = forms.CharField(
validators=[validators.validate_comma_separated_integer_list]
widget=forms.Textarea(),
validators=[validators.validate_comma_separated_integer_list],
)

translation_minimum = forms.IntegerField(required=False, min_value=0)
translation_maximum = forms.IntegerField(required=False, min_value=0)
translation_from = forms.DateField(required=False)
translation_to = forms.DateField(required=False)
class Meta:
model = Message
fields = [
"notification",
"email",
"transactional",
"subject",
"body",
"managers",
"translators",
"contributors",
"locales",
"projects",
"translation_minimum",
"translation_maximum",
"translation_from",
"translation_to",
"review_minimum",
"review_maximum",
"review_from",
"review_to",
"login_from",
"login_to",
"send_to_myself",
]
widgets = {
"translation_from": forms.DateInput(attrs={"type": "date"}),
"translation_to": forms.DateInput(attrs={"type": "date"}),
"review_from": forms.DateInput(attrs={"type": "date"}),
"review_to": forms.DateInput(attrs={"type": "date"}),
"login_from": forms.DateInput(attrs={"type": "date"}),
"login_to": forms.DateInput(attrs={"type": "date"}),
}
labels = {
"translation_minimum": "Minimum",
"translation_maximum": "Maximum",
"translation_from": "From",
"translation_to": "To",
"review_minimum": "Minimum",
"review_maximum": "Maximum",
"review_from": "From",
"review_to": "To",
"login_from": "From",
"login_to": "To",
}

review_minimum = forms.IntegerField(required=False, min_value=0)
review_maximum = forms.IntegerField(required=False, min_value=0)
review_from = forms.DateField(required=False)
review_to = forms.DateField(required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

login_from = forms.DateField(required=False)
login_to = forms.DateField(required=False)
# Set all available Projects as selected
self.fields["projects"].initial = Project.objects.available()

send_to_myself = forms.BooleanField(required=False)
# Remove the colon from all field labels
for field in self.fields.values():
field.label_suffix = ""
101 changes: 101 additions & 0 deletions pontoon/messaging/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Generated by Django 4.2.11 on 2024-09-18 19:47

import django.db.models.deletion
import django.utils.timezone

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("base", "0065_fix_projectlocale_permissions"),
]

operations = [
migrations.CreateModel(
name="Message",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("sent_at", models.DateTimeField(default=django.utils.timezone.now)),
("notification", models.BooleanField(default=False)),
("email", models.BooleanField(default=False)),
("transactional", models.BooleanField(default=False)),
("subject", models.CharField(max_length=255)),
("body", models.TextField()),
("managers", models.BooleanField(default=True)),
("translators", models.BooleanField(default=True)),
("contributors", models.BooleanField(default=True)),
(
"translation_minimum",
models.PositiveIntegerField(blank=True, null=True),
),
(
"translation_maximum",
models.PositiveIntegerField(blank=True, null=True),
),
("translation_from", models.DateField(blank=True, null=True)),
("translation_to", models.DateField(blank=True, null=True)),
("review_minimum", models.PositiveIntegerField(blank=True, null=True)),
("review_maximum", models.PositiveIntegerField(blank=True, null=True)),
("review_from", models.DateField(blank=True, null=True)),
("review_to", models.DateField(blank=True, null=True)),
("login_from", models.DateField(blank=True, null=True)),
("login_to", models.DateField(blank=True, null=True)),
(
"locales",
models.ManyToManyField(related_name="messages", to="base.locale"),
),
(
"projects",
models.ManyToManyField(related_name="messages", to="base.project"),
),
(
"recipients",
models.ManyToManyField(
related_name="received_messages", to=settings.AUTH_USER_MODEL
),
),
(
"sender",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="messages",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.AddConstraint(
model_name="message",
constraint=models.CheckConstraint(
check=models.Q(
("notification", True), ("email", True), _connector="OR"
),
name="at_least_one_message_type",
),
),
migrations.AddConstraint(
model_name="message",
constraint=models.CheckConstraint(
check=models.Q(
("managers", True),
("translators", True),
("contributors", True),
_connector="OR",
),
name="at_least_one_user_role",
),
),
]
Empty file.
Loading

0 comments on commit 138be1b

Please sign in to comment.