How to write test case for sending email - django

Here is the simple view for creating user and staff model.After creating user and staff, it sends the html email to the user's email to fill up the details and the view works fine.
Now I want to write test case for this view and tried like this below but i got stuck on how can i write test to check whether the email will be sent or not after saving staff model, to the users.
models.py
class Staff(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name='staff')
name = models.CharField(max_length=255, blank=True, null=True)
organization = models.ForeignKey(Organization, on_delete=models.SET_NULL,related_name='staff')
position = models.ForeignKey(Position, on_delete=models.SET_NULL,related_name='staff')
.......
views.py
def register_staff(request):
form = RegisterStaffForm()
if request.method == 'POST':
form = RegisterStaffForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
organization = form.cleaned_data['organization']
position = form.cleaned_data['position']
......
user = form.save(commit=False)
user.is_staff = True
user.is_active = True
user.save()
# creating staff model with user data
Staff.objects.create(user=user, name=name, organization=organization, position=position,....)
# sending html_email to the user
config = EmailConfiguration.objects.order_by('-date').first()
backend = EmailBackend(host=config.email_host, port=config.email_port, username=config.email_host_user,
password=config.email_host_password, use_tls=config.email_use_tls)
subject, from_email, to = "Staff Details", config.email_host_user, user.email
text_content = "Staff Details "
site = get_current_site(request)
html_content = render_to_string('send_email.html',
{'user': user, 'site_domain': site,})
msg = EmailMultiAlternatives(subject, text_content, from_email, [to],connection=backend)
msg.attach_alternative(html_content, "text/html")
msg.send()
tests.py
class StaffTestCase(TestCase):
def setUp(self):
self.position = Position.objects.create(title='Developer')
self.org = Organization.objects.create(name='name')
self.user = get_user_model().objects.create_user(username='username01',password='Admin#321',email='abc#xyz.com',is_staff=True)
self.staff = Staff.objects.create(user=self.user,position=self.position,organization=self.org,name='Julia')
self.client = Client()
def test_view_staffs(self):
self.client.login(username='username01', password='Admin#321')
response = self.client.get(reverse('app:view_staff_users'))
self.assertEqual(response.status_code, 200)
def add_staff(self):
self.client.login(username='username01', password='Admin#321')
url = reverse('app:register_staff')
response = self.client.post(url, {'user': self.user,'organization':'name1','position':'Designer','name':'Mark'})
self.assertEqual(response.status_code, 302)
def check_email_will_sent_or_not(self):
??

Django provides tools to test the sending of emails, but from my understanding, these only work with the default email backend configured in your settings file.
That leaves you with four options:
figure out if you can monkeypatch your view to use locmem backend in testing
mock EmailBackend in your register_staff view and check if its send_messages function is called.
spin up a dummy SMTP server and check if it receives the messages
use valid credentials to actually send the emails to addresses you control and check if the email is received
The options 3 & 4 give you the most confidence that your email sending really works, but they might turn out to be slow and brittle and actually test the email sending code of Django itself. I would go with option 2.

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)

Modify the class based view object to save

I currently have the model such
class Newsletter(models.Model):
email = models.EmailField(null=False, blank=True, max_length=200, unique=True)
conf_num = models.CharField(max_length=15)
confirmed = models.BooleanField(default=False)
def __str__(self):
return self.email + " (" + ("not " if not self.confirmed else "") + "confirmed)"
And I have the class based view
class NewsletterView(SuccessMessageMixin, CreateView):
template_name = 'newsletter.html'
success_url = reverse_lazy('newsletter')
form_class = NewsletterRegisterForm
success_message = "Check your inbox for the verification email"
def form_valid(self, form):
self.conf_num = random_digits()
subject = 'Newsletter Confirmation',
html_content = 'Thank you for signing up for my email newsletter! \
Please complete the process by \
<a href="{}/confirm/?email={}&conf_num={}"> clicking here to \
confirm your registration</a>.'.format(self.request.build_absolute_uri('/confirm/'),
self.email,
self.conf_num)
sender = "noreply#example.com"
recipient = form.cleaned_data['email']
msg = EmailMultiAlternatives(subject, html_content, sender, [recipient])
msg.send()
return super().form_valid(form)
I'm slightly confused as to how I would be able to set via the class based view, the conf_num? Would I have to say in my form_valid function correctly call self.conf_num = number?
When I try either of these methods I either get that the email is not unique or that the newsletter object has no email. Any help would be apprecicated.
I would choose this method,
class NewsletterView(SuccessMessageMixin, CreateView):
template_name = 'newsletter.html'
success_url = reverse_lazy('newsletter')
form_class = NewsletterRegisterForm
success_message = "Check your inbox for the verification email"
def send_email(self, conf_num):
# gather relevant data for email compose
# you can use function args or instance attributes
# and then, send mail from here
email.send()
def form_valid(self, form):
response = super().form_valid(form) # calling the `super()` method on the top will be the best, in this case
conf_num = random_digits()
self.send_email(conf_num)
# after sending the mail, access the `self.object` attribute
# which hold the instance which just created
self.object.conf_num = conf_num # assign the value
self.object.save() # call the save() method to save the value into the database
return response
I hope the comments are self-explanatory here :)
In this case, the form is the object that holds the Newsletter instance.
def form_valid(self, form):
form.conf_num = random_digits()
newsletter = form.save()

