Add API endpoint to create message
This commit is contained in:
parent
12f1f6849a
commit
c7fe28e9dc
10 changed files with 167 additions and 15 deletions
27
poetry.lock
generated
27
poetry.lock
generated
|
@ -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"},
|
||||
|
|
|
@ -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"
|
||||
|
|
26
src/pictures/migrations/0006_auto_20210213_1645.py
Normal file
26
src/pictures/migrations/0006_auto_20210213_1645.py
Normal 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
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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):
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
0
src/pictures/tests/__init__.py
Normal file
0
src/pictures/tests/__init__.py
Normal file
12
src/pictures/tests/conftest.py
Normal file
12
src/pictures/tests/conftest.py
Normal 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",
|
||||
)
|
52
src/pictures/tests/test_api.py
Normal file
52
src/pictures/tests/test_api.py
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue