Django Context getting lost in email
I am trying to send a forgot password email in Django. Although even after using print debug statements to make sure that the slug is available in my context, it's keeps throwing an error.
My models.py:
class Restaurant(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
description = models.TextField()
landing_page_tagline = models.TextField()
landing_page_image = models.ImageField(upload_to='restaurant_images/')
address = models.CharField(max_length=255, null=True)
phone = models.CharField(max_length=20, null=True)
logo_image = models.ImageField(upload_to='restaurant_images/')
primary_color = models.CharField(max_length=7) # Hex color code
favicon = models.FileField(upload_to='restaurant_favicons/', null=True, blank=True)
about_us_image1 = models.ImageField(upload_to='restaurant_images/')
about_us_text1 = models.TextField(blank=True, null=True)
about_us_image2 = models.ImageField(upload_to='restaurant_images/')
about_us_text2 = models.TextField(blank=True, null=True)
about_us_image3 = models.ImageField(upload_to='restaurant_images/')
about_us_text3 = models.TextField(blank=True, null=True)
contact_us_image = models.ImageField(upload_to='restaurant_images/')
map_iframe_src = models.TextField(blank=True, null=True)
footer_text = models.TextField()
facebook_link = models.URLField(null=True, blank=True)
instagram_link = models.URLField(null=True, blank=True)
youtube_link = models.URLField(null=True, blank=True)
twitter_link = models.URLField(null=True, blank=True)
slug = models.SlugField(unique=True, max_length=100, null=True)
stripe_subscription_id = models.CharField(max_length=255, blank=True, null=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def __str__(self):
return self.name
class RestaurantUser(AbstractUser):
restaurant = models.OneToOneField('Restaurant', on_delete=models.CASCADE, null=True, blank=True)
phone = models.CharField(max_length=20, blank=True, null=True)
is_restaurant_admin = models.BooleanField(default=False)
groups = models.ManyToManyField(
Group,
related_name='restaurantuser_set',
blank=True,
help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
related_query_name='restaurantuser',
)
user_permissions = models.ManyToManyField(
Permission,
related_name='restaurantuser_set',
blank=True,
help_text='Specific permissions for this user.',
related_query_name='restaurantuser',
)
def save(self, *args, **kwargs):
if self.is_restaurant_admin:
self.is_staff = True
super().save(*args, **kwargs)
My views.py:
class CustomPasswordResetView(PasswordResetView):
template_name = 'restaurants/forgot_password.html'
email_template_name = 'restaurants/password_reset_email.html'
form_class = PasswordResetForm
def get_success_url(self):
print(f"DEBUG: get_success_url - restaurant_slug: {self.kwargs.get('restaurant_slug')}")
return reverse_lazy('password_reset_done', kwargs={'restaurant_slug': self.kwargs['restaurant_slug']})
def form_valid(self, form):
email = form.cleaned_data['email']
restaurant_slug = self.kwargs.get('restaurant_slug')
print(f"DEBUG: form_valid - restaurant_slug: {restaurant_slug}")
restaurant = get_object_or_404(Restaurant, slug=restaurant_slug)
# Get the user associated with the provided email
UserModel = RestaurantUser
try:
user = UserModel.objects.get(email=email)
except UserModel.DoesNotExist:
form.add_error('email', ValidationError("No user is associated with this email address."))
return self.form_invalid(form)
# Prepare the context
context = {
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': default_token_generator.make_token(user),
'protocol': self.request.scheme,
'domain': self.request.get_host(),
'restaurant_slug': restaurant_slug,
'restaurant': restaurant,
'primary_color': restaurant.primary_color,
}
print(f"DEBUG: form_valid - context: {context}")
# Render HTML content
subject = "Password Reset Requested"
html_content = render_to_string('restaurants/password_reset_email.html', context)
text_content = strip_tags(html_content)
print(f"DEBUG: form_valid - Full html_content:\n{html_content}")
print(f"DEBUG: form_valid - html_content: {html_content[:100]}...") # Print first 100 chars
# Send email using EmailMultiAlternatives
email = EmailMultiAlternatives(subject, text_content, settings.DEFAULT_FROM_EMAIL, [email])
email.attach_alternative(html_content, "text/html")
email.send()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['restaurant_slug'] = self.kwargs.get('restaurant_slug')
print(f"DEBUG: get_context_data - restaurant_slug: {context['restaurant_slug']}")
return context
from django.contrib.auth.views import PasswordResetDoneView
class CustomPasswordResetDoneView(PasswordResetDoneView):
template_name = 'restaurants/password_reset_done.html'
def get(self, request, *args, **kwargs):
print(f"DEBUG: CustomPasswordResetDoneView.get - kwargs: {kwargs}")
return super().get(request, *args, **kwargs)
class CustomPasswordResetConfirmView(PasswordResetConfirmView):
template_name = 'restaurants/reset_password.html'
def dispatch(self, *args, **kwargs):
print(f"DEBUG: CustomPasswordResetConfirmView.dispatch - args: {args}")
print(f"DEBUG: CustomPasswordResetConfirmView.dispatch - kwargs: {kwargs}")
return super().dispatch(*args, **kwargs)
def get_success_url(self):
print(f"DEBUG: CustomPasswordResetConfirmView.get_success_url - kwargs: {self.kwargs}")
return reverse_lazy('password_reset_complete', kwargs={
'restaurant_slug': self.kwargs['restaurant_slug'],
})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
restaurant_slug = self.kwargs.get('restaurant_slug')
print(f"DEBUG: CustomPasswordResetConfirmView.get_context_data - restaurant_slug: {restaurant_slug}")
if not restaurant_slug:
messages.error(self.request, "Invalid restaurant context. Please try again.")
return context
context['restaurant_slug'] = restaurant_slug
context['restaurant'] = get_object_or_404(Restaurant, slug=restaurant_slug)
return context
My urls.py:
path('restaurant/<slug:slug>/', views.restaurant_landing_page_view, name='restaurant_landing_page'),
path('password-reset/<slug:restaurant_slug>/', CustomPasswordResetView.as_view(), name='password_reset'),
path('password-reset-done/<slug:restaurant_slug>/', views.CustomPasswordResetDoneView.as_view(), name='password_reset_done'),
path('password-reset-confirm/<slug:restaurant_slug>/<uidb64>/<token>/', CustomPasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('password-reset-complete/<slug:restaurant_slug>/', auth_views.PasswordResetCompleteView.as_view(template_name='restaurants/password_reset_complete.html'), name='password_reset_complete'),
My password_reset_email.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Reset</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 0;">
<div style="background-color: {{ primary_color }}; color: #ffffff; padding: 20px; text-align: center;">
<h1 style="margin: 0;">{{ restaurant.name }}</h1>
</div>
<div style="padding: 20px;">
<p>Hello,</p>
<p>Debug Info:</p>
<p>restaurant_slug: {{ restaurant_slug }}</p>
<p>uid: {{ uid }}</p>
<p>token: {{ token }}</p>
<p>We received a request to reset your password for your {{ restaurant.name }} account. If you did not make this request, please ignore this email.</p>
<p>To reset your password, click the button below:</p>
<a href="{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' restaurant_slug=restaurant_slug uidb64=uid token=token %}" style="background-color: {{ primary_color }}; color: #ffffff; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">Reset Password</a>
<p>If the button above does not work, paste this link into your web browser:</p>
<p>{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' restaurant_slug=restaurant_slug uidb64=uid token=token %}</p>
<p>Thank you,<br>{{ restaurant.name }} Team</p>
</div>
</body>
</html>
The debug statements that I get:
DEBUG: get_context_data - restaurant_slug: dominos
[01/Sep/2024 12:39:18] "GET /password-reset/dominos/ HTTP/1.1" 200 21115
DEBUG: form_valid - restaurant_slug: dominos
DEBUG: form_valid - context: {'uid': 'OA', 'token': 'ccodtk-d092b2b859d47dd4b086be245bd251f6', 'protocol': 'http', 'domain': '127.0.0.1:8000', 'restaurant_slug': 'dominos', 'restaurant': <Restaurant: Dominos>, 'primary_color': '#2445bc'}
DEBUG: form_valid - Full html_content:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Reset</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 0;">
<div style="background-color: #2445bc; color: #ffffff; padding: 20px; text-align: center;">
<h1 style="margin: 0;">Dominos</h1>
</div>
<div style="padding: 20px;">
<p>Hello,</p>
<p>Debug Info:</p>
<p>restaurant_slug: dominos</p>
<p>uid: OA</p>
<p>token: ccodtk-d092b2b859d47dd4b086be245bd251f6</p>
<p>We received a request to reset your password for your Dominos account. If you did not make this request, please ignore this email.</p>
<p>To reset your password, click the button below:</p>
<a href="http://127.0.0.1:8000/password-reset-confirm/dominos/OA/ccodtk-d092b2b859d47dd4b086be245bd251f6/" style="background-color: #2445bc; color: #ffffff; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">Reset Password</a>
<p>If the button above does not work, paste this link into your web browser:</p>
<p>http://127.0.0.1:8000/password-reset-confirm/dominos/OA/ccodtk-d092b2b859d47dd4b086be245bd251f6/</p>
<p>Thank you,<br>Dominos Team</p>
</div>
</body>
</html>
DEBUG: form_valid - html_content: <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=de...
I am also getting an email with the correct slug.
It's just that I am getting this error every time I try to send an email for password reset:
Internal Server Error: /password-reset/dominos/
Traceback (most recent call last):
File "/opt/anaconda3/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/views/generic/base.py", line 104, in view
return self.dispatch(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/utils/decorators.py", line 48, in _wrapper
return bound_method(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/utils/decorators.py", line 48, in _wrapper
return bound_method(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/utils/decorators.py", line 188, in _view_wrapper
result = _process_exception(request, e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/utils/decorators.py", line 186, in _view_wrapper
response = view_func(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/contrib/auth/views.py", line 229, in dispatch
return super().dispatch(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/views/generic/base.py", line 143, in dispatch
return handler(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/views/generic/edit.py", line 151, in post
return self.form_valid(form)
^^^^^^^^^^^^^^^^^^^^^
File "/Users/abrarshahriar/Documents/bite/restaurants/views.py", line 335, in form_valid
return super().form_valid(form)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/contrib/auth/views.py", line 242, in form_valid
form.save(**opts)
File "/opt/anaconda3/lib/python3.12/site-packages/django/contrib/auth/forms.py", line 455, in save
self.send_mail(
File "/opt/anaconda3/lib/python3.12/site-packages/django/contrib/auth/forms.py", line 389, in send_mail
body = loader.render_to_string(email_template_name, context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/template/loader.py", line 62, in render_to_string
return template.render(context, request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/template/backends/django.py", line 107, in render
return self.template.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/template/base.py", line 171, in render
return self._render(context)
^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/template/base.py", line 163, in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/template/base.py", line 1008, in render
return SafeString("".join([node.render_annotated(context) for node in self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/template/base.py", line 969, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/template/defaulttags.py", line 480, in render
url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/urls/base.py", line 88, in reverse
return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/anaconda3/lib/python3.12/site-packages/django/urls/resolvers.py", line 831, in _reverse_with_prefix
raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'password_reset_confirm' with keyword arguments '{'restaurant_slug': '', 'uidb64': 'OA', 'token': 'ccodtp-e1c4e789e104119efece2f7c409c5bb3'}' not found. 1 pattern(s) tried: ['password\\-reset\\-confirm/(?P<restaurant_slug>[-a-zA-Z0-9_]+)/(?P<uidb64>[^/]+)/(?P<token>[^/]+)/\\Z']
[01/Sep/2024 12:39:25] "POST /password-reset/dominos/ HTTP/1.1" 500 183966
I cannot understand why this error would be raised even after getting the correct slug in my email.