Auto populate hidden form fields in modelform

So my model, form, and view are working mostly. View works and sending the email works. The "message" is saved but I cannot get the message_to and message_from to save. It is supposed to save the usernames. I can get everything to save, but cannot get the message saved to the database WITH the to and from usernames. I am trying to only have 1 field in the message. "Content". The to and from should be hidden and auto-populated. I appreciate any other set of eyes on this. Thank you.
'models.py'
class Message(models.Model):
message_content = models.TextField()
message_to = models.ForeignKey(User, on_delete=models.CASCADE, related_name='message_to')
message_from = models.ForeignKey(User, on_delete=models.CASCADE, related_name='message_from')
date_created = models.DateTimeField(default=timezone.now)
unread = models.BooleanField(default=True)
'forms.py'
class MessageSellerForm(forms.ModelForm):
class Meta:
model = Message
'views.py'
def ad_detail(request, *args, **kwargs):
template_name = 'x_ads/ad_detail.html'
ad = get_object_or_404(Ad, pk=kwargs['pk'])
ad.increment_view_count()
if request.method == 'POST':
message_form = MessageSellerForm(data=request.POST)
message_form.message_from = request.user.username
message_form.message_to = ad.creator.username
if message_form.is_valid():
subject = 'Message about your ad. ' + ad.title
from_email = request.user.email
to_email = ad.creator.email
message = 'You have a message about one of your ads waiting for you!'
send_mail(subject=subject, message=message, from_email=from_email,
recipient_list=[to_email], fail_silently=False)
messages.success(request, your message has been sent.')
message_form.save()
return HttpResponseRedirect(request.path_info)
else:
message_form = MessageSellerForm()
return render(request, template_name, {'ad': ad, 'message_form': message_form})
I think I see what you're trying to do there, but there are other ways that I think will be a bit easier.
https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#the-save-method
You could instead:
# create the django object in memory, but don't save to the database
message = message_form.save(commit=False)
message.message_from = request.user.username
message.message_to = ad.creator.username
# now save it to the database
message.save()
If you do that you won't need the assignments to the message form further up:
message_form.message_from = request.user.username
message_form.message_to = ad.creator.username
EDIT
You might also need to modify your MessageSellerForm to not include the message_from and message_to fields so that validation will work. That's OK because you know that you'll be assigning the right values to those fields after form validation but before saving to the database.

Test UpdateView for useraccounts application

Test doesn't give status_code 302 in user profile UpdateView and so there's no updates occurs on the object
the model code
class User(AbstractBaseUser, PermissionsMixin):
'''
This a replaced user profile instead of the default django one
'''
language_choices=[('en',_('English')),('se',_('Swedish'))]
email=models.CharField(verbose_name=_('Email'), max_length=128, blank=False, unique=True)
first_name=models.CharField(verbose_name=_('First Name'), max_length=128)
last_name=models.CharField(verbose_name=_('Last Name'), max_length=128)
joined_at=models.DateField(
verbose_name=_('Joined at'),
auto_now_add=True,
blank=False
)
language=models.CharField(
verbose_name=_('Language'),
max_length=2,
choices=language_choices,
default=language_choices[0][0]
)
active=models.BooleanField(verbose_name=_('Active'), default=False)
is_superuser=models.BooleanField(verbose_name=_('Is Superuser'), default=False)
is_active=models.BooleanField(verbose_name=_('Is Active'), default=True)
is_staff=models.BooleanField(verbose_name=_('Is Staff'), default=False)
The form code
class EditUserForm(UserChangeForm):
'''
Profile form to update existing user information
'''
# error message for email matches
error_messages = {
'email_mismatch': _("The two email fields didn't match."),
}
# create field for email
email1 = forms.EmailField(
label=_("Email"),
widget=forms.EmailInput,
help_text=_("If you change your email your account will be inactive untill your reactivate by email link."),
)
# get the email from confirmed email field
email2 = forms.EmailField(
label=_("Confirm Email"),
widget=forms.EmailInput,
help_text=_("Enter the same email as before, for verification."),
)
# hide password field
password = ReadOnlyPasswordHashField(label="Password")
class Meta:
'''
Initial fields and model for the form
'''
model = models.User
fields = ('first_name','last_name','email1','email2', 'language')
def clean_email2(self):
'''
Method for if email and confirmed email are the same
This method works when confirmed email cleared
'''
# get the email from email field
email1 = self.cleaned_data.get("email1")
# get the email from confirmed email field
email2 = self.cleaned_data.get("email2")
# check if both emails are equal
if email1 and email2 and BaseUserManager.normalize_email(email1) != BaseUserManager.normalize_email(email2):
# give an error message if emails not matches
raise forms.ValidationError(
self.error_messages['email_mismatch'],
code='email_mismatch')
# return the confirmed email
return BaseUserManager.normalize_email(email2)
def save(self, commit=True):
'''
Method tosave the edited user data
'''
# get the initial method
user = super().save(commit=False)
# set the email on the model field
user.email = self.cleaned_data["email1"]
# save edited user data
if commit:
user.save()
return user
def __init__(self, *args, **kwargs):
'''
Method for initial values and functions for the SignUp form class
'''
# get user data from User model
user = get_user_model().objects.get(email=kwargs['instance'])
# get the initial form class values
super(EditUserForm, self).__init__(*args, **kwargs)
# Add the current email as the inital email
self.fields['email1'].initial = user.email
# Add the current email as the intial confirmed email
self.fields['email2'].initial = user.email
# Add help text in the password field for change
self.fields['password'].help_text=(
_("Raw passwords are not stored, so there is no way to see "
"this user's password, but you can change the password "
"using this form.")
.format(reverse(
'core:ChangePassword',
kwargs={'pk':user.pk})))
and the view code
class EditUser(UserPassesTestMixin, UpdateView):
'''
Class view to update user details
'''
# used template
template_name = 'core/edit.html'
# View model
model = models.User
# View form
form_class = forms.EditUserForm
def test_func(self):
return self.request.user == get_user_model().objects.get(pk=self.kwargs['pk'])
def get_success_url(self):
'''
Metho to redirect after a valid form
'''
# check if the email is verified
if self.request.user.active:
# get the user key
pk=self.request.user.pk
# redirect to profile details
return reverse_lazy('core:details', kwargs={'pk':pk})
else:
# send a verification email
return SendActivationEmail(self.request, self.request.user)
the test code
self.viewuser_url = reverse('core:details', kwargs={'pk':self.user.pk})
self.edituser_url = reverse('core:edit', kwargs={'pk':self.user.pk})
def test_edit_user_post(self):
first_name = 'Osama'
response = self.client.post(self.edituser_url,
data={
'first_name': first_name,
'last_name': self.last_name,
'email': self.email,
})
self.assertRedirects(response, self.viewuser_url)
self.user.refresh_from_db()
self.assertEqual(self.user.first_name, first_name)
I tried to get assertEqual for the status code and it gitves me 200 instead of 302
also I tried to enter the form details instead of model details and it gives me an error
The get test works fine and also permission test works great.. all the models, forms and urls test works perfect.
I don't know how I can test this..
If the form isn't valid, then the form will be re-rendered with errors and you'll get a 200 response.
To debug the problem, check response.context['form'].errors in your test to see what the problem is.
response = self.client.post(self.edituser_url,
data={
'first_name': first_name,
'last_name': self.last_name,
'email': self.email,
})
print(response.context['form'].errors
Your view uses EditUserForm, but you are not posting any values for email1 or email2, so there is probably something in the errors about missing data.

How to increase Django forms.Form CharField Size

I've create following form:
class ContactForm(forms.Form):
full_name = forms.CharField(required=False)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea(attrs={'class':'special', 'size': '40'}))
But when I add data in Message field with some data it isn't coming up in my email associated with this form with settings mentioned in Settings.py. But full_name and email is coming up fine.
My view is:
def contact(request):
title = "Contact Us"
form = ContactForm(request.POST or None)
if form.is_valid():
form_email = form.cleaned_data.get('email')
form_message = form.cleaned_data.get('message')
form_full_name = form.cleaned_data.get('full_name')
subject = "Site Contact Form"
from_email = settings.EMAIL_HOST_USER
to_email = [from_email, myemail#gmail.com']
contact_message = "%s: %s via %s"%(form_full_name, form_message, form_email)
html_template = "<h1>Hello There</h1>"
send_mail(subject, contact_message, from_email, to_email, html_message=html_template, fail_silently=True)
context = {
"form":form,
"title": title
}
return render(request, 'contact/form.html', context)
Also I need to know what would be the best option to create some form to recieve information directly to my email. Should I use models based form or simple form without models? Please advise as my message field text is not coming up in email but Name and email is coming up fine.
Try this,
message = forms.CharField(widget=forms.TextInput(attrs={'class':'special', 'size': '40'}))