Django ClearableFileInput widget doesn't set FileField to None - django

My Django app is saving articles and includes a upload file field that uses the ClearableFileInput widget When I set an article to publish, that's calling a task to send emails.
It seems to work, but there is a bug. When I edit an existing article, and mark the "clear" checkbox, the send email task fails. The error says IOError: [Errno 21] Is a directory: u'/path/to/my/static-dir'
In my edit_article view, I was trying this logic:
def edit_article(request, slug):
article = get_object_or_404(Article, slug=slug)
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES, instance=article)
if form.is_valid():
article = form.save()
msg = "Article updated successfully"
messages.success(request, msg, fail_silently=True)
if article.is_published and article.publish_date <= datetime.datetime.today():
subject = article.title
email_text = article.email_text
story_text = article.text
if article.docfile != None:
attachment = article.docfile
send_published_article.delay(request.user.email,
subject,
email_text,
story_text,
attachment)
else:
send_published_article.delay(request.user.email,
subject,
email_text,
story_text)
msg = "Article published successfully"
messages.success(request, msg, fail_silently=True)
return redirect(article)
else:
form = ArticleForm(instance=article)
return render_to_response('story/article_form.html',
{
'form': form,
'article': article,
},
context_instance=RequestContext(request))
The offending line is probably
if article.docfile != None:
But what should I use to check if the file has been cleared? What does Django's ClearableFileInput widget actually set my filefield's value to, so I can test if there's a file to attach?
Here's my tasks.py:
class EMail(object):
"""
A wrapper around Django's EmailMultiAlternatives
that renders txt and html templates.
Example Usage:
>>> email = Email(to='oz#example.com', subject='A great non-spammy email!')
>>> ctx = {'username': 'Oz Katz'}
>>> email.text('templates/email.txt', ctx)
>>> email.html('templates/email.html', ctx) # Optional
>>> email.send()
>>>
"""
def __init__(self, subject, to, cc, bcc):
self.subject = subject
self.to = to
self.cc = cc
self.bcc = bcc
self._html = None
self._text = None
self._attachment = None
def _render(self, template, context):
return render_to_string(template, context)
def html(self, template, context):
self._html = self._render(template, context)
def text(self, template, context):
self._text = self._render(template, context)
def add_attachment(self, attachment):
self._attachment = default_storage.open(attachment.name, 'r')
def send(self, from_addr=None, fail_silently=False):
if isinstance(self.to, basestring):
self.to = [self.to]
if not from_addr:
from_addr = getattr(settings, 'DEFAULT_FROM_EMAIL')
msg = EmailMultiAlternatives(
self.subject,
self._text,
from_addr,
to=self.to,
cc=self.cc,
bcc=self.bcc
)
if self._html:
msg.attach_alternative(self._html, 'text/html')
if self._attachment:
msg.attach(self._attachment.name, self._attachment.read())
msg.send()
#task(name='send-email')
def send_published_article(sender, subject, email_text, story_text, attachment=None):
"""
Task for emailing published articles.
Runs when an article is saved and is_published==True
"""
recipients = []
reporters = []
for profile in UserProfile.objects.all():
if profile.user_type == 'Client':
recipients.append(profile.user.email)
if profile.user_type == 'Reporter':
reporters.append(profile.user.email)
email = EMail(subject, to='nns.aroberts#gmail.com', cc=reporters, bcc=recipients)
ctx = {'story_text': story_text, 'email_text': email_text}
email.text('../templates/templated_email/emaila.txt', ctx)
email.html('../templates/templated_email/emaila.html', ctx) # Optional
if attachment != None:
email.add_attachment(attachment) # Optional
email.send()

I should just withdraw this question, the answer was me confusing None with Empty.
I changed
if article.docfile != None:
to
if article.docfile:
and all was right with the world.

Related

Django post-form cannot validate when using Form with additional inputs

