Not able to send email overriding create method django rest framework - django

I got to know that we can override create method in order to send email but it's not working with my code.
views.py:
class FileUploadView(APIView):
parser_class = (MultiPartParser,)
def post(self, request, *args, **kwargs):
file_serializer = FileSerializer(data=request.data)
if file_serializer.is_valid():
file_serializer.save()
return Response(file_serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def create(self, request, *args, **kwargs):
response = super(FileUploadView, self).create(request, *args, **kwargs)
send_email() # sending mail
return response
def send_email(request):
email = EmailMessage(
'Title',
(FileSerializer.Fullname, FileSerializer.Email, FileSerializer.Contact),
'mymail#gmail.com',
['anothermail#gmail.com']
)
email.attach_file(FileSerializer.Upload)
email.send()
Help me in figuring out what's the problem here
Edit: APIView doesn't support create method, above code doesn't work
. I want to send the contents received from post method from rest API through a mail. Suggest me a proper method in order to do it with respect to above code.

APIView does not support create
You have got this part right. You want to send an email after the model object has been saved to database. Actually there are several ways to do that. You can do it from view, you can do it from your serializer, you can do it from your model, you can do it from a post_save signal hooked to your model. I am going to show you what was wrong with your code and then some of the other ways -
The corrections to be made in your code that, you could call the send_email function just after file_serializer.save() in your FileUploadView.post
class FileUploadView(APIView):
parser_class = (MultiPartParser,)
def post(self, request, *args, **kwargs):
file_serializer = FileSerializer(data=request.data)
if file_serializer.is_valid():
file_serializer.save()
send_email() #sending Email
return Response(file_serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def send_email(request):
email = EmailMessage(
'Title',
(FileSerializer.Fullname, FileSerializer.Email, FileSerializer.Contact),
'mymail#gmail.com',
['anothermail#gmail.com']
)
email.attach_file(FileSerializer.Upload)
email.send()
This process also calls the send_email function from view, but this view is a bit different. This view inherits from generics.CreateAPIView
from rest_framework import generics
class FileUploadView(generics.CreateAPIView):
serializer_class = FileUploadSerializer
parser_class = [MultiParser, ]
queryset = FileUploadModel.objects.all()
def perform_create(self, serializer):
serializer.save()
send_email()
You can call the send_email function from the create method of your seiralizer. You can override serializer.create method
class FileUploadSerializer(serializers.ModelSerializer):
class Meta:
model = FileUploadModel
fields = ['your', 'model', 'fields']
def create(self, validated_data):
instance = super(FileUploadSerializer, self).create(validated_data)
send_email()
return instance
Sending Email from your FileUploadModel model class. Here you override the save method of the model
class FileUploadModel(models.Model):
...your model fields definition...
def save(self, *args, **kwargs):
if not self.pk: #assuming we want to send email only when object is created first time in database
send_email()
super(FileUploadModel, self).save(*args, **kwargs)
On catch about overriding model.save method is that, it will not be called when you're performing bulk operations. Also it will be called every time you save the model i.e. both create and update that's why we added the pk check. This logic will send email before the object is inserted in your database if you want to send email only when the object is completely saved in database then do the following way with signals
You can hook a post_save signal to your model. In this way you send an email only after the object is saved in database
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=FileUploadModel)
def file_upload_post_save(sender, instance, **kwargs):
send_email()
Catch with using signals is that it will not be called when you do bulk operation.
I am assuming you're fairly new to the django enviornment. You should also have a look on celery to perform long running background tasks such as - sending emails.

Related

Override a specific argument in all django model queries?

I have a custom django user model that does not contain a username field, it uses email instead. I am attempting to implement a 3rd party package that makes queries to the user model based on username. Is there a way to override all queries (get, filter, etc.) to this model through a custom manager or otherwise so that username is simply converted to email?
It would turn this:
User.objects.filter(username="grrrrrr#grrrrrr.com")
into
User.objects.filter(email="grrrrrr#grrrrrr.com")
You can create a QuerySet subclass where you "clean" the incoming kwargs for the get and filter methods and change username to email
class UserQuerySet(models.QuerySet):
def _clean_kwargs(self, kwargs):
if 'username' in kwargs:
kwargs['email'] = kwargs['username']
del kwargs['username']
return kwargs
def filter(self, *args, **kwargs):
kwargs = self._clean_kwargs(kwargs)
return super().filter(*args, **kwargs)
def get(self, *args, **kwargs):
kwargs = self._clean_kwargs(kwargs)
return super().get(*args, **kwargs)
class UserManager(BaseUserManager):
def get_queryset(self):
return UserQuerySet(self.model, using=self._db)
class User(AbstractBaseUser):
objects = UserManager()
The custom manager will need to implement create_user and create_superuser as detailed here in the docs

Triggering a django rest framework update() function using HTTP Requests

I just started to learn django last week so please excuse my ignorance if I'm completely approaching this problem the wrong way.
So I've been following a thinkster tutorial on setting up a User model that allows the change of a password in the model. So far I have a url (/api/user) that leads to this view:
class UserRetrieveUpdateAPIView(RetrieveUpdateAPIView):
permission_classes = (IsAuthenticated,)
renderer_classes = (UserJSONRenderer,)
serializer_class = UserSerializer
def retrieve(self, request, *args, **kwargs):
#turns the object recieved into a JSON object
serializer = self.serializer_class(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
def update(self, request, *args, **kwargs):
serializer_data = request.data
serializer = self.serializer_class(
request.user, data=serializer_data, partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
I understand that this section :
serializer = self.serializer_class(
request.user, data=serializer_data, partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
will call upon a serializer class along the lines of:
class UserSerializer(serializers.ModelSerializer):
#This class handles serialization and deserialization of User objects
password = serializers.CharField(
max_length=128,
min_length=8,
write_only=True
)
class Meta:
model = User
fields = ('email', 'username', 'password', 'token',)
read_only_fields=('token',)
def update(self, instance, validated_data):
#performs an update on the user
password = validated_data.pop('password', None)
#have to take out password because setattr does not handle hashing etc
for (key, value) in validated_data.items():
#for the keys after taking out password set them to the updating User instance
setattr(instance, key, value)
if password is not None:
instance.set_password(password) #set_password is handled bydjango
instance.save() #set_password does not save instance
return instance
again I understand this section will essentially take request.data and "update" the model. However I'm stuck on how to test this feature using Postman.
Currently when I send a GET request to the URL using Postman I get this response:
GET Request result
The response is based off of my authenticate class that uses JWT authentication.
My question is, how do I trigger that update function using a Postman HTTP Request.
PATCH(partial_update) or PUT(update) http://127.0.0.1:8000/api/user/user_id/
you can see router table here

When and how to validate data with Django REST Framework

I have a model which is exposed as a resource with Django REST Framework.
I need to manually create the objects when a POST requests is performed on the related endpoints, that why I use a generics.ListCreateAPIView and override the create() method.
However I need to check that the parameters given in the payload of the POST request are well-formed/existing/etc...
Where shall I perform this validation, and how is it related with the Serializer?
I tried to write a validate() method in the related Serializer, but it is never called on POST requests.
class ProductOrderList(generics.ListCreateAPIView):
model = ProductOrder
serializer_class = ProductOrderSerializer
queryset = ProductOrder.objects.all()
def create(self, request, *args, **kwargs):
data = request.data
# Some code here to prepare the manual creation of a 'ProductOrder' from the data
# I would like the validation happens here (or even before)
po = ProductOrder.objects.create(...)
class ProductOrderSerializer(serializers.ModelSerializer):
class Meta:
model = ProductOrder
def validate(self, data): # Never called
# Is it the good place to write the validator ??
Here's the implementation of the create method that you overrided, taken from the mixins.CreateModelMixin class:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
As you can see, it gets the serializer, validates the data and performs the creation of the object from the serializer validated data.
If you need to manually control the creation of the object, perform_create is the hook that you need to override, not create.
def perform_create(self, serializer):
# At this, the data is validated, you can do what you want
# by accessing serializer.validated_data

How to make a PATCH request using DJANGO REST framework

I am not very experience with Django REST framework and have been trying out many things but can not make my PATCH request work.
I have a Model serializer. This is the same one I use to add a new entry and ideally I Would want to re-use when I update an entry.
class TimeSerializer(serializers.ModelSerializer):
class Meta:
model = TimeEntry
fields = ('id', 'project', 'amount', 'description', 'date')
def __init__(self, user, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
super(TimeSerializer, self).__init__(*args, **kwargs)
self.user = user
def validate_project(self, attrs, source):
"""
Check that the project is correct
"""
.....
def validate_amount(self, attrs, source):
"""
Check the amount in valid
"""
.....
I tried to use a class based view :
class UserViewSet(generics.UpdateAPIView):
"""
API endpoint that allows timeentries to be edited.
"""
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
My urls are:
url(r'^api/edit/(?P<pk>\d+)/$', UserViewSet.as_view(), name='timeentry_api_edit'),
My JS call is:
var putData = { 'id': '51', 'description': "new desc" }
$.ajax({
url: '/en/hours/api/edit/' + id + '/',
type: "PATCH",
data: putData,
success: function(data, textStatus, jqXHR) {
// ....
}
}
In this case I would have wanted my description to be updated, but I get errors that the fields are required(for 'project'and all the rest). The validation fails. If add to the AJAX call all the fields it still fails when it haves to retrieve the 'project'.
I tried also to make my own view:
#api_view(['PATCH'])
#permission_classes([permissions.IsAuthenticated])
def edit_time(request):
if request.method == 'PATCH':
serializer = TimeSerializer(request.user, data=request.DATA, partial=True)
if serializer.is_valid():
time_entry = serializer.save()
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)
This did not work for partial update for the same reason(the validation for the fields were failing) and it did not work even if I've sent all the fields. It creates a new entry instead of editing the existing one.
I would like to re-use the same serializer and validations, but I am open to any other suggestions.
Also, if someone has a piece of working code (ajax code-> api view-> serializer) would be great.
class DetailView(APIView):
def get_object(self, pk):
return TestModel.objects.get(pk=pk)
def patch(self, request, pk):
testmodel_object = self.get_object(pk)
serializer = TestModelSerializer(testmodel_object, data=request.data, partial=True) # set partial=True to update a data partially
if serializer.is_valid():
serializer.save()
return JsonResponse(code=201, data=serializer.data)
return JsonResponse(code=400, data="wrong parameters")
Documentation
You do not need to write the partial_update or overwrite the update method. Just use the patch method.
Make sure that you have "PATCH" in http_method_names. Alternatively you can write it like this:
#property
def allowed_methods(self):
"""
Return the list of allowed HTTP methods, uppercased.
"""
self.http_method_names.append("patch")
return [method.upper() for method in self.http_method_names
if hasattr(self, method)]
As stated in documentation:
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the partial argument in order to allow partial updates.
Override update method in your view:
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = TimeSerializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save(customer_id=customer, **serializer.validated_data)
return Response(serializer.validated_data)
Or just override method partial_update in your view:
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
Serializer calls update method of ModelSerializer(see sources):
def update(self, instance, validated_data):
raise_errors_on_nested_writes('update', self, validated_data)
# Simply set each attribute on the instance, and then save it.
# Note that unlike `.create()` we don't need to treat many-to-many
# relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with.
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Update pushes the validated_data values to the given instance. Note that update should not assume all the fields are available. This helps to deal with partial updates (PATCH requests).
The patch method is worked for me using viewset in DRF. I'm changing you code:
class UserViewSet(viewsets.ModelViewSet):
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
def perform_update(self, serializer):
user_instance = serializer.instance
request = self.request
serializer.save(**modified_attrs)
return Response(status=status.HTTP_200_OK)
Use ModelViewSet instead and override perform_update method from UpdateModelMixin
class UserViewSet(viewsets.ModelViewSet):
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
def perform_update(self, serializer):
serializer.save()
# you may also do additional things here
# e.g.: signal other components about this update
That's it. Don't return anything in this method. UpdateModelMixin has implemented update method to return updated data as response for you and also clears out _prefetched_objects_cache. See the source code here.
I ran into this issues as well, I solved it redefining the get_serializer_method and adding custom logic to handle the partial update. Python 3
class ViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action == "partial_update":
return PartialUpdateSerializer
Note: you may have to override the partial_update function on the serializer. Like so:
class PartialUpdateSerializer(serializers.Serializer):
def partial_update(self, instance, validated_data):
*custom logic*
return super().update(instance, validated_data)
Another posiblity is to make the request by URL. For example, I have a model like this
class Author(models.Model):
FirstName = models.CharField(max_length=70)
MiddleName = models.CharField(max_length=70)
LastName = models.CharField(max_length=70)
Gender = models.CharField(max_length=1, choices = GENDERS)
user = models.ForeignKey(User, default = 1, on_delete = models.CASCADE, related_name='author_user')
IsActive = models.BooleanField(default=True)
class Meta:
ordering = ['LastName']
And a view like this
class Author(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
So can enter http://127.0.0.1:8000/author/ to get or post authors. If I want to make a PATCH request you can point to http://127.0.0.1:8000/author/ID_AUTHOR from your client. For example in angular2, you can have something like this
patchRequest(item: any): Observable<Author> {
return this.http.patch('http://127.0.0.1:8000/author/1', item);
}
It suppose you have configured your CORS and you have the same model in back and front.
Hope it can be usefull.

Using the Django Rest Framework

I have the following model that basically stores a random hash value for each tag associated with a particular user.
class PublicTags(models.Model):
tag = models.ForeignKey(Tag, related_name='hashes')
hash_value = models.CharField(max_length=103)
user = models.ForeignKey(User, related_name='tags')
public = models.BooleanField(default=False)
class Meta:
unique_together = ('tag', 'user')
verbose_name_plural = 'Public Tag Hashes'
def __unicode__(self):
return u'%s: %s' % (self.tag, self.user, self.hash_value)
I am trying to use the Django Rest Framework to do the following:
Create a view that will be accessed at api/v1/public_tags.
I will use AJAX to post data using the Django Rest API.
On a post, the system does the following:
It checks to see if there is already a Public Tag row for the tag id (sent via post)
If not, it creates a random hash_value for that tag and user.
I am confused about how to use the Django Rest Framework to accomplish this task.
I got this far:
class PublicTagView(RetrieveUpdateAPIView):
model = PublicTags
serializer_class = serializers.PublicTagSerializer
permission_classes = (IsAuthenticated,)
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.SingleObjectAPIView):
"""
Concrete view for retrieving or updating a model instance.
FIXME: the newest version of rest_framework has this class
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
As far as I understand you need to query an individual record and create a record.
In this case generic class based views are fine enough to start with.
You can then map these views in your urls.py like this:
urlpatterns = patterns('',
url(r'^api/v1/public_tags/$', views.PublicTagsList.as_view()),
url(r'^api/v1/public_tags/(?P<pk>[0-9]+)/$', views.PublicTagsDetail.as_view()),
)
This is valid if you use primary key to fetch individual record.