Flake8 Fixes
Mostly whitespace fixes Some unicode fixes Fixed up CSV writer. str is not bytes and all.
This commit is contained in:
parent
ecf14b514d
commit
298b162be6
18 changed files with 97 additions and 94 deletions
|
@ -25,13 +25,13 @@ def export_as_csv_action(description=None, fields=None, exclude=None,
|
||||||
excludeset = set(exclude)
|
excludeset = set(exclude)
|
||||||
field_names = field_names - excludeset
|
field_names = field_names - excludeset
|
||||||
response = HttpResponse(content_type="text/csv")
|
response = HttpResponse(content_type="text/csv")
|
||||||
response["Content-Disposition"] = "attachment; filename=%s.csv" % unicode(opts).replace(".", "_")
|
response["Content-Disposition"] = "attachment; filename=%s.csv" % str(opts).replace(".", "_")
|
||||||
writer = csv.writer(response)
|
writer = csv.writer(response)
|
||||||
if header:
|
if header:
|
||||||
writer.writerow(list(field_names))
|
writer.writerow(list(field_names))
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
writer.writerow(
|
writer.writerow(
|
||||||
[unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names])
|
[str(getattr(obj, field)).encode("utf-8", "replace") for field in field_names])
|
||||||
return response
|
return response
|
||||||
if description is None:
|
if description is None:
|
||||||
description = _("Export selected objects as CSV file")
|
description = _("Export selected objects as CSV file")
|
||||||
|
|
|
@ -205,6 +205,7 @@ class ProposalBase(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
reversion.register(ProposalBase)
|
reversion.register(ProposalBase)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,5 +8,5 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
for proposal in ProposalBase.objects.filter(cancelled=0):
|
for proposal in ProposalBase.objects.filter(cancelled=0):
|
||||||
print "Creating assignments for %s" % (proposal.title,)
|
print("Creating assignments for %s" % proposal.title)
|
||||||
ReviewAssignment.create_assignments(proposal)
|
ReviewAssignment.create_assignments(proposal)
|
||||||
|
|
|
@ -22,4 +22,4 @@ class Command(BaseCommand):
|
||||||
content_type__pk=ct.id,
|
content_type__pk=ct.id,
|
||||||
defaults={"name": "Can %s %s" % (action, ps), "content_type": ct}
|
defaults={"name": "Can %s %s" % (action, ps), "content_type": ct}
|
||||||
)
|
)
|
||||||
print perm
|
print(perm)
|
||||||
|
|
|
@ -47,6 +47,8 @@ class Votes(object):
|
||||||
(MINUS_TWO, _("−2 — Serious issues and I will argue to reject this proposal.")),
|
(MINUS_TWO, _("−2 — Serious issues and I will argue to reject this proposal.")),
|
||||||
(ABSTAIN, _("Abstain - I do not want to review this proposal and I do not want to see it again.")),
|
(ABSTAIN, _("Abstain - I do not want to review this proposal and I do not want to see it again.")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
VOTES = Votes()
|
VOTES = Votes()
|
||||||
|
|
||||||
|
|
||||||
|
@ -395,4 +397,6 @@ def accepted_proposal(sender, instance=None, **kwargs):
|
||||||
promote_proposal(instance.proposal)
|
promote_proposal(instance.proposal)
|
||||||
else:
|
else:
|
||||||
unpromote_proposal(instance.proposal)
|
unpromote_proposal(instance.proposal)
|
||||||
|
|
||||||
|
|
||||||
post_save.connect(accepted_proposal, sender=ProposalResult)
|
post_save.connect(accepted_proposal, sender=ProposalResult)
|
||||||
|
|
|
@ -78,23 +78,23 @@ REVIEW_STATUS_FILTERS = {
|
||||||
# proposals with at least VOTE_THRESHOLD reviews and at least one +2 and no -2s, sorted by
|
# proposals with at least VOTE_THRESHOLD reviews and at least one +2 and no -2s, sorted by
|
||||||
# the 'score'
|
# the 'score'
|
||||||
POSITIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0,
|
POSITIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0,
|
||||||
result__minus_two=0).order_by("-result__score"),
|
result__minus_two=0).order_by("-result__score"),
|
||||||
# proposals with at least VOTE_THRESHOLD reviews and at least one -2 and no +2s, reverse
|
# proposals with at least VOTE_THRESHOLD reviews and at least one -2 and no +2s, reverse
|
||||||
# sorted by the 'score'
|
# sorted by the 'score'
|
||||||
NEGATIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two__gt=0,
|
NEGATIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two__gt=0,
|
||||||
result__plus_two=0).order_by("result__score"),
|
result__plus_two=0).order_by("result__score"),
|
||||||
# proposals with at least VOTE_THRESHOLD reviews and neither a +2 or a -2, sorted by total
|
# proposals with at least VOTE_THRESHOLD reviews and neither a +2 or a -2, sorted by total
|
||||||
# votes (lowest first)
|
# votes (lowest first)
|
||||||
INDIFFERENT: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two=0,
|
INDIFFERENT: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two=0,
|
||||||
result__plus_two=0).order_by("result__vote_count"),
|
result__plus_two=0).order_by("result__vote_count"),
|
||||||
# proposals with at least VOTE_THRESHOLD reviews and both a +2 and -2, sorted by total
|
# proposals with at least VOTE_THRESHOLD reviews and both a +2 and -2, sorted by total
|
||||||
# votes (highest first)
|
# votes (highest first)
|
||||||
CONTROVERSIAL: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD,
|
CONTROVERSIAL: lambda qs: qs.filter(
|
||||||
result__plus_two__gt=0, result__minus_two__gt=0)
|
result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0,
|
||||||
.order_by("-result__vote_count"),
|
result__minus_two__gt=0).order_by("-result__vote_count"),
|
||||||
# proposals with fewer than VOTE_THRESHOLD reviews
|
# proposals with fewer than VOTE_THRESHOLD reviews
|
||||||
TOO_FEW: lambda qs: qs.filter(result__vote_count__lt=VOTE_THRESHOLD)
|
TOO_FEW: lambda qs: qs.filter(
|
||||||
.order_by("result__vote_count"),
|
result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ def review_all_proposals_csv(request):
|
||||||
|
|
||||||
# The fields from each proposal object to report in the csv
|
# The fields from each proposal object to report in the csv
|
||||||
fields = [
|
fields = [
|
||||||
"id", "proposal_type", "speaker_name","speaker_email", "title",
|
"id", "proposal_type", "speaker_name", "speaker_email", "title",
|
||||||
"submitted", "other_speakers", "speaker_travel",
|
"submitted", "other_speakers", "speaker_travel",
|
||||||
"speaker_accommodation", "cancelled", "status", "score", "total_votes",
|
"speaker_accommodation", "cancelled", "status", "score", "total_votes",
|
||||||
"minus_two", "minus_one", "plus_one", "plus_two",
|
"minus_two", "minus_one", "plus_one", "plus_two",
|
||||||
|
@ -189,11 +189,6 @@ def review_all_proposals_csv(request):
|
||||||
|
|
||||||
csv_line = [getattr(proposal, field) for field in fields]
|
csv_line = [getattr(proposal, field) for field in fields]
|
||||||
|
|
||||||
# Enusre that unicode items are handled properly.
|
|
||||||
for i, item in enumerate(csv_line):
|
|
||||||
if isinstance(item, unicode):
|
|
||||||
csv_line[i] = item.encode("utf8")
|
|
||||||
|
|
||||||
writer.writerow(csv_line)
|
writer.writerow(csv_line)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -229,7 +224,7 @@ def review_random_proposal(request, section_slug):
|
||||||
# Select a proposal with less than the median number of total votes
|
# Select a proposal with less than the median number of total votes
|
||||||
proposals = proposals_generator(request, queryset, check_speaker=False)
|
proposals = proposals_generator(request, queryset, check_speaker=False)
|
||||||
proposals = list(proposals)
|
proposals = list(proposals)
|
||||||
proposals.sort(key = lambda proposal: proposal.total_votes)
|
proposals.sort(key=lambda proposal: proposal.total_votes)
|
||||||
# The first half is the median or less.
|
# The first half is the median or less.
|
||||||
# The +1 means we round _up_.
|
# The +1 means we round _up_.
|
||||||
proposals = proposals[:(len(proposals) + 1) / 2]
|
proposals = proposals[:(len(proposals) + 1) / 2]
|
||||||
|
@ -319,7 +314,7 @@ def review_admin(request, section_slug):
|
||||||
yield user
|
yield user
|
||||||
|
|
||||||
reviewers_sorted = list(reviewers())
|
reviewers_sorted = list(reviewers())
|
||||||
reviewers_sorted.sort(key= lambda reviewer: 0 - reviewer.total_votes)
|
reviewers_sorted.sort(key=lambda reviewer: 0 - reviewer.total_votes)
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
"section_slug": section_slug,
|
"section_slug": section_slug,
|
||||||
|
|
|
@ -28,4 +28,4 @@ def create_slot(section_slug, date, kind, start, end, rooms):
|
||||||
slot_room.slot = slot
|
slot_room.slot = slot
|
||||||
slot_room.room = room
|
slot_room.room = room
|
||||||
slot_room.save(force_insert=True)
|
slot_room.save(force_insert=True)
|
||||||
print "created {} [start={}; end={}]".format(slot.kind.label, slot.start, slot.end)
|
print("created {} [start={}; end={}]".format(slot.kind.label, slot.start, slot.end))
|
||||||
|
|
|
@ -21,6 +21,7 @@ from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session
|
||||||
from symposion.schedule.timetable import TimeTable
|
from symposion.schedule.timetable import TimeTable
|
||||||
from symposion.conference.models import Conference
|
from symposion.conference.models import Conference
|
||||||
|
|
||||||
|
|
||||||
def fetch_schedule(slug):
|
def fetch_schedule(slug):
|
||||||
qs = Schedule.objects.all()
|
qs = Schedule.objects.all()
|
||||||
|
|
||||||
|
@ -265,11 +266,13 @@ def schedule_json(request):
|
||||||
content_type="application/json"
|
content_type="application/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EventFeed(ICalFeed):
|
class EventFeed(ICalFeed):
|
||||||
|
|
||||||
product_id = '-//linux.conf.au/schedule//EN'
|
product_id = '-//linux.conf.au/schedule//EN'
|
||||||
timezone = settings.TIME_ZONE
|
timezone = settings.TIME_ZONE
|
||||||
filename = 'conference.ics'
|
filename = 'conference.ics'
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
return Conference.objects.all().first().title
|
return Conference.objects.all().first().title
|
||||||
|
|
||||||
|
@ -283,7 +286,7 @@ class EventFeed(ICalFeed):
|
||||||
|
|
||||||
def item_title(self, item):
|
def item_title(self, item):
|
||||||
if hasattr(item.content, 'proposal'):
|
if hasattr(item.content, 'proposal'):
|
||||||
title = item.content.title
|
title = item.content.title
|
||||||
else:
|
else:
|
||||||
title = item.kind if item.kind else "Slot"
|
title = item.kind if item.kind else "Slot"
|
||||||
return title
|
return title
|
||||||
|
@ -305,18 +308,18 @@ class EventFeed(ICalFeed):
|
||||||
def item_location(self, item):
|
def item_location(self, item):
|
||||||
return ", ".join(room["name"] for room in item.rooms.values())
|
return ", ".join(room["name"] for room in item.rooms.values())
|
||||||
|
|
||||||
def item_link(self, item):
|
def item_link(self, item) -> str:
|
||||||
if hasattr(item.content, 'proposal'):
|
if hasattr(item.content, 'proposal'):
|
||||||
return 'http://%s%s' % (
|
return (
|
||||||
Site.objects.get_current().domain,
|
'http://%s%s' % (Site.objects.get_current().domain,
|
||||||
reverse('schedule_presentation_detail', args=[item.content.pk])
|
reverse('schedule_presentation_detail', args=[item.content.pk])))
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return 'http://%s' % Site.objects.get_current().domain
|
return 'http://%s' % Site.objects.get_current().domain
|
||||||
|
|
||||||
def item_guid(self, item):
|
def item_guid(self, item):
|
||||||
return '%d@%s' % (item.pk, Site.objects.get_current().domain)
|
return '%d@%s' % (item.pk, Site.objects.get_current().domain)
|
||||||
|
|
||||||
|
|
||||||
def session_list(request):
|
def session_list(request):
|
||||||
sessions = Session.objects.all().order_by('pk')
|
sessions = Session.objects.all().order_by('pk')
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,12 @@ from symposion.speakers.models import Speaker
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb"))
|
with open(os.path.join(os.getcwd(), "speakers.csv"), "w") as csv_file:
|
||||||
csv_file.writerow(["Name", "Bio"])
|
csv_writer = csv.writer(csv_file)
|
||||||
|
csv_writer.writerow(["Name", "Bio"])
|
||||||
|
|
||||||
for speaker in Speaker.objects.all():
|
for speaker in Speaker.objects.all():
|
||||||
csv_file.writerow([
|
csv_writer.writerow([
|
||||||
speaker.name.encode("utf-8"),
|
speaker.name,
|
||||||
speaker.biography.encode("utf-8"),
|
speaker.biography,
|
||||||
])
|
])
|
||||||
|
|
|
@ -66,9 +66,9 @@ class Speaker(models.Model):
|
||||||
accessibility = models.TextField(
|
accessibility = models.TextField(
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Please describe any special accessibility requirements "
|
help_text=_("Please describe any special accessibility requirements "
|
||||||
"that you may have. Edit using "
|
"that you may have. Edit using "
|
||||||
"<a href='http://warpedvisions.org/projects/"
|
"<a href='http://warpedvisions.org/projects/"
|
||||||
"markdown-cheat-sheet/' target='_blank'>Markdown</a>."),
|
"markdown-cheat-sheet/' target='_blank'>Markdown</a>."),
|
||||||
verbose_name=_("Accessibility requirements"))
|
verbose_name=_("Accessibility requirements"))
|
||||||
accessibility_html = models.TextField(blank=True)
|
accessibility_html = models.TextField(blank=True)
|
||||||
travel_assistance = models.BooleanField(
|
travel_assistance = models.BooleanField(
|
||||||
|
|
|
@ -30,48 +30,48 @@ class Command(BaseCommand):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
csv_file = csv.writer(
|
with open(os.path.join(os.getcwd(), "build", "sponsors.csv"), "w") as csv_file:
|
||||||
open(os.path.join(os.getcwd(), "build", "sponsors.csv"), "wb")
|
csv_writer = csv.writer(csv_file)
|
||||||
)
|
csv_writer.writerow(["Name", "URL", "Level", "Description"])
|
||||||
csv_file.writerow(["Name", "URL", "Level", "Description"])
|
|
||||||
|
|
||||||
for sponsor in Sponsor.objects.all():
|
for sponsor in Sponsor.objects.all():
|
||||||
path = os.path.join(os.getcwd(), "build", slugify(sponsor.name))
|
path = os.path.join(os.getcwd(), "build", slugify(sponsor.name))
|
||||||
try:
|
try:
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"name": sponsor.name,
|
"name": sponsor.name,
|
||||||
"url": sponsor.external_url,
|
"url": sponsor.external_url,
|
||||||
"level": sponsor.level.name,
|
"level": sponsor.level.name,
|
||||||
"description": "",
|
"description": "",
|
||||||
}
|
}
|
||||||
for sponsor_benefit in sponsor.sponsor_benefits.all():
|
|
||||||
if sponsor_benefit.benefit_id == 2:
|
|
||||||
data["description"] = sponsor_benefit.text
|
|
||||||
if sponsor_benefit.benefit_id == 1:
|
|
||||||
if sponsor_benefit.upload:
|
|
||||||
data["ad"] = sponsor_benefit.upload.path
|
|
||||||
if sponsor_benefit.benefit_id == 7:
|
|
||||||
if sponsor_benefit.upload:
|
|
||||||
data["logo"] = sponsor_benefit.upload.path
|
|
||||||
|
|
||||||
if "ad" in data:
|
for sponsor_benefit in sponsor.sponsor_benefits.all():
|
||||||
ad_path = data.pop("ad")
|
if sponsor_benefit.benefit_id == 2:
|
||||||
shutil.copy(ad_path, path)
|
data["description"] = sponsor_benefit.text
|
||||||
if "logo" in data:
|
if sponsor_benefit.benefit_id == 1:
|
||||||
logo_path = data.pop("logo")
|
if sponsor_benefit.upload:
|
||||||
shutil.copy(logo_path, path)
|
data["ad"] = sponsor_benefit.upload.path
|
||||||
|
if sponsor_benefit.benefit_id == 7:
|
||||||
|
if sponsor_benefit.upload:
|
||||||
|
data["logo"] = sponsor_benefit.upload.path
|
||||||
|
|
||||||
csv_file.writerow([
|
if "ad" in data:
|
||||||
data["name"].encode("utf-8"),
|
ad_path = data.pop("ad")
|
||||||
data["url"].encode("utf-8"),
|
shutil.copy(ad_path, path)
|
||||||
data["level"].encode("utf-8"),
|
if "logo" in data:
|
||||||
data["description"].encode("utf-8")
|
logo_path = data.pop("logo")
|
||||||
])
|
shutil.copy(logo_path, path)
|
||||||
|
|
||||||
zipdir(
|
csv_writer.writerow([
|
||||||
os.path.join(os.getcwd(), "build"),
|
data["name"],
|
||||||
os.path.join(os.getcwd(), "sponsors.zip"))
|
data["url"],
|
||||||
|
data["level"],
|
||||||
|
data["description"]
|
||||||
|
])
|
||||||
|
|
||||||
|
zipdir(
|
||||||
|
os.path.join(os.getcwd(), "build"),
|
||||||
|
os.path.join(os.getcwd(), "sponsors.zip"))
|
||||||
|
|
|
@ -19,7 +19,7 @@ class Command(BaseCommand):
|
||||||
sponsor=sponsor, benefit=benefit_level.benefit)
|
sponsor=sponsor, benefit=benefit_level.benefit)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
print "created", sponsor_benefit, "for", sponsor
|
print("created %s for %s" % (sponsor_benefit, sponsor))
|
||||||
|
|
||||||
# and set to default limits for this level.
|
# and set to default limits for this level.
|
||||||
sponsor_benefit.max_words = benefit_level.max_words
|
sponsor_benefit.max_words = benefit_level.max_words
|
||||||
|
|
|
@ -203,12 +203,16 @@ class Sponsor(models.Model):
|
||||||
def _store_initial_level(sender, instance, **kwargs):
|
def _store_initial_level(sender, instance, **kwargs):
|
||||||
if instance:
|
if instance:
|
||||||
instance._initial_level_id = instance.level_id
|
instance._initial_level_id = instance.level_id
|
||||||
|
|
||||||
|
|
||||||
post_init.connect(_store_initial_level, sender=Sponsor)
|
post_init.connect(_store_initial_level, sender=Sponsor)
|
||||||
|
|
||||||
|
|
||||||
def _check_level_change(sender, instance, created, **kwargs):
|
def _check_level_change(sender, instance, created, **kwargs):
|
||||||
if instance and (created or instance.level_id != instance._initial_level_id):
|
if instance and (created or instance.level_id != instance._initial_level_id):
|
||||||
instance.reset_benefits()
|
instance.reset_benefits()
|
||||||
|
|
||||||
|
|
||||||
post_save.connect(_check_level_change, sender=Sponsor)
|
post_save.connect(_check_level_change, sender=Sponsor)
|
||||||
|
|
||||||
|
|
||||||
|
@ -330,4 +334,6 @@ def _denorm_weblogo(sender, instance, created, **kwargs):
|
||||||
sponsor = instance.sponsor
|
sponsor = instance.sponsor
|
||||||
sponsor.sponsor_logo = instance
|
sponsor.sponsor_logo = instance
|
||||||
sponsor.save()
|
sponsor.save()
|
||||||
|
|
||||||
|
|
||||||
post_save.connect(_denorm_weblogo, sender=SponsorBenefit)
|
post_save.connect(_denorm_weblogo, sender=SponsorBenefit)
|
||||||
|
|
|
@ -13,4 +13,5 @@ class MembershipAdmin(VersionAdmin):
|
||||||
list_filter = ["team"]
|
list_filter = ["team"]
|
||||||
search_fields = ["user__username"]
|
search_fields = ["user__username"]
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Membership, MembershipAdmin)
|
admin.site.register(Membership, MembershipAdmin)
|
||||||
|
|
|
@ -69,6 +69,7 @@ class Team(models.Model):
|
||||||
verbose_name = _('Team')
|
verbose_name = _('Team')
|
||||||
verbose_name_plural = _('Teams')
|
verbose_name_plural = _('Teams')
|
||||||
|
|
||||||
|
|
||||||
MEMBERSHIP_STATE_CHOICES = [
|
MEMBERSHIP_STATE_CHOICES = [
|
||||||
("applied", _("applied")),
|
("applied", _("applied")),
|
||||||
("invited", _("invited")),
|
("invited", _("invited")),
|
||||||
|
@ -93,4 +94,5 @@ class Membership(models.Model):
|
||||||
verbose_name = _("Membership")
|
verbose_name = _("Membership")
|
||||||
verbose_name_plural = _("Memberships")
|
verbose_name_plural = _("Memberships")
|
||||||
|
|
||||||
|
|
||||||
reversion.register(Membership)
|
reversion.register(Membership)
|
||||||
|
|
|
@ -53,16 +53,6 @@ def __send_email__(template_prefix, to, kind, **kwargs):
|
||||||
|
|
||||||
from_email = settings.DEFAULT_FROM_EMAIL
|
from_email = settings.DEFAULT_FROM_EMAIL
|
||||||
|
|
||||||
try:
|
|
||||||
bcc_email = settings.ENVELOPE_BCC_LIST
|
|
||||||
except AttributeError:
|
|
||||||
bcc_email = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
bcc_email = settings.ENVELOPE_BCC_LIST
|
|
||||||
except AttributeError:
|
|
||||||
bcc_email = None
|
|
||||||
|
|
||||||
email = EmailMultiAlternatives(subject, message_plaintext, from_email, to)
|
email = EmailMultiAlternatives(subject, message_plaintext, from_email, to)
|
||||||
email.attach_alternative(message_html, "text/html")
|
email.attach_alternative(message_html, "text/html")
|
||||||
email.send()
|
email.send()
|
||||||
|
|
Loading…
Reference in a new issue