I have a form containing af MultipleChoiceField where the choices are created dynamic based on the given user
class UpdateForm(forms.Form):
def __init__(self,names,*args,**kwargs):
super(UpdateForm,self).__init__(*args,**kwargs)
self.fields["list_names"] = forms.MultipleChoiceField(choices = zip(names,names),widget=forms.CheckboxSelectMultiple,label="Pick some names")
add_new = forms.BooleanField(initial=True, label="Add new names?",required=False)
delete_missing = forms.BooleanField(label = "Delete names?",required=False)
and it works fine as GET-request, the issues arrives with the post-request:
My view is the following:
def update(request):
user = request.user
list_names = MyModel.objects.filter(user=user).all().values_list("nick_name",flat=True).distinct()
form = UpdateWishlistForm(names =list_names)
if request.method == "POST":
post_form = UpdateForm(request.POST)
if post_form.is_valid():
list_names = post_form.cleaned_data["list_names"]
add_new = post_form.cleaned_data["add_new"]
delete_missing = post_form.cleaned_data["delete_missing"]
messages.success(request, "Success")
context = {
"form":form,
}
redirect("home")
else:
#invalid post_form
messages.error(request, "Error")
context = {
"form":form,
}
return render(request, "discounttracker/update.html")
else: #Get request
context = {
"form":form,
}
return render(request, "myapp/update.html",context=context)
The post_form = UpdateForm(request.POST) does not validate and the post_form.errors is empty.
It does contain data though (before calling post_form.is_valid())
print(post_form)
# UpdateForm: <UpdateForm bound=False, valid=Unknown, fields=(add_new;delete_missing;list_names)>
print(request.POST.dict())
#<QueryDict: {'csrfmiddlewaretoken': ['...'], 'add_new': ['on'], 'list_names': ['test_name_1']}>
but I notice it is not bound, thus not validating. But I cannot understand why it's not "binding" when parsing request.POST?
In the POST request, you need to pass the names as well, so:
list_names = MyModel.objects.filter(user=user).values_list("nick_name",flat=True).distinct()
form = UpdateWishlistForm(names=list_names)
if request.method == 'POST':
post_form = UpdateForm(names=list_names, data=request.POST)
# …
# …
But I would advise to work with a ModelMultipleChoiceField [Django-doc] and thus pass a queryset. Since the nick names apparently can contain duplicates, it might be better to make a Nickname model, and use ForeignKeys to that model.

why does Django returns BadHeaderError when adding a new line in my 'Contact' page's 'message' field

Everything works except when I add a new line via 'enter' in the "Message" field. It goes through if I don't add new lines in the message textfield.
What am i missing here? Tried to solve this problem for 2 days, nothing similar on google.
I feel like there could be the problem of my views.py config:
def success(request):
return render(request, 'home/success.html')
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# send email code goes here
sender_name = form.cleaned_data['name']
sender_email = form.cleaned_data['email']
sender_phone = form.cleaned_data['phone']
sender_message = form.cleaned_data['message']
subject = "Enquiry: {0}".format(sender_message[:50])
message = "New message from {0}\n phone number: {1}\n email: {2}\n\n{3}".format(sender_name, sender_phone, sender_email, sender_message)
recipients = ['john.smith#gmail.com']
sender = "{0}<{1}>".format(sender_name, sender_email)
try:
send_mail(subject, message, sender, recipients, fail_silently=False)
except BadHeaderError:
return HttpResponse('Invalid header found')
return HttpResponseRedirect('success')
else:
form = ContactForm()
return render(request, 'home/contact.html', {'form': form})
Any ideas?
As described in the documentation, a BadHeaderError is raised to "protect against header injection by forbidding newlines in header values".
Since you're copying part of sender_message directly into the subject header, you may be including newlines as well. The simple solution is to strip them out first.
sender_message = form.cleaned_data['message']
clean_message = sender_message.replace('\n', '').replace('\r', '')
subject = "Enquiry: {0}".format(clean_message[:50])

Django: window.onpopstate unexpected behavior with Chrome 79

EDIT
finally I use window.onpopstate event to prevent user go back
it works with Firefox but not with Chrome 79 (and other browsers?)
I have read many posts and official documentation that seems to say that this 'bug' was fixed after Chrome 34... but doesn't seems to be
to be honest, event reading the documentation, I do not really understand this event and the behavior so far...
How can I resolve this?
I know this topic has been already discuss but none of solutions resolve my problem.
I have 3 forms that update the same models.
I would like to manage/prevent user going back using back arrow.
I tried using window.location, window.onhashchange = function () {}, flag session variable but nothing works and I am lost
When user click on back arrow from 2nd form to go back to 1st form, I would like either the event is blocked (best), either user is redirected to home page
I notice that click on back arrow on the 2nd form do not call index view, reason why 'flag strategy' do not work
#login_required
#permission_required('unblind.can_unblind')
def index(request):
if request.method == "POST":
form = AveugleProcedureForm(request, data=request.POST or None)
if form.is_valid():
unblind = form.save()
# database treatment
return redirect('unblind:edit', pk = unblind.pk)
else:
form = AveugleProcedureForm(request)
return render(request, 'unblind/index.html', {'form': form})
#login_required
#permission_required('unblind.can_unblind')
def edit(request, pk):
if request.method == "POST":
form = AveugleForm(request, data=request.POST or None)
if form.is_valid():
# unblind = form.save()
# database treatment
unblind = Aveugle.objects.get(unb_ide = pk)
unblind.unb_pro = form.cleaned_data['unb_pro']
unblind.unb_nom_dem = form.cleaned_data['unb_nom_dem']
unblind.unb_nom_rea = form.cleaned_data['unb_nom_rea']
unblind.unb_dat = form.cleaned_data['unb_dat']
unblind.pat = form.cleaned_data['pat']
unblind.unb_num = form.cleaned_data['unb_num']
unblind.unb_mot = form.cleaned_data['unb_mot']
unblind.save()
# contrôler le couple numéro patient/boite de médicament
# récupération du traitement
treatment = Medicament.objects.get(med_num = unblind.unb_num).med_dru
# envoie d'un email de confirmation
if treatment == 1:
unblind_treatment = _('ASPIRIN')
else:
unblind_treatment = _('PLACEBO')
email(unblind.pat,unblind.unb_num,unblind_treatment,unblind.unb_mot)
return redirect('unblind:result', pk = unblind.pk)
else:
form = AveugleForm(request)
return render(request, 'unblind/edit.html', {'form': form,'pk': pk})
#login_required
#permission_required('unblind.can_unblind')
def result(request, pk):
# Récupération de l'enregistrement
unblind = Aveugle.objects.get(unb_ide = pk)
treatment = Medicament.objects.get(med_num = unblind.unb_num).med_dru
if request.method == "POST":
form = AveugleResultForm(request, data=request.POST or None)
if form.is_valid():
# database treatment
unblind.unb_rec = form.cleaned_data['unb_rec']
# unblind.unb_com = form.cleaned_data['unb_com']
unblind.unb_log_dat = datetime.now()
unblind.unb_log = request.user.username
unblind.unb_log_sit = request.session['selected_site']
unblind.save()
return redirect('home')
else:
form = AveugleResultForm(request)
return render(request, 'unblind/result.html', {'form': form,'unblind':unblind,'treatment':treatment})

