Add API endpoint to create message

This commit is contained in:
Gabriel Augendre 2021-02-13 16:47:34 +01:00
parent 12f1f6849a
commit c7fe28e9dc
10 changed files with 167 additions and 15 deletions

27
poetry.lock generated
View file

@ -98,7 +98,7 @@ bcrypt = ["bcrypt"]
[[package]]
name = "django-anymail"
version = "8.1"
version = "8.2"
description = "Django email integration for Amazon SES, Mailgun, Mailjet, Postmark, SendGrid, SendinBlue, SparkPost and other transactional ESPs"
category = "main"
optional = false
@ -276,6 +276,21 @@ toml = "*"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
name = "pytest-django"
version = "4.1.0"
description = "A Django plugin for pytest."
category = "dev"
optional = false
python-versions = ">=3.5"
[package.dependencies]
pytest = ">=5.4.0"
[package.extras]
docs = ["sphinx", "sphinx-rtd-theme"]
testing = ["django", "django-configurations (>=2.0)"]
[[package]]
name = "pytz"
version = "2020.5"
@ -388,7 +403,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "f4481753176cb89044373243f6f4432b96b2c850482e4a5b5d6d1f9c27df24ad"
content-hash = "34b835304e7e7c52c384ada11f110aec4bf776f4a568f7f7fc0361a83989a614"
[metadata.files]
appdirs = [
@ -432,8 +447,8 @@ django = [
{file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"},
]
django-anymail = [
{file = "django-anymail-8.1.tar.gz", hash = "sha256:0c3e56a339a37e654b7511572564fe0949f4fbb12c072761c9e35cfc49cb4dc1"},
{file = "django_anymail-8.1-py3-none-any.whl", hash = "sha256:0301f2ea1dde7840e5276a5e2d1ca2a56fd558e2b71800e89ca895c18aa3c615"},
{file = "django-anymail-8.2.tar.gz", hash = "sha256:6381e04c41b2644e2d3ba2f95ee61ee3ee40cb6184506c52a363b9ddef0b098e"},
{file = "django_anymail-8.2-py3-none-any.whl", hash = "sha256:e011c582e771ce3970480c10d1e129ac036ba773e37ec56780a79776534b2ba6"},
]
django-cleanup = [
{file = "django-cleanup-5.1.0.tar.gz", hash = "sha256:8976aec12a22913afb3d1fcb541b1aedde2f5ec243e4260c5ff78bb6aa75a089"},
@ -495,6 +510,10 @@ pytest = [
{file = "pytest-6.2.1-py3-none-any.whl", hash = "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8"},
{file = "pytest-6.2.1.tar.gz", hash = "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"},
]
pytest-django = [
{file = "pytest-django-4.1.0.tar.gz", hash = "sha256:26f02c16d36fd4c8672390deebe3413678d89f30720c16efb8b2a6bf63b9041f"},
{file = "pytest_django-4.1.0-py3-none-any.whl", hash = "sha256:10e384e6b8912ded92db64c58be8139d9ae23fb8361e5fc139d8e4f8fc601bc2"},
]
pytz = [
{file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"},
{file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"},

View file

@ -16,7 +16,19 @@ pytest = "^6.2"
pre-commit = "^2.9.3"
importlib-metadata = "^3.4.0"
typing-extensions = "^3.7.4"
pytest-django = "^4.1.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.black]
target-version = ['py38']
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
addopts = "--color=yes"
minversion = "6.0"
DJANGO_SETTINGS_MODULE = "picture_display.settings"

View file

@ -0,0 +1,26 @@
# Generated by Django 3.1.5 on 2021-02-13 16:45
import phonenumber_field.modelfields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pictures", "0005_auto_20210124_1644"),
]
operations = [
migrations.AlterField(
model_name="contact",
name="display_name",
field=models.CharField(blank=True, max_length=250),
),
migrations.AlterField(
model_name="contact",
name="phone_number",
field=phonenumber_field.modelfields.PhoneNumberField(
max_length=128, region=None, unique=True
),
),
]

View file

@ -12,11 +12,11 @@ class User(AbstractUser):
class Contact(models.Model):
phone_number = PhoneNumberField()
display_name = models.CharField(max_length=250)
phone_number = PhoneNumberField(unique=True)
display_name = models.CharField(max_length=250, blank=True)
def __str__(self):
return self.display_name
return self.display_name or self.phone_number
class Message(models.Model):

View file

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View file

View file

@ -0,0 +1,12 @@
import pytest
from pictures.models import Contact
@pytest.fixture
@pytest.mark.django_db
def contact():
return Contact.objects.create(
phone_number="+33611111111",
display_name="Test contact",
)

View file

@ -0,0 +1,52 @@
import json
import pytest
from django.test import Client
from django.urls import reverse
from pictures.models import Contact, Message
from pictures.views import STATUS_OK
@pytest.mark.django_db
@pytest.mark.parametrize("content", ["This is a test message", ""])
def test_post_message_existing_contact(client: Client, contact: Contact, content: str):
assert Message.objects.count() == 0
res = _post_message(client, str(contact.phone_number), content)
assert res.status_code == 201
data = res.json()
message = Message.objects.first()
assert data["status"] == STATUS_OK
assert data["data"]["id"] == message.pk
assert message.sender == contact
assert message.content == content
def _post_message(client: Client, phone_number: str, content: str):
res = client.post(
reverse("api-create-message"),
data=json.dumps(
{
"phone_number": phone_number,
"content": content,
}
),
content_type="application/json",
)
return res
@pytest.mark.django_db
@pytest.mark.parametrize("content", ["This is a test message", ""])
def test_post_message_new_contact(client: Client, content: str):
assert Message.objects.count() == 0
phone_number = "+33622222222"
res = _post_message(client, phone_number, content)
data = res.json()
assert res.status_code == 201
message = Message.objects.first()
assert data["status"] == STATUS_OK
assert data["data"]["id"] == message.pk
assert message.sender.phone_number == phone_number
assert message.sender.display_name == ""
assert message.content == content

View file

@ -2,10 +2,16 @@ from django.conf import settings
from django.conf.urls.static import static
from django.urls import path
from pictures.views import MessageDetailView, MessageListView, send_email
from pictures.views import (
MessageDetailView,
MessageListView,
create_message,
send_email,
)
urlpatterns = [
path("", MessageListView.as_view(), name="messages-list"),
path("<int:pk>/", MessageDetailView.as_view(), name="messages-detail"),
path("<int:pk>/email/", send_email, name="messages-to-email"),
path("api/message/", create_message, name="api-create-message"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -1,3 +1,4 @@
import json
from smtplib import SMTPException
from anymail.exceptions import AnymailRequestsAPIError
@ -7,9 +8,13 @@ from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views import generic
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from pictures.models import Message
from pictures.models import Contact, Message
STATUS_OK = "ok"
STATUS_ERROR = "error"
class MessageListView(generic.ListView):
@ -49,5 +54,28 @@ def send_email(request, pk):
email.attach_file(media.file.path)
email.send(fail_silently=False)
except (SMTPException, AnymailRequestsAPIError) as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
return JsonResponse({"status": "ok"}, status=200)
return _error_response(str(e))
return JsonResponse({"status": STATUS_OK}, status=200)
def _error_response(message: str, status_code: int = 500):
return JsonResponse(
{"status": STATUS_ERROR, "message": message}, status=status_code
)
@csrf_exempt
@require_http_methods(["POST"])
def create_message(request):
try:
body = request.body.decode("utf-8")
data = json.loads(body)
phone_number = data.get("phone_number")
if phone_number is None:
return _error_response("phone_number is required", 400)
contact, _ = Contact.objects.get_or_create(phone_number=phone_number)
content = data.get("content", "")
message = Message.objects.create(sender=contact, content=content)
except Exception as e:
return _error_response(str(e))
return JsonResponse({"status": STATUS_OK, "data": {"id": message.pk}}, status=201)