Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛(i18n/backend) Fix/email in receiving user language #401

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ and this project adheres to

## [Unreleased]

## Added

## Changed

- ✨(frontend) sync user and frontend language #401

## Fixed

- 🐛(backend) invitation e-mails in receivers language #401


## [1.8.2] - 2024-11-28

Expand Down
2 changes: 1 addition & 1 deletion src/backend/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class UserSerializer(serializers.ModelSerializer):

class Meta:
model = models.User
fields = ["id", "email", "full_name", "short_name"]
fields = ["id", "email", "full_name", "short_name", "language"]
read_only_fields = ["id", "email", "full_name", "short_name"]


Expand Down
10 changes: 5 additions & 5 deletions src/backend/core/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -741,10 +741,9 @@ class DocumentAccessViewSet(
def perform_create(self, serializer):
"""Add a new access to the document and send an email to the new added user."""
access = serializer.save()
language = self.request.headers.get("Content-Language", "en-us")

access.document.email_invitation(
language,
access.user.language,
access.user.email,
access.role,
self.request.user,
Expand Down Expand Up @@ -984,10 +983,11 @@ def perform_create(self, serializer):
"""Save invitation to a document then send an email to the invited user."""
invitation = serializer.save()

language = self.request.headers.get("Content-Language", "en-us")

invitation.document.email_invitation(
language, invitation.email, invitation.role, self.request.user
self.request.user.language,
invitation.email,
invitation.role,
self.request.user,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
pytestmark = pytest.mark.django_db


# List


def test_api_document_accesses_list_anonymous():
"""Anonymous users should not be allowed to list document accesses."""
document = factories.DocumentFactory()
Expand Down Expand Up @@ -128,6 +131,9 @@ def test_api_document_accesses_list_authenticated_related(via, mock_user_teams):
)


# Retrieve


def test_api_document_accesses_retrieve_anonymous():
"""
Anonymous users should not be allowed to retrieve a document access.
Expand Down Expand Up @@ -216,6 +222,9 @@ def test_api_document_accesses_retrieve_authenticated_related(via, mock_user_tea
}


# Update


def test_api_document_accesses_update_anonymous():
"""Anonymous users should not be allowed to update a document access."""
access = factories.UserDocumentAccessFactory()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
pytestmark = pytest.mark.django_db


# Create


def test_api_document_accesses_create_anonymous():
"""Anonymous users should not be allowed to create document accesses."""
document = factories.DocumentFactory()
Expand Down Expand Up @@ -123,7 +126,7 @@ def test_api_document_accesses_create_authenticated_administrator(via, mock_user
document=document, team="lasuite", role="administrator"
)

other_user = factories.UserFactory()
other_user = factories.UserFactory(language="en-us")

# It should not be allowed to create an owner access
response = client.post(
Expand Down Expand Up @@ -198,7 +201,7 @@ def test_api_document_accesses_create_authenticated_owner(via, mock_user_teams):
document=document, team="lasuite", role="owner"
)

other_user = factories.UserFactory()
other_user = factories.UserFactory(language="en-us")

role = random.choice([role[0] for role in models.RoleChoices.choices])

Expand Down Expand Up @@ -233,3 +236,72 @@ def test_api_document_accesses_create_authenticated_owner(via, mock_user_teams):
in email_content
)
assert "docs/" + str(document.id) + "/" in email_content


@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_create_email_in_receivers_language(via, mock_user_teams):
"""
The email sent to the accesses to notify them of the adding, should be in their language.
"""
user = factories.UserFactory()

client = APIClient()
client.force_login(user)

document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
elif via == TEAM:
mock_user_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="owner"
)

role = random.choice([role[0] for role in models.RoleChoices.choices])

assert len(mail.outbox) == 0

other_users = (
factories.UserFactory(language="en-us"),
factories.UserFactory(language="fr-fr"),
)

for index, other_user in enumerate(other_users):
expected_language = other_user.language
response = client.post(
f"/api/v1.0/documents/{document.id!s}/accesses/",
{
"user_id": str(other_user.id),
"role": role,
},
format="json",
)

assert response.status_code == 201
assert models.DocumentAccess.objects.filter(user=other_user).count() == 1
new_document_access = models.DocumentAccess.objects.filter(
user=other_user
).get()
other_user_data = serializers.UserSerializer(instance=other_user).data
assert response.json() == {
"id": str(new_document_access.id),
"user": other_user_data,
"team": "",
"role": role,
"abilities": new_document_access.get_abilities(user),
}
assert len(mail.outbox) == index + 1
email = mail.outbox[index]
assert email.to == [other_user_data["email"]]
email_content = " ".join(email.body.split())
if expected_language == "en-us":
assert (
f"{user.full_name} shared a document with you: {document.title}"
in email_content
)
elif expected_language == "fr-fr":
assert (
f"{user.full_name} a partagé un document avec vous: {document.title}"
in email_content
)
assert "docs/" + str(document.id) + "/" in email_content
53 changes: 5 additions & 48 deletions src/backend/core/tests/documents/test_api_document_invitations.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ def test_api_document_invitations_create_privileged_members(
Only owners and administrators should be able to invite new users.
Only owners can invite owners.
"""
user = factories.UserFactory()
user = factories.UserFactory(language="en-us")
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=inviting)
Expand Down Expand Up @@ -417,11 +417,11 @@ def test_api_document_invitations_create_privileged_members(
}


def test_api_document_invitations_create_email_from_content_language():
rvveber marked this conversation as resolved.
Show resolved Hide resolved
def test_api_document_invitations_create_email_from_senders_language():
"""
The email generated is from the language set in the Content-Language header
When inviting on a document a user who does not exist yet in our database, the invitation email should be sent in the language of the sending user.
"""
user = factories.UserFactory()
user = factories.UserFactory(language="fr-fr")
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")

Expand All @@ -439,7 +439,6 @@ def test_api_document_invitations_create_email_from_content_language():
f"/api/v1.0/documents/{document.id!s}/invitations/",
invitation_values,
format="json",
headers={"Content-Language": "fr-fr"},
)

