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.
Related
Good day SO.
I want to ask something basic. I tried on my end to update my data from html to save to database but to no avail. My Model structure is Account extends to AbstractBaseUser, BaseUserManager and CompanyAccount links to Account by OneToOneField. I can say that my Model should have no problem since I can Register Account and Company Account without any problems.
EDIT
Now I can save my CompanyAccount but the link to Account was removed.
Still trying to check which is the problem
views.py
#login_required(login_url='/login/')
def user_my_page(request, *args, **kwargs):
context = {}
context['form_type'] = 'update'
context['account_type'] = request.user.account_type
if request.POST:
if request.user.account_type == 1:
company = CompanyAccount.objects.get(account=request.user)
account_form = CompanyAccountForm(request.POST, instance=company)
if account_form.is_valid():
account_form.save()
print(request.POST)
print('SAVE')
else:
print('ERROR')
forms.py
class AccountCreationForm(UserCreationForm):
accuser_id = forms.CharField(max_length=10, help_text="Required")
class Meta:
model = Account
fields = ("accuser_id", "password1", "password2")
class CompanyAccountForm(forms.ModelForm):
class Meta:
model = CompanyAccount
fields = "__all__"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['company_name'].widget.attrs.update({'class': 'content-input'})
self.fields['company_name_kana'].widget.attrs.update({'class': 'content-input'})
print(request.POST)
<QueryDict: {'csrfmiddlewaretoken': ['6zrwt68PNiJKrgZcKanDcVJkqAtogbQbNk2wHwjOzg7ybfq3Lyei9ZqdbmAJcYrV'], 'company_name': ['TESTING'], ....etc
This part here, it does print SAVE on my terminal but it does not save to the database.
I got it. Created a new Form for update then excluded the Account.
class CompanyAccountUpdateForm(forms.ModelForm):
class Meta:
model = CompanyAccount
fields = "__all__"
exclude = ('account',)
Then used the new Form to my update view
There are many answers concerning this topic. None of them which I read, helped me to find a way out.
Situation: I've a contact model and I wish to assign an email address. They reside in a table with a foreign key.
Now, I'd like to add a button: "add e-mail" which opens a form. As I am in the detail view of the contact, I'd like to hand the id over into the form to either pre- or post-populate the email object (fk to contact) with the contact pk.
The easiest way would from my point of view be to just add the pk to the url and use it in "form_valid".
The view:
class EmailCreateView(LoginRequiredMixin, CreateView):
model = EmailAddress
fields = ('scope', 'email',) # 'contact',
template_name = 'contacts/contact_form.html'
# initial = {'email': 'test'}
def form_valid(self, form):
form.instance.create_by = self.request.user.username
form.instance.update_by = self.request.user.username
# form.instance.contact = Contact.objects.get(self.kwargs['contact_pk']) <- not working
return super().form_valid(form)
The urlpattern:
path('email/add/', EmailCreateView.as_view(), name='add_email'),
The model:
class EmailAddress(CommonInfo):
scope = models.CharField('Bereich', max_length=2, choices=SCOPE_TYPES)
contact = models.ForeignKey(Contact, verbose_name='Kontakt', on_delete=models.CASCADE)
email = models.EmailField('E-Mail')
def __str__(self):
return '{}'.format(self.email)
def get_absolute_url(self):
return reverse('contacts:detail', kwargs={'pk': self.contact_id})
I really have no idea what I'm missing. As this is a standard case in DB development, it ought to be easy. I'm almost certain that I've overlooked something simple. But whenever I try to pass a url parameter over, I run into a NoReverseMatch error.
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.create_by = self.request.user.username
self.object.update_by = self.request.user.username
self.object.save()
return super().form_valid(form)
I'm using Django-Profiles with Django 1.4, and I need a way to unsubscribe a user, so they can stop getting emails.
One of the fields in my UserProfile model is user_type, and I have a USER_TYPES list of choices. To keep users in the system, even if they unsubscribe, I decided to have one of the USER_TYPES be InactiveClient, and I'd include a checkbox like so:
Models.py:
USER_TYPES = (
('Editor', 'Editor'),
('Reporter', 'Reporter'),
('Client', 'Client'),
('InactiveClient', 'InactiveClient'),
('InactiveReporter', 'InactiveReporter'),
)
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
user_type = models.CharField(max_length=25, choices=USER_TYPES, default='Client')
... etc.
forms.py
class UnsubscribeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UnsubscribeForm, self).__init__(*args, **kwargs)
try:
self.initial['email'] = self.instance.user.email
self.initial['first_name'] = self.instance.user.first_name
self.initial['last_name'] = self.instance.user.last_name
except User.DoesNotExist:
pass
email = forms.EmailField(label='Primary Email')
first_name = forms.CharField(label='Editor first name')
last_name = forms.CharField(label='Editor last name')
unsubscribe = forms.BooleanField(label='Unsubscribe from NNS Emails')
class Meta:
model = UserProfile
fields = ['first_name','last_name','email','unsubscribe']
def save(self, *args, **kwargs):
u = self.instance.user
u.email = self.cleaned_data['email']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
if self.unsubscribe:
u.get_profile().user_type = 'InactiveClient'
u.save()
client = super(UnsubscribeForm, self).save(*args,**kwargs)
return client
Edit: I've added additional code context. if self.unsubscribe: is in save() override. Should that be somewhere else? Thank you.
Edit2: I've tried changing UnsubscribeForm in several ways. Now I get a 404, No User matches the given query. But the view function being called works for other forms, so I'm not sure why?
urls.py
urlpatterns = patterns('',
url('^client/edit', 'profiles.views.edit_profile',
{
'form_class': ClientForm,
'success_url': '/profiles/client/edit/',
},
name='edit_client_profile'),
url('^unsubscribe', 'profiles.views.edit_profile',
{
'form_class': UnsubscribeForm,
'success_url': '/profiles/client/edit/',
},
name='unsubscribe'),
)
These two urls are calling the same view, just using a different form_class.
Edit3: So I don't know why, but when I removed the trailing slash from the unsubscribe url, the form finally loads. But when I submit the form, I still get an error: 'UnsubscribeForm' object has no attribute 'unsubscribe' If anyone could help me understand why a trailing slash would cause the 404 error (No User matches the given query) I wouldn't mind knowing. But as of now, the form loads, but doesn't submit, and the trace ends on this line of my form:
if self.unsubscribe:
Answering my own question again. On ModelForms, you can add form elements that don't exist in the model, and access the value of those fields by accessing self.cleaned_data['form_element_name'] in the save method.
This is what my save method looks like:
def save(self, *args, **kwargs):
u = self.instance.user
p = self.instance.user.get_profile()
u.email = self.cleaned_data['email']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
if self.cleaned_data['unsubscribe']:
p.user_type = 'InactiveClient'
u.save()
p.save()
client = super(UnsubscribeForm, self).save(*args,**kwargs)
return client
The problem
I'm trying to modify the class-based view 'CreateView' to handle a formset instead of a form.
When client does a GET request, the formset is displayed to the client correctly.
The problem is when the client submit the form with a POST.
When Django recieve POST, it lands in form_invalid() and the form.errors say 'this field is required' for the length and name field.
class Service(models.Model):
TIME_CHOICES = (
(15, '15 minutes'),
(30, '30 minutes'),
)
length = models.FloatField(choices=TIME_CHOICES,max_length=6)
name = models.CharField(max_length=40)
class ServiceForm(ModelForm):
class Meta:
model = Service
ServiceFormSet = modelformset_factory(Service,form=ServiceForm)
class ServiceEditView(CreateView):
template_name = "service_formset.html"
model = Service
form_class = ServiceForm
success_url = 'works/'
def form_valid(self, form):
context = self.get_context_data()
formset = context['formset']
if formset.is_valid():
self.object = form.save()
return HttpResponseRedirect('works/')
else:
return HttpResponseRedirect('doesnt-work/')
def form_invalid(self, form):
print form.errors
return HttpResponseRedirect('doesnt-work/')
def get_context_data(self, **kwargs):
context = super(ServiceEditView, self).get_context_data(**kwargs)
if self.request.POST:
context['formset'] = ServiceFormSet(self.request.POST)
else:
context['formset'] = ServiceFormSet(queryset=Service.objects.filter(user__exact=self.request.user.id))
return context
My question is
How can I use a createview to handle a formset?
What am I missing to get it do validate correctly?
The tutorial I've taken most of the bits from so far http://haineault.com/blog/155/
In short, what I've done so far
Since the form.errors variable say each field is required, I think it expects a regular form not a formset -> I'm missing some option that tell the CreateView it's a formset.
I've also tried the solution suggested here: http://www.kevinbrolly.com/.
class BaseServiceFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseServiceFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
But it didnt make any difference.
Solution
pip install django-extra-views
And in view.py:
from extra_views import FormSetView
class ItemFormSetView(ModelFormSetView):
model = Service
template_name = 'service_formset.html'
There's a discussion about getting this into Django core, but the discussions seems to have stagnated.
https://code.djangoproject.com/ticket/16256
Where I found the solution
At this repository https://github.com/AndrewIngram/django-extra-views
there's a view called ModelFormSetView, which does exactly what I needed.
It's a class-based view, that does the same as CreateView, but for formsets.
Django go into form_invalid() and the form.errors say 'this field is required' for the length and name field.
This is normal and due to the required field paramatere:
By default, each Field class assumes the value is required, so if you
pass an empty value -- either None or the empty string ("") -- then
clean() will raise a ValidationError exception:
If you want to inverse that, you can set required=False:
class Service(models.Model):
TIME_CHOICES = (
(15, '15 minutes'),
(30, '30 minutes'),
)
length = models.FloatField(choices=TIME_CHOICES,max_length=6, required=False)
name = models.CharField(max_length=40, required=False)
What am I missing to get it do validate correctly
Did you try to post a form with name and length values ?
I do apologise for all the questions I'm posting today, but I'm at my wits end on this one.
I'm trying to make a Q&A thing for a video site, and I'm trying to get the question to submit via AJAX.
Question model:
class Question(models.Model):
user = models.ForeignKey(User, editable=False)
video = models.ForeignKey(Video, editable=False)
section = models.ForeignKey(Section, editable=False)
title = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
ModelForm:
class QuestionForm(ModelForm):
def __init__(self, video, *args, **kwargs):
super(QuestionForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['section'].queryset = Section.objects.filter(video=video)
class Meta:
model = Question
POST parameters sent by jQuery's AJAX request (the video parameter is added by the Javascript code):
section=6&title=test&description=test&video=1
And finally, here's the view I'm working on to handle the submit:
def question_submit(request):
u = request.user
if u.is_authenticated():
q=QuestionForm(request.POST)
if q.is_valid():
logger.debug("YES!")
else:
logger.debug("NO!")
f=q.save(commit=False)
f.user=u
f.video_id=int(request.POST['video'])
f.save()
return HttpResponse("OK")
else:
return JsonResponse({'failed': 'You are not logged in. Try logging in in a new tab, then re-submit your question.'})
As suggested by the docs, I'm saving with commit=false so that I can modify the object.
I have two problems:
When it reaches q.is_valid(), it throws the error "'QuestionForm' object has no attribute 'cleaned_data'".
If I take out the q.is_valid() bit, f.save() succeeds, but it inserts a blank row into the database.
To anyone who can help, I owe you my sanity.
You aren't passing in video in the view:
forms.py
def __init__(self, video, *args, **kwargs):
views.py
q=QuestionForm(request.POST)
as video is a positional argument, I'd imagine it is interpreting request.POST as the video?
You could change video to a keyword argument:
def __init__(self, video=None, *args, **kwargs):
if video:
...
as mordi metions, you should check if a) it's a valid POST, and b) it's an ajax request
def question_submit(request):
if request.method == "POST" and request.is_ajax():
...
It's look like your request.POST is empty. Are you sure that the data is sent by POST method?, check
if request.method == 'POST:
or use
q=QuestionForm(request.REQUEST)
to get POST/GET data.