I am tryting to implement sendgrid email verification to my django project. I have been following https://www.twilio.com/blog/send-dynamic-emails-python-twilio-sendgrid tutorial. I have models and views setup accordingly however, I am facing UniqueConstraint error when trying to use the same email address twice (although my model has Email Field set to unique = True). Instead of getting UniqueConstraint error, I would like the app just to prevent user from adding the same email twice. I tried to use cleaned_data for form, but I cannot get through this UniqueConstraint error anyway.
My error:
Exception Type: IntegrityError
Exception Value:
UNIQUE constraint failed: newsletter_newsletteruser.email
My models.py:
from django.db import models
class NewsletterUser (models.Model):
email= models.EmailField(unique=True)
date_added = models.DateTimeField(auto_now_add=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)"
My admin.py
from .models import NewsletterUser, MailMessage
# Register your models here.
class NewsletterAdmin(admin.ModelAdmin):
list_display = ('email','date_added','conf_num', 'confirmed')
admin.site.register(NewsletterUser, NewsletterAdmin)
admin.site.register(MailMessage)
My views.py
from django.db.models.fields import EmailField
from django.shortcuts import render, redirect
from sendgrid.helpers.mail.bcc_email import Bcc
from django.http import HttpResponse
from .forms import NewsletterUserForm, MailMessageForm
from django.contrib import messages
from django.contrib.auth.decorators import login_required
import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Email, Content, To
from .models import NewsletterUser
from django import forms
import pandas as pd
from django.shortcuts import render
from django.http import HttpResponse
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
import random
from sendgrid import SendGridAPIClient
# Create your views here.
#!REGISTER CURRENT HOST!
host = 'http://127.0.0.1:8000/'
# Helper Functions
def random_digits():
return "%0.12d" % random.randint(0, 999999999999)
@csrf_exempt
def newsletters(request):
if request.method == 'POST':
sub = NewsletterUser(email=request.POST['email'], conf_num=random_digits())
sub.save()
message = Mail(
from_email='[email protected]',
to_emails=sub.email,
subject='Potwierdzenie Adresu Email w Newsletterze Pasieka Radość',
html_content= "<h1>Dziękujemy za zapisanie się do newslettera Pasieka Radość!<h1> \
<h2>Aby potwierdzić rejestrację newslettera</h2> \
<h2><a href='{}confirm/?email={}&conf_num={}'> Kliknij w poniższy link</a>.<h2>".format(request.build_absolute_uri(host),
sub.email,
sub.conf_num))
sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
response = sg.send(message)
return render(request, 'newsletter/newsletter.html', {'email': sub.email, 'action': 'wysłano', 'form': NewsletterUserForm()})
else:
return render(request, 'newsletter/newsletter.html', {'form': NewsletterUserForm()})
def confirm(request):
sub = NewsletterUser.objects.get(email=request.GET['email'])
if sub.conf_num == request.GET['conf_num']:
sub.confirmed = True
sub.save()
messages.success(request, f'Dziękujemy za zapisanie adresu email do naszego newslettera!')
return render(request, 'pages/home.html' , {'email': sub.email, 'action': 'confirmed'})
My forms.py
from django import forms
from .models import NewsletterUser, MailMessage
class NewsletterUserForm (forms.ModelForm):
email = forms.EmailField(label='Twój email',
max_length=100,
widget=forms.EmailInput(attrs={'class': 'form-control'}))
class Meta:
model = NewsletterUser
fields = ['email']
My html template (newsletter.html)
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content%}
<h1> {{title}} </h1>
<h4>Zapisz się do newslettera i odbieraj najświeższe informacje dotyczące naszej pasieki i oferty.</h4>
<div class="col-12">
{% if email %}
<p class="alert alert-info">Na {{ email }} {{ action }} wiadomość weryfikacyjną. Otwórz wiadomość i kliknij w link aby dodać adres do newslettera. </p>
{% endif %}
<form method="POST" autocomplete="off">
{% csrf_token %}
<div class="col-sm-4">
{{form|crispy}}
</div>
<br>
<div class="col-sm-4">
<input class="btn btn-info" type='submit' value='Zapisz Się'>
</div>
</form>
<br>
<div class="row">
<div class="col-sm-4">
<p>Spokojnie, nie będziemy wysyłać Ci tony niepotrzebnych wiadomości. Zapisz się do newslettera i dowiedz się pierwszy/pierwsza, kiedy w naszej pasiecie pojawi się nowa dostawa miodów lub innych interesujących produktów.</p>
</div>
</div>
</div>
{% endblock content %}
I am quite new to django and sendgrid, so it makes me wonder if I need to somehow rebuild my code totally, or there are some steps I could take to make it work as it is above. If any hints, please share. Thank you
CodePudding user response:
Twilio SendGrid developer evangelist here.
I believe Django is behaving as expected here. From the Django documentation:
If you try to save a model with a duplicate value in a unique field, a
django.db.IntegrityError
will be raised by the model’ssave()
method.
What you need to do is catch that IntegrityError
and then provide an error back to the user.
@csrf_exempt
def newsletters(request):
if request.method == 'POST':
sub = NewsletterUser(email=request.POST['email'], conf_num=random_digits())
try:
sub.save()
message = Mail(
from_email='[email protected]',
to_emails=sub.email,
subject='Potwierdzenie Adresu Email w Newsletterze Pasieka Radość',
html_content= "<h1>Dziękujemy za zapisanie się do newslettera Pasieka Radość!<h1> \
<h2>Aby potwierdzić rejestrację newslettera</h2> \
<h2><a href='{}confirm/?email={}&conf_num={}'> Kliknij w poniższy link</a>.<h2>".format(request.build_absolute_uri(host),
sub.email,
sub.conf_num))
sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
response = sg.send(message)
return render(request, 'newsletter/newsletter.html', {'email': sub.email, 'action': 'wysłano', 'form': NewsletterUserForm()})
except IntegrityError as ex:
# Return the error to your user
else:
return render(request, 'newsletter/newsletter.html', {'form': NewsletterUserForm()})
I've added the try/except
into the code above, you will need to fill in what to do in the except
block when the email is not unique.