Django - Sending emails using class based view - django

I have been making a website containing a listview and formview. I have a form.py which looks like this :
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(required=True)
email = forms.EmailField(required=True)
message = forms.CharField(required=True)
and the views.py :
from django.shortcuts import render
from .forms import ContactForm
from django.views.generic import ListView
from django.views.generic.edit import FormMixin
from blog.models import Post
from django.core.mail import EmailMessage
class PostListAndFormView(FormMixin,ListView):
queryset = Post.objects.all().order_by("-date")[:2]
template_name = 'personal/index.html'
form_class = ContactForm
success_url = 'personal/index.html'
This view also deals with a Listview functionality which you can ignore.
My HTML template is:
<form action="/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="SEND MESSAGE" />
</form>
Finally urls.py :
from django.conf.urls import url,include
from . import views
from homepage.views import PostListAndFormView
urlpatterns = [
url(r'^$', PostListAndFormView.as_view(), name='PostListAndFormView'),
]
What I want to do here is take the data from the form and send an email to my address with the entered data. Someone help me do this.

Don't know why you mixed up listview with formview. From your model it's kind a blog app. Normally in a blog app the articles/posts list page doesn't contain any form with email field, except for article detail or Contact Us page. I prefer to split it into different class than using mixin.
My answer based on Daniel Greenfeld's example here with little bit modification.
In yourapp/forms.py:
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
import floppyforms as forms
class ContactForm(forms.Form):
name = forms.CharField(required=True)
email = forms.EmailField(required=True)
subject = forms.CharField(required=True)
message = forms.CharField(widget=forms.Textarea)
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.add_input(Submit('submit', 'Submit'))
super(ContactForm, self).__init__(*args, **kwargs)
In yourapp/views.py:
from django.views.generic.list import ListView
from django.views.generic.edit import FormView
from django.conf import settings
from django.core.mail import send_mail
from yourapp.models import Post
from yourapp.forms import ContactForm
class PostListView(ListView):
model = Post
def get_context_data(self, **kwargs):
context = super(ArticleListView, self).get_context_data(**kwargs)
context['latest_articles'] = Post.objects.all()[:5]
return context
class ContactFormView(FormView):
form_class = ContactForm
template_name = "myapp/email_form.html"
success_url = '/email-sent/'
# this is what you want
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'))
send_mail(
subject=form.cleaned_data.get('subject').strip(),
message=message,
from_email='contact-form#myapp.com',
recipient_list=[settings.LIST_OF_EMAIL_RECIPIENTS],
)
return super(ContactFormView, self).form_valid(form)

Have a look at the documentation, check the methods of the FormMixin and pick the one that fits.
form_valid() might be an option.
in python3:
def form_valid(self, form):
# do your stuff
# call the parents class method
return super().form_valid(form)
This is a quite common approach when using class based views. They usually provide a bunch of attributes and methods - sometimes it is enough to just change some attributes, which change the behavior of the methods (e.g. success_url). But often you will need to override some of there methods. It is always a good idea to read their code in this case, cause you need to decide whether you want to call the mixins implementation at the beginning, at the end (as in the example) or if you really want to override it and provide their logic on your own...

Related

How to properly implement Django form session wizard

I've been attempting to construct a multi-step form using the Django session wizard for hours, but I keep getting the error, AttributeError: 'function' object has no property 'as_view'. I'm not sure why this mistake occurred. Any ideas?
views
from django.shortcuts import render
from formtools.wizard.views import SessionWizardView
from .forms import WithdrawForm1, WithdrawForm2
class WithdrawWizard(SessionWizardView):
template_name = 'withdraw.html'
form_list = [WithdrawForm1, WithdrawForm2]
def done(self, form_list, **kwargs):
form_data = [form.cleaned_data for form in form_list]
return render(self.request, 'done.html', {'data': form_data})
forms
from django import forms
from .models import Investment, Withdraw
from .models import WithdrawStepOne, WithdrawStepTwo
class WithdrawForm1(forms.ModelForm):
class Meta:
model = WithdrawStepOne
fields = ['investment_id',]
class WithdrawForm2(forms.ModelForm):
class Meta:
model = WithdrawStepTwo
fields = [
'proof_of_address',
'user_pic'
]
urls
from django.urls import path
from .forms import WithdrawForm1, WithdrawForm2
from . import views
urlpatterns = [
path('withdraw/', views.WithdrawWizard.as_view(), name='withdraw'),
]
You used #login_required decorator on WithdrawWizard class, but the decorator works only for function based views.
Use LoginRequiredMixin for class based views.

how do I add template conditions in the ListView class?

