Django: Email isn't sent in built-in PasswordResetView - django

I'm overriding the built-in PasswordResetView but the email isn't sent. I'm currently using django.core.mail.backends.console.EnailBackend but the email's content doesn't show up on console.
My code is like this
class CustomPasswordResetView(PasswordResetView):
email_template_name = 'accounts/password_reset_email.html'
form_class = CustomForm
template_name = 'accounts/password_reset.html'
subject_template_name = 'accounts/password_reset_subject.txt'
title = 'Custom Title'
success_url = reverse_lazy('accounts/password_reset_done')
It redirects to the password_reset_done as expected but the email doesn't show on concole.
Is there something I missed? As long as I see the Django's code, I cannot find the part handling with sending email in PasswordResetView Do I have to write email functionality manually?
forms.py
class CustomPasswordResetForm(PasswordResetForm):
def __init__(self, *args, **kwargs):
super(CustomPasswordResetForm, self).__init__(*args, **kwargs)
...
def save(self, ...):
super().save()

Problem is that, the email is sent from the form, not the view. So if you are using CustomForm, its better to implement the send email method in the form like this:
class CustomForm(forms.Form):
...
def send_mail(self):
return send_mail(
'Subject here',
'Here is the message.',
'from#example.com',
[self.cleaned_data.get('email')],
fail_silently=False,
)
def is_valid(self):
valid = super(CustomForm, self).is_valid()
if valid:
self.send_email()
return valid
Or you can override from PasswordResetForm and put your customization there.

Related

Can't get uuid value in email body in django

I have authentication app with email verification in it. I send an email like this:
If registration form is valid then we save the form(create user) and set his token to uuid.uuid4.
class customer_register(CreateView):
model = User
form_class = CustomerSignUpForm
template_name = 'authentication/customer_register.html'
def form_valid(self, form):
user = form.save()
user.token = str(uuid.uuid4)
subject = 'Verify your account | Zane'
message = f"http://127.0.0.1:8000/verify/{user.token}/"
send_mail(
subject,
message,
'from#example.com',
['to#example.com'],
fail_silently=False,
)
In my mailtrap.io email arrives but it has some weird body:
http://127.0.0.1:8000/verify/<function uuid4 at 0x103f32040>/
Please use str(uuid.uuid4()) instead of str(uuid.uuid4)

Not able to send email overriding create method django rest framework

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.

When and how to use form methods

I am reading the Django documentation and ran across a couple examples in the forms sections that I do not understand why they did something 2 separate ways..
In the first example I found they send email from the FBV. This makes a lot of sense to a beginner:
from django.core.mail import send_mail
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
cc_myself = form.cleaned_data['cc_myself']
recipients = ['info#example.com']
if cc_myself:
recipients.append(sender)
send_mail(subject, message, sender, recipients)
return HttpResponseRedirect('/thanks/')
In the second example I found they use CBVs and apply methods to the form and call this method in the view:
from django import forms
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
# send email using the self.cleaned_data dictionary
pass
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)
When should you apply form methods and call those methods in the view? Is there a benefit to writing the code this way?
Also, in the second example they used pass in the send_email() method. I wanted to investigate further so I applied this to simple invite form I had..
class InviteForm(forms.Form):
name = forms.CharField()
phone = PhoneNumberField(help_text='Format must be: +15595551234', required=False)
email = forms.EmailField()
subject = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
send_mail(self.subject, self.message, 'someone#company.com', [self.email])
class InviteView(FormView):
template_name = 'invite/invite_form.html'
form_class = InviteForm
success_url = reverse_lazy('overview')
def form_valid(self, form):
form.send_email()
return super().form_valid(form)
Issue here is that I get:
AttributeError at /invite/
'InviteForm' object has no attribute 'subject'
Do I have to create parameters and pass them as arguments in the view after pulling the forms cleaned_data? If so, what is the point of creating the form method?
There isn't really a "correct" way of doing this. One could argue for doing this in the view but there are also good arguments to be made why send_mail should be part of the form.
Personally I like to keep my views as concise as possible. This is the first thing you read when you try to understand the behavior of the app. By just writing form.send_mail() it's clear that an email will be sent based on the data passed to the form. How this is done is nicely hidden and not really of concern to the view.
If you then later want to change what gets sent in the email, all the functionality is in one place, the form: the fields to populate and the send_mail functionality.

Django Contact Emails Not Working

