Skip to content

Commit 469901c

Browse files
committed
Merge branch 'develop'
2 parents 477da4d + a3a84df commit 469901c

44 files changed

Lines changed: 2147 additions & 1514 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ recursive-include recoco/apps/resources/static *.*
1111
recursive-include recoco/apps/crm/static *.*
1212
recursive-include recoco/apps/onboarding/static *.*
1313
recursive-include recoco/apps/survey/static *.*
14+
recursive-include recoco/apps/addressbook/static *.*
1415
recursive-include recoco/apps/resources *.md
1516
recursive-include recoco/sql/ *.sql
1617
recursive-include recoco/apps/ *.html

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ requires-python = ">=3.11"
1414
dependencies = [
1515
"beautifulsoup4==4.12.3",
1616
"celery==5.4.0",
17-
"django>=5.1.9,<5.2",
17+
"django>=5.1.10,<5.2",
1818
"django-activity-stream",
1919
"django-admin-csvexport==2.2",
2020
"django-allauth[openid,socialaccount]==0.63.6",

recoco/apps/addressbook/serializers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
SerializerMethodField,
1616
)
1717

18+
from recoco.apps.geomatics.serializers import DepartmentSerializer
1819
from recoco.rest_api.serializers import BaseSerializerMixin
1920

2021
from .models import Contact, Organization, OrganizationGroup
@@ -71,6 +72,8 @@ def get_departments(self, obj):
7172

7273

7374
class OrganizationDetailSerializer(OrganizationListSerializer):
75+
departments = DepartmentSerializer(read_only=True, many=True)
76+
7477
class Meta:
7578
model = Organization
7679
fields = [
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
.contact-list {
2+
.header {
3+
&__actions {
4+
display: flex;
5+
justify-content: space-between;
6+
gap: 10px;
7+
.actions {
8+
&__search-bar {
9+
position: relative;
10+
width: 75%;
11+
max-width: 852px;
12+
span {
13+
position: absolute;
14+
top: 0.5rem;
15+
left: 0.75rem;
16+
color: var(--light-text-mention-grey, #666);
17+
}
18+
input {
19+
padding-left: 2rem;
20+
}
21+
}
22+
&__add-contact {
23+
align-self: flex-end;
24+
}
25+
}
26+
}
27+
&__filter-letter {
28+
color: var(--light-background-action-high-blue-france, #000091);
29+
font-size: 14px;
30+
font-weight: 400;
31+
line-height: 36px;
32+
text-decoration-line: underline;
33+
text-decoration-style: solid;
34+
text-decoration-skip-ink: none;
35+
text-decoration-thickness: auto;
36+
text-underline-offset: auto;
37+
text-underline-position: from-font;
38+
&--active {
39+
color: var(--light-background-action-high-blue-france, #000091);
40+
font-weight: 700;
41+
font-size: 18px;
42+
text-decoration: none;
43+
}
44+
}
45+
}
46+
&__list {
47+
display: flex;
48+
gap: 2%;
49+
flex-wrap: wrap;
50+
align-content: start;
51+
flex-direction: row;
52+
justify-content: start;
53+
.organization-container {
54+
width: 100%;
55+
.organization {
56+
&__name {
57+
color: #161616;
58+
font-weight: 400;
59+
font-size: 18px;
60+
line-height: 36px;
61+
letter-spacing: 0%;
62+
margin-top: 10px;
63+
}
64+
&__departments {
65+
white-space: nowrap;
66+
overflow: hidden;
67+
text-overflow: ellipsis;
68+
&-item {
69+
margin-top: 10px;
70+
display: inline-block;
71+
margin-left: 0.625rem;
72+
padding: 2px 4px;
73+
align-items: center;
74+
border-radius: 2px;
75+
background: var(--light-background-contrast-info, #e8edff);
76+
color: var(--light-text-default-info, #0063cb);
77+
font-family: Marianne;
78+
font-size: 11px;
79+
font-style: normal;
80+
font-weight: 700;
81+
line-height: 1rem;
82+
}
83+
}
84+
}
85+
}
86+
&-item {
87+
width: 32.6%;
88+
}
89+
.contact-card {
90+
position: relative;
91+
border: 1px solid var(--light-border-default-grey, #e5e5e5);
92+
&__updated-at {
93+
bottom: 0;
94+
}
95+
.button__actions {
96+
display: none; //TODO: Uncomment this line
97+
.modal__confirm-delete {
98+
--ul-type: none;
99+
--ul-start: 0;
100+
z-index: 2;
101+
position: absolute;
102+
background: #fff;
103+
border: 1px solid var(--light-border-default-grey, #e5e5e5);
104+
border-radius: 4px;
105+
box-shadow: 4px 8px 8px -16px rgba(0, 0, 0, 0.2);
106+
right: 0;
107+
top: 25px;
108+
width: 450px;
109+
.modal__header {
110+
padding: 0.625rem;
111+
color: #3a3a3a;
112+
font-size: 12px;
113+
font-weight: 700;
114+
line-height: 1.125rem;
115+
display: flex;
116+
justify-content: space-between;
117+
}
118+
.modal__body {
119+
padding: 0.625rem;
120+
.modal__text {
121+
color: var(--light-border-plain-error, #ce0500);
122+
font-size: 13px;
123+
font-weight: 700;
124+
line-height: 150%;
125+
}
126+
.contact__resume {
127+
border: 1px solid var(--light-border-plain-error, #ce0500);
128+
padding: 10px;
129+
display: flex;
130+
flex-direction: column;
131+
div {
132+
line-height: 150%; /* 16.5px */
133+
}
134+
}
135+
}
136+
137+
.modal__footer {
138+
padding: 0.625rem;
139+
width: 100%;
140+
&-cancel,
141+
&-confirm {
142+
border-radius: 1px;
143+
font-size: 12px;
144+
font-weight: 700;
145+
line-height: 1.125rem;
146+
display: flex;
147+
padding: 6px 8px;
148+
justify-content: center;
149+
align-items: flex-start;
150+
gap: 10px;
151+
flex: 1 0 0;
152+
}
153+
&-cancel {
154+
background: var(--light-background-alt-grey, #f6f6f6);
155+
color: var(--light-text-default-grey, #3a3a3a);
156+
}
157+
&-confirm {
158+
color: #fff;
159+
background: var(--light-background-flat-error, #ce0500);
160+
}
161+
}
162+
}
163+
}
164+
&:hover.actions,
165+
&.actions.active {
166+
border: 1px solid var(--light-border-action-high-blue-france, #000091);
167+
.button__actions {
168+
position: absolute;
169+
top: 1rem;
170+
right: 1rem;
171+
display: grid;
172+
gap: 4px;
173+
&-edit {
174+
height: 16px;
175+
border-radius: 1px;
176+
color: #000000;
177+
background: var(--light-background-alt-grey, #f6f6f6);
178+
}
179+
&-delete {
180+
height: 16px;
181+
border-radius: 1px;
182+
color: var(--background-flat-error);
183+
background: var(--light-background-contrast-error, #ffe9e9);
184+
}
185+
}
186+
}
187+
}
188+
}
189+
&__list-container {
190+
display: flex;
191+
gap: 1rem 1%;
192+
flex-wrap: wrap;
193+
}
194+
}
195+
.no-contacts {
196+
border-radius: 4px;
197+
background: #f6f6f6;
198+
padding: 40px;
199+
align-items: center;
200+
align-self: stretch;
201+
width: 100%;
202+
}
203+
204+
.specific-banner {
205+
border-radius: 1px;
206+
background: var(--light-background-alt-grey, #f6f6f6);
207+
text-align: center;
208+
}
209+
210+
.text-style {
211+
color: var(--light-text-mention-grey, #666);
212+
text-align: right;
213+
font-family: Marianne;
214+
font-size: 11px;
215+
font-style: normal;
216+
font-weight: 700;
217+
line-height: 150%;
218+
}
219+
220+
.correct-p {
221+
margin-bottom: 0px;
222+
}

recoco/apps/addressbook/static/addressbook/widgets/card_contact_horizontal.scss renamed to recoco/apps/addressbook/static/addressbook/css/widgets/card_contact_horizontal.scss

File renamed without changes.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
{% extends "base.html" %}
2+
{% load guardian_tags %}
3+
{% load static %}
4+
{% load sass_tags %}
5+
{% load django_vite %}
6+
{% block title %}
7+
Carnet de contacts {{ block.super }}
8+
{% endblock title %}
9+
{% block og_title %}
10+
Carnet de contacts {{ block.super }}
11+
{% endblock og_title %}
12+
{% block css %}
13+
<link href="{% sass_src 'addressbook/css/contact_list.scss' %}"
14+
rel="stylesheet"
15+
type="text/css">
16+
{% endblock css %}
17+
{% block js %}
18+
{% vite_asset 'js/components/ContactBook.js' %}
19+
{% vite_asset 'js/apps/featureAddContact.js' %}
20+
{% endblock js %}
21+
{% block content %}
22+
<div x-data="ContactBook"
23+
@modal-response="closeCreateContactModal($event)"
24+
class="contact-list col-12 fr-px-15w fr-mx-auto">
25+
<!-- Breadcrumb -->
26+
<nav role="navigation" class="fr-breadcrumb" aria-label="vous êtes ici :">
27+
<button class="fr-breadcrumb__button"
28+
aria-expanded="false"
29+
aria-controls="breadcrumb-1">Voir le fil d’Ariane</button>
30+
<div class="fr-collapse" id="breadcrumb-1">
31+
<ol class="fr-breadcrumb__list">
32+
<li>
33+
<a class="fr-breadcrumb__link" href="{% url 'home' %}">Accueil</a>
34+
</li>
35+
<li>
36+
<a class="fr-breadcrumb__link" aria-current="page">Contacts</a>
37+
</li>
38+
</ol>
39+
</div>
40+
</nav>
41+
<div class="header">
42+
<h2>Contact</h2>
43+
<div class="header__actions">
44+
<div class="actions__search-bar">
45+
<span class="fr-icon-search-line fr-icon--sm" aria-hidden="true"></span>
46+
<input class="fr-input w-100"
47+
type="search"
48+
@input.debounce.500ms="searchContacts($event.target.value)"
49+
x-model="searchParams.search"
50+
placeholder="Rechercher">
51+
</div>
52+
<button class="actions__add-contact no-wrap fr-btn fr-btn--icon-left fr-icon-add-line"
53+
@click="openModalCreateContact()">Créer un contact</button>
54+
</div>
55+
<div class="header__filter d-flex">
56+
<template x-for="letter of letters">
57+
<button class="header__filter-letter"
58+
:class="letter === searchParams.letter ? 'header__filter-letter--active': ''"
59+
x-text="letter"
60+
@click="loadOrganizationStartingWith(letter)"></button>
61+
</template>
62+
<template x-if="searchParams.letter">
63+
<div>
64+
<span>-</span>
65+
<button class="header__filter-letter" @click="resetLetterFilter">supprimer le filtre</button>
66+
</div>
67+
</template>
68+
</div>
69+
</div>
70+
<div class="contact-list__list">
71+
<template x-for="organization in contactListGroupByOrganization" :key="index">
72+
<div class="organization-container">
73+
<div class="d-flex align-items-center">
74+
<h3 class="organization__name fr-mb-0 no-wrap" x-text="organization.name"></h3>
75+
<template x-if="organization.departments?.length > 0">
76+
<div class="organization__departments"
77+
:title="organization.departments.map(department => `${department.name} (${department.code})`).join(', ')">
78+
<template x-for="department in organization.departments">
79+
<template x-if="department.name">
80+
<span class="organization__departments-item"
81+
x-text="`${department.name} (${department.code})`"></span>
82+
</template>
83+
</template>
84+
</div>
85+
</template>
86+
</div>
87+
<div class="contact-list__list-container">
88+
<template x-for="contact in organization.contacts" :key="contact.id">
89+
<div x-data="{contact:contact, isOpenDeleteContact: false}"
90+
class="contact-list__list-item">
91+
{% include "tools/contacts/contact_card.html" with button_actions=True %}
92+
</div>
93+
</template>
94+
</div>
95+
</div>
96+
</template>
97+
<template x-if="contactListGroupByOrganization.length === 0">
98+
<div class="no-contacts fr-m-2w">
99+
<p class="no-contacts__text">Aucun contact trouvé.</p>
100+
<br>
101+
<p class="no-contacts__text fr-m-0">Vous pouvez créer un contact en cliquant sur le bouton ci-dessus.</p>
102+
</div>
103+
</template>
104+
</div>
105+
{% include "tools/contacts/create_contact_modal.html" with isItReturningData=False %}
106+
</div>
107+
{% endblock content %}

recoco/apps/addressbook/tests/test_views_contact.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
import pytest
22
from django.contrib.sites.shortcuts import get_current_site
33
from django.urls import reverse
4+
from guardian.shortcuts import assign_perm
45
from model_bakery.recipe import Recipe
56
from pytest_django.asserts import assertContains, assertRedirects
67

78
from recoco.utils import login
89

910
from ..models import Contact, Organization
1011

12+
13+
# Contact list
14+
@pytest.mark.django_db
15+
def test_contact_list_not_available_for_non_staff(client):
16+
url = reverse("addressbook-contact-list")
17+
with login(client):
18+
response = client.get(url)
19+
assert response.status_code == 403
20+
21+
22+
@pytest.mark.django_db
23+
def test_contact_list_available(client, current_site):
24+
url = reverse("addressbook-contact-list")
25+
with login(client) as user:
26+
assign_perm("site.use_addressbook", user, current_site)
27+
response = client.get(url)
28+
29+
assert response.status_code == 200
30+
31+
1132
# Creation
1233

1334

0 commit comments

Comments
 (0)