I have a ListView class in views.py, I want to add a condition if the authenticated user displays another template
urls.py
from django.urls import path, include
from django.contrib.auth import views as auth_views
from .views import (
PostListView,
)
urlpatterns = [
path('', PostListView.as_view(), name='index'),
]
Views.py
from django.shortcuts import render, get_object_or_404
from django.views.generic import (
ListView,
)
from .models import Post
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
class PostListView(ListView):
model = Post
template_name = 'page/index.html'
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 7
i want to add
if self.request.user.is_authenticated:
template_name = 'page/index.html'
else:
template_name = 'page/home.html'
Django 2.2.x
You can override the get_template_names function [Django-doc]:
class PostListView(ListView):
model = Post
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 7
def get_template_names(self):
if self.request.user.is_authenticated:
return ['page/index.html']
else:
return ['page/home.html']
As the documentation says, this function:
Returns a list of template names to search for when rendering the template. The first template that is found will be used.
If template_name is specified, the default implementation will return a list containing template_name (if it is specified).
That being said, if you do not plan to render the list on your home.html page, it might be better to perform a redirect to another page, instead of just rendering a page. Otherwise, if you later want to add more content to your home.html page, you will each time need to update all the views that render this.
The basic implementation [GitHub] in the TemplateResponseMixin [Django-doc] is thus:
def get_template_names(self):
"""
Return a list of template names to be used for the request. Must return
a list. May not be called if render_to_response() is overridden.
"""
if self.template_name is None:
raise ImproperlyConfigured(
"TemplateResponseMixin requires either a definition of "
"'template_name' or an implementation of 'get_template_names()'")
else:
return [self.template_name]

How can I use "add_fields" in django?

I was reading django docs and found the add_fields method.
The documentation says:
"If you need to add additional fields to the formset this can be
easily accomplished. The formset base class provides an add_fields
method."
I want to use it but there is no example or explanation how
it can be used in views and templates. Can you provide me a small
example of using this method?
I will give a small example.
The models.py looks like this,
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=100)
pub_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
forms.py looks like this,
from django import forms
from django.forms import BaseFormSet
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title']
class BaseArticleFormSet(BaseFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
form.fields['body'] = forms.CharField()
views.py looks like this,
from django.shortcuts import render
from django.forms import formset_factory
from .forms import ArticleForm, BaseArticleFormSet
def home(request):
ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, extra=3)
context = dict()
formset = ArticleFormSet()
if request.method == 'POST':
formset = ArticleFormSet(request.POST)
if formset.is_valid():
print(formset.cleaned_data)
context['formset'] = formset
return render(request, 'home.html', context)
Finally, home.html will look like this,
<form method="post">
{% csrf_token %}
<table>
{{ formset }}
</table>
<button type="submit">submit</button>
</form>
As you can see, the body field appears in templates but it is not included in the the Article model.
Hope that helps!

How can I set success_mesasge with format() in django generic view?

What I want to implement is like this :
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic import CreateView
from posts.models import Post
class PostNewView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = Post
fields = ['title', 'content', 'image']
success_message = "{} has been created successfully".format(self.post.title)
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
But it occurs error. Any ideas?
This isn't an issue with format(), but with trying to reference an attribute that doesn't exist at that point. Attributes at class level are evaluated at import time, but self.title only exists at runtime, and only within a method.
Rather than setting the message at that level, you should use the get_success_message method:
def get_success_message(self, cleaned_data):
return "{} has been created successfully".format(cleaned_data['title'])

Django Template Context Processor with Class Based Views

I have a Login Class Based View which I want to include in all my pages.
Is there a way to include Class Based View as template context processor, or do we have any other way to include in all pages ?
class LoginView(RedirectAuthenticatedUserMixin, FormView):
form_class = LoginForm
template_name = "account/login.html"
success_url = None
redirect_field_name = "next"
def form_valid(self, form):
success_url = self.get_success_url()
return form.login(self.request, redirect_url=success_url)
def get_success_url(self):
# Explicitly passed ?next= URL takes precedence
ret = (get_next_redirect_url(self.request,
self.redirect_field_name)
or self.success_url)
return ret
def get_context_data(self, **kwargs):
ret = super(LoginView, self).get_context_data(**kwargs)
signup_url = passthrough_next_redirect_url(self.request,
reverse("account_signup"),
self.redirect_field_name)
redirect_field_value = self.request.REQUEST \
.get(self.redirect_field_name)
ret.update({"signup_url": signup_url,
"site": Site.objects.get_current(),
"redirect_field_name": self.redirect_field_name,
"redirect_field_value": redirect_field_value})
return ret
login = LoginView.as_view()
You can decorate the view:
Either in urls.py:
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
from .views import VoteView
urlpatterns = patterns('',
(r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))),
(r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())),
)
Or in views.py:
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = 'secret.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
Or, you can inherit from LoginRequiredMixin from django-braces:
from braces.views import LoginRequiredMixin
class Index(LoginRequiredMixin, TemplateView):
template_name = 'sekret.html'
All these examples show how to require authentication for class based views (which I believe is what you are trying to do). The first two are directly from the documentation.
Once you implement either of the above examples, your application will behave like this:
A user enters a URL that maps to one of the above views.
django middleware checks if the user is logged in.
If not, django will redirect to LOGIN_URL, by default its /accounts/login/, and add a ?next parameter, pointing to the original URL from #1.
The view from #3 will display a login form.
Once the user successfully authenticates, the view from #3 will update the session, and redirect the user to the URL from the next parameter.
If the user is logged in, then the decorated view will be executed as normal.