Is it possible to attach a pdf generated by a view to an email message in django?

I'm using this third party app for pdf generation.
In my view I've this code for creating the Pdf:
class artwork_catalog(PDFTemplateView):
template_name = "someapp/sometemplate.html"
def get_context_data(self, **kwargs):
return super(artwork_catalog, self).get_context_data(
pagesize="A4",
title="My Title" + datetime.datetime.now().strftime('%m/%Y'),
artworks=ArtWork.objects.filter(public=True).order_by('year'),
**kwargs
)
I've the following Url for my view:
url(
r"^mypdf.pdf$",
artwork_catalog.as_view()
),
So I can access my Pdf at localhost:8000/mypdf.pdf.
Now I want to send this Pdf as attachment per email. I created a view but I can't bring it to work:
def order_catalog(request):
if request.method == 'POST':
form = OrderCatalogForm(request.POST)
if form.is_valid():
subject = 'Test'
from_email = 'Test#Test.com'
message = 'TESTTESTTESTTEST'
recipient = form.cleaned_data['email']
pdf = artwork_catalog # here I'd like to insert my Pdf
try:
mail = EmailMessage(subject, message, from_email, [recipient])
mail.attach("MyPdf.pdf", pdf, "application/pdf")
mail.send()
except:
return HttpResponse('Error')
return redirect('/somewhere/')
else:
form = OrderCatalogForm()
return render(request, 'someapp/order_catalog.html', {'form': form})
Any hints? best regards Manuel
If you are still interested by a solution, the following should work, using easy_pdf.rendering.render_to_pdf:
from easy_pdf.rendering import render_to_pdf
...
pdf = render_to_pdf(
'someapp/sometemplate.html',
{
'pagesize': "A4",
'title': "My Title" + datetime.datetime.now().strftime('%m/%Y'),
'artworks' ArtWork.objects.filter(public=True).order_by('year')
}
)

Django form efficiency issue