I have a contact form on my Django site and when submitted it goes to the success url but the email is not sent, and the logging set up in the form_valid function is never called.
Here is the code for the view:
class ContactView(FormView):
form_class = ContactForm
template_name = "contact.html"
success_url = "/contact-sent/"
def form_valid(self, form):
message = "{name} / {email} said: ".format(
name=form.cleaned_data.get('name'),
email=form.cleaned_data.get('email'))
message += "\n\n{0}".format(form.cleaned_data.get('message'))
recipients = [recipient for recipient in settings.LIST_OF_EMAIL_RECIPIENTS]
try:
send_mail(
subject=form.cleaned_data.get('subject').strip(),
message=message,
from_email='XXX#XXX.com'
recipient_list=recipients,
)
logger = logging.getLogger(__name__)
logger.info("Contact Email sent successfully")
except Exception as e:
logger = logging.getLogger(__name__)
logger.warning("Contact Email failed to send\nInfo: %s" % e)
return super(ContactView, self).form_valid(form)
and the form, which is a model form using floppyforms and crispyforms:
class ContactForm(ffuture.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'id-contactForm'
self.helper.form_class = 'contact-form'
self.helper.form_method = 'post'
self.helper.form_action = 'submit-feedback'
self.helper.form_tag = True
self.helper.layout = Layout(
Fieldset(
_('Contact Us'),
Field('name', placeholder=_('Name'), css_class='input-medium'),
Field('email', placeholder=_('Email'), css_class='input-xlarge'),
Field('subject', placeholder=_('Subject'), css_class='input-xlarge'),
Field('message', placeholder=_('Add a message'), rows='5', css_class='input-xlarge'),
),
)
self.helper.add_input(Submit('submit', _('Submit')))
class Meta:
model = Feedback
fields = ('name', 'email', 'subject', 'message')
and the model:
#python_2_unicode_compatible
class Feedback(models.Model):
subject = models.CharField(max_length=100)
message = models.TextField(max_length=500)
name = models.CharField(max_length=100)
email = models.EmailField()
creation_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.topic
class Meta:
verbose_name_plural = _("Feedback")
verbose_name = _("Feedback")
The emails are never sent, and the Feedback model is never updated in the admin.
Anyone have any ideas as to why this would be happening? I've been pouring over the code and looking at other examples and none of them seem much different from what I have. I am stumped as to why it is not sending the emails, nor calling any of the logging in the form_valid method.
Ideally I want it to send the contact email to the recipients, and also save the information entered into the Feedback model.
Two other things that may be relevant:
The site is currently running on Apache and for the from_email set in the view I never configured any credentials for it. I am unsure of where to do this. But even if that's the reason the email is not being sent, I don't see why the Feedback model would not be updated.
Thanks for any help you guys can provide, I've been stuck on this for a bit now.
EDIT:
I was thinking it could be the send_mail function that is the issue, but I added logging above the try block and that wasn't called either, so I am now sure that the form_valid method is never being called.
As for the Feedback model not being saved, I realized this is probably because I am never actually saving the form.
I am a bit confused here, because I am using a model form for the contact so the user submitting the form is not logged in. The objective was to both send the email, and store the results in the database. But I can't seem to figure out how I should go about saving the modelform without a valid user.
Would it be enough to just do
feedback = form.save()
inside my form_valid method in the ContactView? Or do I want a save method inside my model form?
The solution was to just call
form.save()
and store the model. The user being logged in did not matter as the fields on the model don't reference a logged in user at all.

Compare two Django fields to be identical, raise Validation error if not

I am putting together a Non Disclosure Form(NDA) on my clients site and need to add some field comparison validation. When someone wants more information on one of my clients listings, they would register a name and email (AllAuth) and upon verifying their email, they would immediately be shown the NDA form which would have already inserted their user.first_name and user.last_name at the top of the form. At the bottom of the form after other fields to fill out, they would type their signature in a field that would have the validation -{{ nda.user_signature }}.
I did my best to write a filter that would concatenate the Django user fields to create a signature variable and then compare the value of user_signature matches, if not, it would raise an error. Below is custom tag attempt. Any thoughts are greatly appreciated. Thank you.
from django import template
from django.db.models import F
from django.forms import forms
register = template.Library()
#register.filter(name='confirm_sig')
def confirm_sig(value):
delta = value
signature = F('user.first_name') + "" + F('user.last_name')
if delta != signature:
raise forms.ValidationError("Signatures must match first and last name")
return delta
UPDATED
Here is my form currently
class BaseModelForm(ModelForm):
def __init__(self, *args, **kwargs):
kwargs.setdefault('auto_id', '%s')
kwargs.setdefault('label_suffix', '')
super().__init__(*args, **kwargs)
for field_name in self.fields:
field = self.fields.get(field_name)
if field:
field.widget.attrs.update({
'placeholder': field.label,
'class': 'form-control placeholder-no-fix'
})
class NonDisclosureForm(BaseModelForm):
class Meta:
model = NonDisclosure
fields = ['user_signature', 'user_street', 'user_email', 'user_city', 'user_state', 'user_zip', 'phone', 'cash_on_hand', 'value_of_securities', 'equity_in_real_estate', 'other']
class NdaCreate(CreateView):
form_class = NonDisclosureForm
template_name = 'nda/nda_form.html'
def form_valid(self, form):
form.instance.user = Profile.objects.get(user=self.request.user)
form.instance.created_by = self.request.user
return super(NdaCreate, self).form_valid(form)