Django 2.0, Python 3.6, Django Rest Framework 3.8
I'm still pretty new to Django Rest Framework, and I'm trying to wrap my head around the logic for using functions in a viewset (and if this is even the correct place to include a function).
Basically, I would like to send out an email when a user posts something to the api in this specific viewset. I tried using the send_mail
function, but have been unsuccessful. I have the following class based view:
class SendInviteView(viewsets.ModelViewSet):
queryset = models.Message.objects.all()
serializer_class = serializers.MessageSerializer
@action(methods=['post'], detail=True)
def send_the_mail(self, request):
send_mail(
'Invitation',
'Try our app!',
'[email protected]',
['[email protected]'],
fail_silently=False,
)
[The Model and Serializer are pretty basic, and I don't think will be required for the context of this problem, basically just an EmailField(). I eventually plan to use the input of that email field to replace [email protected]
, but for now I just want to understand how to add functionality to viewsets]
This results in an error when running python manage.py check
I have my email client set up through sendgrid and am able to successfully send emails to users who ask to have their passwords reset through rest-auth
, but I don't understand how sending an email works outside of that context.
Any help is greatly appreciated.
After the discussion, I would came up with the following.
from django.conf import settings
from django.core.mail import send_mail
from django.db import models
from rest_framework import serializers, viewsets, routers, mixins
from rest_framework.response import Response
class Message(models.Model):
sender = models.ForeignKey(settings.AUTH_USER_MODEL)
recipient = models.EmailField()
class MessageSerializer(serializers.ModelSerializer):
message = serializers.CharField(write_only=True)
class Meta:
model = Message
fields = ['recipient', 'message']
def create(self, validated_data):
message = validated_data.pop('message')
message_obj = super().create(validated_data)
send_mail(
'Invitation',
message,
'[email protected]',
[message_obj.recipient]
)
return message_obj
class SendInviteView(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = MessageSerializer
def perform_create(self, serializer):
serializer.save(sender=self.request.user)
router = routers.DefaultRouter()
router.register('send_invite', SendInviteView, base_name='send_invite')
urlpatterns = router.urls
Let's break things up.
If you want to store sender, you need ForeignKey
to User
in your model.
For serializer you need to add message
field manually because it doesn't exists in your model, but users should submit it. We set it to write-only, because this serializer will be also used to serialize created Message
back to user for response, and Message
don't have message
field. This serializer will also generate field for recipient
automatically from Message
model.
Then we override create
in this serializer, so whenever new Message
will be created using it, it will send an email. It calls super().create(..)
to actually save Message
to database and then sends an email. We use .pop()
to remove message
from validated_data
, because Message
doesn't contain such field.
For the view we don't need the whole stuff the ModelViewSet
provides. It adds ability to Create, Read, Update and Delete (CRUD
) your Message
, which is not actually needed. All you need is simple Create which translates to POST
in term of HTTP request. GenericViewSet
with CreateModelMixin
is exactly the thing we want (And actually ModelViewSet
just have MORE mixins). The data from user will be validated by serializer and than perform_create
method will be invoked. We are passing sender=self.request.user
to serializer.save()
because we need to save sender
into Message
, and sender
field is not actually in the data, it is the user currently logged-in.
serializer.save()
will run our MessageSerializer.create()
so we are happy.
Note that this stuff will work only for logged-in users, because we somehow need to populate sender
field in database, so it will be correct to add
class SendInviteView(mixins.CreateModelMixin, viewsets.GenericViewSet):
permission_classes = [IsAuthenticated]
....
So only authenticated users can make request. Hopefully this will clarify things for you. Best regards)
If I'm understood correctly, you could mention the function/method in urls
as below,
url(r'some/end/point/', views.SendInviteView.as_view({"post": "send_the_mail"})
Hence, your view be like,
class SendInviteView(viewsets.ModelViewSet):
queryset = models.Message.objects.all()
serializer_class = serializers.MessageSerializer
def send_the_mail(self, request):
recipient = request.data['recipient'] # json array
send_mail(
'Invitation',
'Try our app!',
'[email protected]',
recipient,
fail_silently=False,
)
return Response("mail sent successfully")
Since recipient
expects an array, so the POST payload
will be like,
{
"recipient": ["[email protected]", "[email protected]", "[email protected]"]
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With