From 625f122ad594b0586763faf4f20fd2a3d9745519 Mon Sep 17 00:00:00 2001 From: Quentin BEY Date: Thu, 28 Nov 2024 10:55:44 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(contacts)=20add=20`notes`=20&=20force?= =?UTF-8?q?=20`full=5Fname`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We make the full name mandatory and add a field to allow user to store personnal notes on the contact. This also make the "base" contact not mandatory because user may want to create new contacts out of the blue. --- CHANGELOG.md | 4 ++ src/backend/core/api/client/serializers.py | 4 ++ ...6_contact_notes_alter_contact_full_name.py | 24 +++++++ src/backend/core/models.py | 6 +- src/backend/core/tests/test_api_contacts.py | 62 +++++++++++++++++-- .../core/tests/test_models_contacts.py | 6 -- 6 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 src/backend/core/migrations/0006_contact_notes_alter_contact_full_name.py diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa49be..d4c2bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to ## [Unreleased] +### Added + +- ✨(contacts) add notes & force full_name #565 + ## [1.7.1] - 2024-11-28 ## [1.7.0] - 2024-11-28 diff --git a/src/backend/core/api/client/serializers.py b/src/backend/core/api/client/serializers.py index ff46554..d0b707d 100644 --- a/src/backend/core/api/client/serializers.py +++ b/src/backend/core/api/client/serializers.py @@ -17,10 +17,14 @@ class ContactSerializer(serializers.ModelSerializer): "base", "data", "full_name", + "notes", "owner", "short_name", ] read_only_fields = ["id", "owner"] + extra_kwargs = { + "base": {"required": False}, + } def update(self, instance, validated_data): """Make "base" field readonly but only for update/patch.""" diff --git a/src/backend/core/migrations/0006_contact_notes_alter_contact_full_name.py b/src/backend/core/migrations/0006_contact_notes_alter_contact_full_name.py new file mode 100644 index 0000000..0956836 --- /dev/null +++ b/src/backend/core/migrations/0006_contact_notes_alter_contact_full_name.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.3 on 2024-11-28 09:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_add_serviceprovider'), + ] + + operations = [ + migrations.AddField( + model_name='contact', + name='notes', + field=models.TextField(blank=True, default='', verbose_name='notes'), + ), + migrations.AlterField( + model_name='contact', + name='full_name', + field=models.CharField(default='-', max_length=150, verbose_name='full name'), + preserve_default=False, + ), + ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index cc99fbe..585ac52 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -113,11 +113,11 @@ class Contact(BaseModel): null=True, blank=True, ) - full_name = models.CharField(_("full name"), max_length=150, null=True, blank=True) + full_name = models.CharField(_("full name"), max_length=150) short_name = models.CharField(_("short name"), max_length=30, null=True, blank=True) # avatar = - # notes = + notes = models.TextField(_("notes"), blank=True, default="") data = models.JSONField( _("contact information"), help_text=_("A JSON object containing the contact information"), @@ -151,7 +151,7 @@ class Contact(BaseModel): ] def __str__(self): - return self.full_name or self.short_name + return self.full_name def clean(self): """Validate fields.""" diff --git a/src/backend/core/tests/test_api_contacts.py b/src/backend/core/tests/test_api_contacts.py index 12c4747..2672054 100644 --- a/src/backend/core/tests/test_api_contacts.py +++ b/src/backend/core/tests/test_api_contacts.py @@ -97,6 +97,7 @@ def test_api_contacts_list_authenticated_no_query(): "owner": str(contact2.owner.id), "data": contact2.data, "full_name": contact2.full_name, + "notes": "", "short_name": contact2.short_name, }, ] @@ -271,6 +272,7 @@ def test_api_contacts_retrieve_authenticated_owned(): "owner": str(contact.owner.id), "data": contact.data, "full_name": contact.full_name, + "notes": "", "short_name": contact.short_name, } @@ -293,6 +295,7 @@ def test_api_contacts_retrieve_authenticated_public(): "owner": None, "data": contact.data, "full_name": contact.full_name, + "notes": "", "short_name": contact.short_name, } @@ -328,7 +331,7 @@ def test_api_contacts_create_anonymous_forbidden(): def test_api_contacts_create_authenticated_missing_base(): - """Anonymous users should be able to create users.""" + """Authenticated user should be able to create contact without override.""" user = factories.UserFactory(profile_contact=None) client = APIClient() @@ -339,13 +342,23 @@ def test_api_contacts_create_authenticated_missing_base(): { "full_name": "David Bowman", "short_name": "Dave", + "data": {}, }, format="json", ) - assert response.status_code == 400 - assert models.Contact.objects.exists() is False + assert response.status_code == 201 - assert response.json() == {"base": ["This field is required."]} + new_contact = models.Contact.objects.get(owner=user) + + assert response.json() == { + "base": None, + "data": {}, + "full_name": "David Bowman", + "id": str(new_contact.pk), + "notes": "", + "owner": str(user.pk), + "short_name": "Dave", + } def test_api_contacts_create_authenticated_successful(): @@ -379,6 +392,7 @@ def test_api_contacts_create_authenticated_successful(): "base": str(base_contact.id), "data": CONTACT_DATA, "full_name": "David Bowman", + "notes": "", "owner": str(user.id), "short_name": "Dave", } @@ -408,6 +422,7 @@ def test_api_contacts_create_authenticated_existing_override(): { "base": str(base_contact.id), "full_name": "David Bowman", + "notes": "", "short_name": "Dave", "data": CONTACT_DATA, }, @@ -422,6 +437,45 @@ def test_api_contacts_create_authenticated_existing_override(): } +def test_api_contacts_create_authenticated_successful_with_notes(): + """Authenticated users should be able to create contacts with notes.""" + user = factories.UserFactory(profile_contact=None) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/contacts/", + { + "full_name": "David Bowman", + "short_name": "Dave", + "data": CONTACT_DATA, + "notes": "This is a note", + }, + format="json", + ) + + assert response.status_code == 201 + assert models.Contact.objects.count() == 1 + + contact = models.Contact.objects.get(owner=user) + assert response.json() == { + "id": str(contact.id), + "base": None, + "data": CONTACT_DATA, + "full_name": "David Bowman", + "notes": "This is a note", + "owner": str(user.id), + "short_name": "Dave", + } + + assert contact.full_name == "David Bowman" + assert contact.short_name == "Dave" + assert contact.data == CONTACT_DATA + assert contact.owner == user + assert contact.notes == "This is a note" + + def test_api_contacts_update_anonymous(): """Anonymous users should not be allowed to update a contact.""" contact = factories.ContactFactory() diff --git a/src/backend/core/tests/test_models_contacts.py b/src/backend/core/tests/test_models_contacts.py index 34c9b58..7434737 100644 --- a/src/backend/core/tests/test_models_contacts.py +++ b/src/backend/core/tests/test_models_contacts.py @@ -17,12 +17,6 @@ def test_models_contacts_str_full_name(): assert str(contact) == "David Bowman" -def test_models_contacts_str_short_name(): - """The str representation should be the contact's short name if full name is not set.""" - contact = factories.ContactFactory(full_name=None, short_name="Dave") - assert str(contact) == "Dave" - - def test_models_contacts_base_self(): """A contact should not point to itself as a base contact.""" contact = factories.ContactFactory()