assert response.status_code == 201
Expand All @@ -458,53 +457,11 @@ def test_api_document_invitations_create_email_from_content_language():
)


def test_api_document_invitations_create_email_from_content_language_not_supported():
"""
If the language from the Content-Language is not supported
it will display the default language, English.
"""
user = factories.UserFactory()
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")

invitation_values = {
"email": "[email protected]",
"role": "reader",
}

assert len(mail.outbox) == 0

client = APIClient()
client.force_login(user)

response = client.post(
f"/api/v1.0/documents/{document.id!s}/invitations/",
invitation_values,
format="json",
headers={"Content-Language": "not-supported"},
)

assert response.status_code == 201
assert response.json()["email"] == "[email protected]"
assert models.Invitation.objects.count() == 1
assert len(mail.outbox) == 1

email = mail.outbox[0]

assert email.to == ["[email protected]"]

email_content = " ".join(email.body.split())
assert (
f"{user.full_name} shared a document with you: {document.title}"
in email_content
)


def test_api_document_invitations_create_email_full_name_empty():
"""
If the full name of the user is empty, it will display the email address.
"""
user = factories.UserFactory(full_name="")
user = factories.UserFactory(full_name="", language="en-us")
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")

Expand Down
6 changes: 5 additions & 1 deletion src/backend/core/tests/test_api_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ def test_api_config(is_authenticated):
"CRISP_WEBSITE_ID": "123",
"ENVIRONMENT": "test",
"FRONTEND_THEME": "test-theme",
"LANGUAGES": [["en-us", "English"], ["fr-fr", "French"], ["de-de", "German"]],
"LANGUAGES": [
["en-us", "English"],
["fr-fr", "Français"],
["de-de", "Deutsch"],
],
"LANGUAGE_CODE": "en-us",
"MEDIA_BASE_URL": "http://testserver/",
"SENTRY_DSN": "https://sentry.test/123",
Expand Down
1 change: 1 addition & 0 deletions src/backend/core/tests/test_api_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def test_api_users_retrieve_me_authenticated():
"id": str(user.id),
"email": user.email,
"full_name": user.full_name,
"language": user.language,
"short_name": user.short_name,
}

Expand Down
17 changes: 6 additions & 11 deletions src/backend/demo/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,12 @@
}

DEV_USERS = [
{"username": "impress", "email": "[email protected]", "language": "en-us"},
{"username": "user-e2e-webkit", "email": "[email protected]", "language": "en-us"},
{"username": "user-e2e-firefox", "email": "[email protected]", "language": "en-us"},
{
"username": "impress",
"email": "[email protected]",
"username": "user-e2e-chromium",
"email": "[email protected]",
"language": "en-us",
},
{
"username": "user-e2e-webkit",
"email": "[email protected]",
},
{
"username": "user-e2e-firefox",
"email": "[email protected]",
},
{"username": "user-e2e-chromium", "email": "[email protected]"},
]
3 changes: 2 additions & 1 deletion src/backend/demo/management/commands/create_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ def create_demo(stdout):
is_superuser=False,
is_active=True,
is_staff=False,
language=random.choice(settings.LANGUAGES)[0],
language=dev_user["language"]
or random.choice(settings.LANGUAGES)[0],
)
)

Expand Down
6 changes: 3 additions & 3 deletions src/backend/impress/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ class Base(Configuration):
# fallback/default languages throughout the app.
LANGUAGES = values.SingleNestedTupleValue(
(
("en-us", _("English")),
("fr-fr", _("French")),
("de-de", _("German")),
("en-us", "English"),
("fr-fr", "Français"),
("de-de", "Deutsch"),
)
)

Expand Down
4 changes: 2 additions & 2 deletions src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const config = {
MEDIA_BASE_URL: 'http://localhost:8083',
LANGUAGES: [
['en-us', 'English'],
['fr-fr', 'French'],
['de-de', 'German'],
['fr-fr', 'Français'],
['de-de', 'Deutsch'],
],
LANGUAGE_CODE: 'en-us',
SENTRY_DSN: null,
Expand Down
35 changes: 0 additions & 35 deletions src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,6 @@ test.beforeEach(async ({ page }) => {
});

test.describe('Doc Editor', () => {
test('it check translations of the slash menu when changing language', async ({
page,
browserName,
}) => {
await createDoc(page, 'doc-toolbar', browserName, 1);

const header = page.locator('header').first();
const editor = page.locator('.ProseMirror');
// Trigger slash menu to show english menu
await editor.click();
await editor.fill('/');
await expect(page.getByText('Headings', { exact: true })).toBeVisible();
await header.click();
await expect(page.getByText('Headings', { exact: true })).toBeHidden();

// Reset menu
await editor.click();
await editor.fill('');

// Change language to French
await header.click();
await header.getByRole('combobox').getByText('English').click();
await header.getByRole('option', { name: 'Français' }).click();
await expect(
header.getByRole('combobox').getByText('Français'),
).toBeVisible();

// Trigger slash menu to show french menu
await editor.click();
await editor.fill('/');
await expect(page.getByText('Titres', { exact: true })).toBeVisible();
await header.click();
await expect(page.getByText('Titres', { exact: true })).toBeHidden();
});

test('it checks default toolbar buttons are displayed', async ({
page,
browserName,
Expand Down
Loading
Loading