I have inherited some code that was handled by multiple developers, and as such, there is no standard for the product. I am having significant performance issues, and am remedying a lot of them, but don't know how to break this one down. When the user submits on a page that pre-populates with data, and allows the user to select a date for the activity, they can submit, and anything with a date goes away for them. The problem is within the views.py. When the user submits, the following code runs, and hangs up at formset = formset(request.POST). I'm assuming that there are multiple db calls happening, but am not seeing them when I print to terminal. Below is the entirety of the submission process.
STATES = ['Routed','Reconciled']
formset = None
form = StudyOnlyForm()
study_pk = request.GET.get('study', '0')
if study_pk == '' or study_pk == '0':
study_pk = 0
# child transactions that are 'Recoginzed',
# reportable with a 'Routed' parent
qs = Transaction.objects.filter(contract__reportable=True,
cleared = False,
contract__study__pk=study_pk,
transaction_status='Recognized',
parent_transaction__isnull=False,
parent_transaction__transaction_status__in=STATES)
#Designed to capture standalone contracts
qs1 = Transaction.objects.filter(contract__reportable=True,
cleared = False,
contract__study__pk=study_pk,
contract__contract_type__name='Standalone',
transaction_status__in=STATES)
#Captures Arms contracts for Milestone payments
parent_list = []
arms_list = []
parent = Transaction.objects.filter(parent_transaction__isnull=True,
contract__reportable=True,
cleared = False,
contract__study__pk=study_pk,
contract__contract_type__name='ARMs',
transaction_status__in=STATES)
children = Transaction.objects.filter(contract__reportable=True,
cleared = False,
contract__study__pk=study_pk,
transaction_status='Recognized',
contract__contract_type__name='ARMs',
parent_transaction__isnull=False,
parent_transaction__transaction_status__in=STATES)
for child_item in children:
parent_list.append(child_item.parent_transaction.pk)
arms_list.append(child_item.pk)
for parent_item in parent:
if parent_item.pk not in parent_list:
arms_list.append(parent_item.pk)
qs3 = Transaction.objects.filter(pk__in=arms_list)
qs4 = qs | qs1 | qs3
qs = qs4.order_by('-pk')
formset = modelformset_factory(Transaction, form=PaidDateForm, extra=0, can_delete=False)
if request.method == "POST":
print 'if request.POST'
print datetime.datetime.now()
formset = formset(request.POST)
print 'created formset'
print datetime.datetime.now()
if formset.is_valid():
print 'formset valid'
print datetime.datetime.now()
updated_transactions = formset.save(commit=False)
print 'updated transactions'
print datetime.datetime.now()
for trans in updated_transactions:
if trans.paid_amount is not None and trans.date_cleared is not None:
trans_to_change = Transaction.objects.get(pk=trans.pk)
trans_to_change.paid_amount = trans.paid_amount
trans_to_change.date_cleared = trans.date_cleared
trans_to_change.paid_currency = trans_to_change.entered_currency
trans_to_change.paid_amount_usd =
Decimal(str(trans_to_change.paid_amount * Decimal(str(trans_to_change.exchange_rate)).quantize(Decimal('0.01')))).quantize(Decimal('0.01'))
trans_to_change.edited_by = request.user
trans_to_change.cleared = True
trans_to_change.save()
if updated_transactions:
messages.add_message(request, messages.INFO, 'The transactions have been updated successfully.')
return HttpResponseRedirect(reverse('track:update_pdate'))
else:
messages.add_message(request, messages.INFO, 'No transactions have been updated.')
return render_to_response(
'track/paid_date_update.html',
{'formset':formset,
'form': form,
'study_pk':study_pk,
},
context_instance=template.RequestContext(request))
else:
formset = formset(queryset=qs)
return render_to_response(
'track/paid_date_update.html',
{'formset':formset,
'form': form,
'study_pk':study_pk,
},
context_instance=template.RequestContext(request))
Forms:
class StudyOnlyForm(forms.Form):
action = forms.CharField(widget=forms.HiddenInput())
#
# JBL - Q4.0 changed this form to pull all studies (previously
# only 'active' studies), which really means all studies
# that are not 'ccc' studies
#
study = forms.ModelChoiceField(required=False,
label='Protocol',
queryset=Study.objects.all().exclude(study_status='ccc'))
def __init__(self, *args, **kwargs):
self.req = True
if 'req' in kwargs:
self.req = kwargs.pop('req')
super(StudyOnlyForm, self).__init__(*args, **kwargs)
print 'StudyOnlyForm() init'
self.fields['study'].required = self.req
class PaidDateForm(forms.ModelForm):
formfield_callback = jquery_datefield
paid_amount = forms.DecimalField(label="Cleared
Amount",max_digits=14,decimal_places=2,required=False)
date_cleared = forms.DateField(label="Cleared Date",widget=JQueryDateWidget(),
input_formats=settings.DATE_INPUT_FORMATS, required=False)
class Meta:
model = Transaction
include = ('date_time_created')
def __init__(self, *args, **kwargs):
super(PaidDateForm, self).__init__(*args, **kwargs)
print 'PaidDateForm init'
for field in self.fields:
if field != 'date_cleared':
self.fields[field].widget = forms.HiddenInput()
self.fields['paid_amount'].widget.attrs['size'] = 12
self.initial['paid_amount'] = '%.2f' % (self.instance.usd_amount)
You restrict the queryset in the GET branch, but not the POST branch. That means that the POST branch creates a formset containing every transaction.
replace your for loop:
for trans in updated_transactions.exclude(paid_amount=None, date_cleared=None).all():
trans_to_change = Transaction.objects.get(pk=trans.pk)
trans_to_change.paid_amount = trans.paid_amount
trans_to_change.date_cleared = trans.date_cleared
trans_to_change.paid_currency = trans_to_change.entered_currency
trans_to_change.paid_amount_usd = Decimal(str(trans_to_change.paid_amount * Decimal(str(trans_to_change.exchange_rate)).quantize(Decimal('0.01')))).quantize(Decimal('0.01'))
trans_to_change.edited_by = request.user
trans_to_change.cleared = True
trans_to_change.save()