def remind_empty_submissions(self, whatstr, conference): # Get all sessions with empty abstract (they forgot to hit save), if they have not been touched in # 3 days (this will also make the reminder show up every 3 days, and not every day, since we touch # the lastmodified timestemp when a reminder is sent). for sess in conference.conferencesession_set.filter( abstract='', status=0, lastmodified__lt=timezone.now() - timedelta(days=3)): for spk in sess.speaker.all(): send_conference_mail( conference, spk.email, "Your submission".format(conference), 'confreg/mail/speaker_empty_submission.txt', { 'conference': conference, 'session': sess, }, receivername=spk.name, ) whatstr.write( "Reminded speaker {0} that they have made an empty submission\n" .format(spk.name)) sess.lastmodified = timezone.now() sess.save()
def remind_empty_speakers(self, whatstr, conference): # Get all the speakers with an active submission to this conference # but no bio included, if they have not been touched in 3 days. speakers = Speaker.objects.filter( conferencesession__conference=conference, conferencesession__status=0, lastmodified__lt=timezone.now() - timedelta(days=3), abstract='', ).distinct() for spk in speakers: send_conference_mail( conference, spk.email, "Your submission".format(conference), 'confreg/mail/speaker_empty_profile.txt', { 'conference': conference, 'speaker': spk, }, receivername=spk.name, ) spk.lastmodified = timezone.now() spk.save() whatstr.write( "Reminded speaker {0} that their profile is empty\n".format( spk.name))
def remind_unregistered_speakers(self, whatstr, conference): # Get speakers that are approved but not registered speakers = list( Speaker.objects.raw( "SELECT s.* FROM confreg_speaker s WHERE EXISTS (SELECT 1 FROM confreg_conferencesession sess INNER JOIN confreg_conferencesession_speaker css ON css.conferencesession_id=sess.id WHERE sess.conference_id=%s AND css.speaker_id=s.id AND sess.status=1 and sess.lastnotifiedstatus=1 AND sess.lastnotifiedtime<%s) AND NOT EXISTS (SELECT 1 FROM confreg_conferenceregistration r WHERE r.conference_id=%s AND r.attendee_id=s.user_id AND r.payconfirmedat IS NOT NULL AND r.canceledat IS NULL)", [ conference.id, timezone.now() - timedelta(days=7), conference.id ])) if speakers: whatstr.write("Found {0} unregistered speakers:\n".format( len(speakers))) for speaker in speakers: # Update the last notified date on all sessions that are in # status approved, to make sure we don't send a second # reminder tomorrow. ConferenceSession.objects.filter( conference=conference, speaker=speaker, status=1).update(lastnotifiedtime=timezone.now()) send_conference_mail( conference, speaker.user.email, "Your registration".format(conference), 'confreg/mail/speaker_remind_register.txt', { 'conference': conference, }, receivername=speaker.fullname, ) whatstr.write( "Reminded speaker {0} to register\n".format(speaker)) whatstr.write("\n\n")
def remind_pending_speakers(self, whatstr, conference): # Remind speakers that are in pending status. But only the ones # where we've actually sent the status emails, meaning the # lastsent is the same as the current. speakers = Speaker.objects.filter(conferencesession__conference=conference, conferencesession__status=3, conferencesession__lastnotifiedstatus=3, conferencesession__lastnotifiedtime__lt=datetime.now() - timedelta(days=7)).distinct() if speakers: whatstr.write("Found {0} unconfirmed talks:\n".format(len(speakers))) for speaker in speakers: sessions = speaker.conferencesession_set.filter(conference=conference, status=3) for s in sessions: s.lastnotifiedtime = datetime.now() s.save() send_conference_mail(conference, speaker.user.email, "Your submissions".format(conference), 'confreg/mail/speaker_remind_confirm.txt', { 'conference': conference, 'sessions': sessions, }, receivername=speaker.fullname, ) whatstr.write("Reminded speaker {0} to confirm {1} talks\n".format(speaker, len(sessions))) whatstr.write("\n\n")
def process_invoice_payment(self, invoice): try: pv = PurchasedVoucher.objects.get(pk=invoice.processorid) except PurchasedVoucher.DoesNotExist: raise Exception("Could not find voucher order %s" % invoice.processorid) if pv.batch: raise Exception( "This voucher order has already been processed: %s" % invoice.processorid) # Set up the batch batch = PrepaidBatch(conference=pv.conference, regtype=pv.regtype, buyer=pv.user, buyername="{0} {1}".format( pv.user.first_name, pv.user.last_name), sponsor=pv.sponsor) batch.save() for n in range(0, pv.num): v = PrepaidVoucher(conference=pv.conference, vouchervalue=base64.b64encode( os.urandom(37)).rstrip(b'=').decode('utf8'), batch=batch) v.save() pv.batch = batch pv.save() if pv.sponsor: send_conference_sponsor_notification( pv.conference, "Sponsor %s purchased vouchers" % pv.sponsor.name, "The sponsor\n%s\nhas purchased %s vouchers of type \"%s\".\n\n" % (pv.sponsor.name, pv.num, pv.regtype.regtype), ) else: # For non-sponsors, there is no dashboard available, so we send the actual vouchers in an # email directly. send_conference_mail( pv.conference, pv.batch.buyer.email, "Entry vouchers to {}".format(pv.conference.conferencename), 'confreg/mail/prepaid_vouchers.txt', { 'batch': batch, 'vouchers': batch.prepaidvoucher_set.all(), 'conference': pv.conference, }, sender=pv.conference.contactaddr, )
def handle(self, *args, **options): # Any entries that actually have an invoice will be canceled by the invoice # system, as the expiry time of the invoice is set synchronized. In this # run, we only care about offers that have not been picked up at all. wlentries = RegistrationWaitlistEntry.objects.filter( registration__payconfirmedat__isnull=True, registration__invoice__isnull=True, offerexpires__lt=datetime.now()) for w in wlentries: reg = w.registration # Create a history entry so we know exactly when it happened RegistrationWaitlistHistory(waitlist=w, text="Offer expired at {0}".format( w.offerexpires)).save() # Notify conference organizers send_simple_mail( reg.conference.notifyaddr, reg.conference.notifyaddr, 'Waitlist expired', 'User {0} {1} <{2}> did not complete the registration before the waitlist offer expired.' .format(reg.firstname, reg.lastname, reg.email), sendername=reg.conference.conferencename) # Also send an email to the user send_conference_mail( reg.conference, reg.email, 'Your waitlist offer has expired', 'confreg/mail/waitlist_expired.txt', { 'conference': reg.conference, 'reg': reg, 'offerexpires': w.offerexpires, }, receivername=reg.fullname, ) # Now actually expire the offer w.offeredon = None w.offerexpires = None # Move the user to the back of the waitlist (we have a history entry for the # initial registration date, so it's still around) w.enteredon = datetime.now() w.save()
def remind_pending_multiregs(self, whatstr, conference): # Reminde owners of "multiregs" that have not been completed. Basic rules # are the same as remind_pending_registrations(), but we only consider # those that are managed by somebody else. regs = ConferenceRegistration.objects.filter( conference=conference, conference__active=True, conference__enddate__gt=timezone.now(), attendee__isnull=True, registrator__isnull=False, payconfirmedat__isnull=True, invoice__isnull=True, bulkpayment__isnull=True, registrationwaitlistentry__isnull=True, created__lt=timezone.now() - timedelta(days=5), lastmodified__lt=timezone.now() - timedelta(days=5)) if regs: multiregs = set([r.registrator for r in regs]) whatstr.write( "Found {0} unconfirmed multiregistrations that are stalled:\n". format(len(multiregs))) for r in multiregs: send_conference_mail( conference, r.email, "Your registrations".format(conference), 'confreg/mail/multireg_stalled_registration.txt', { 'conference': conference, 'registrator': r, }, receivername="{0} {1}".format(r.first_name, r.last_name), ) whatstr.write( "Reminded user {0} ({1}) that their multi-registration is not confirmed\n" .format(r.username, r.email)) whatstr.write("\n\n") # Separately mark each part of the multireg as touched for reg in regs: reg.lastmodified = timezone.now() reg.save()
def save_form(self, form, claim, request): if int(form.cleaned_data['vouchercount']) == 0: # No vouchers --> unclaim this benefit claim.claimdata = "0" claim.declined = True claim.confirmed = True else: # Actual number, form has been validated, so create the vouchers. batch = PrepaidBatch( conference=self.level.conference, regtype=RegistrationType.objects.get( conference=self.level.conference, regtype=self.params['type']), buyer=request.user, buyername="%s %s" % (request.user.first_name, request.user.last_name), sponsor=claim.sponsor) batch.save() vouchers = [] for n in range(0, int(form.cleaned_data['vouchercount'])): v = PrepaidVoucher( conference=self.level.conference, vouchervalue=base64.b64encode( os.urandom(37)).rstrip(b'=').decode('utf8'), batch=batch) v.save() vouchers.append(v) # Send an email about the new vouchers send_conference_mail( self.level.conference, request.user.email, "Entry vouchers", 'confreg/mail/prepaid_vouchers.txt', { 'batch': batch, 'vouchers': vouchers, 'conference': self.level.conference, }, sender=self.level.conference.sponsoraddr, ) # Finally, finish the claim claim.claimdata = batch.id claim.confirmed = True # Always confirmed, they're generated after all return True
def remind_pending_registrations(self, whatstr, conference): # Get registrations made which have no invoice, no bulk registration, # and are not completed. We look at registrations created more than 5 # days ago and also unmodified for 5 days. This is intentionally not 7 # days in order to "rotate the day of week" the reminders go out on. # Only send reminders if attendee has a value, meaning we don't send # reminders to registrations that are managed by somebody else. regs = ConferenceRegistration.objects.filter( conference=conference, conference__active=True, conference__enddate__gt=timezone.now(), attendee__isnull=False, payconfirmedat__isnull=True, invoice__isnull=True, bulkpayment__isnull=True, registrationwaitlistentry__isnull=True, created__lt=timezone.now() - timedelta(days=5), lastmodified__lt=timezone.now() - timedelta(days=5)) if regs: whatstr.write( "Found {0} unconfirmed registrations that are stalled:\n". format(len(regs))) for reg in regs: send_conference_mail( conference, reg.email, "Your registration".format(conference), 'confreg/mail/attendee_stalled_registration.txt', { 'conference': conference, 'reg': reg, }, receivername=reg.fullname, ) reg.lastmodified = timezone.now() reg.save() whatstr.write( "Reminded attendee {0} that their registration is not confirmed\n" .format(reg.fullname)) whatstr.write("\n\n")
def confirm_sponsor(sponsor, who): # Confirm a sponsor, including sending the confirmation email. # This will save the specified sponsor model as well, but the function # expects to be wrapped in external transaction handler. sponsor.confirmed = True sponsor.confirmedat = timezone.now() sponsor.confirmedby = who sponsor.save() for manager in sponsor.managers.all(): send_conference_mail(sponsor.conference, manager.email, "Sponsorship confirmed", 'confsponsor/mail/sponsor_confirmed.txt', { 'sponsor': sponsor, 'conference': sponsor.conference, }, sender=sponsor.conference.sponsoraddr, receivername='{0} {1}'.format( manager.first_name, manager.last_name))
def handle(self, *args, **options): # We're always going to process all conferences, since most will not have any # open discount codes. filt = Q(sponsor__isnull=False, is_invoiced=False) & ( Q(validuntil__lte=today_global()) | Q(num_uses__gte=F('maxuses'))) codes = DiscountCode.objects.annotate( num_uses=Count('registrations')).filter(filt) for code in codes: # Either the code has expired, or it is fully used by now. Time to generate the invoice. We'll also # send an email to the sponsor (and the admins) to inform them of what's happening. # The invoice will be a one-off one, we don't need a registered manager for it since the # discounts have already been given out. if code.count == 0: # In case there is not a single user, we just notify the user of this and set it to # invoiced in the system so we don't try again. code.is_invoiced = True code.save() send_conference_sponsor_notification( code.conference, "[{0}] Discount code expired".format(code.conference), "Discount code {0} has expired without any uses.".format( code.code), ) for manager in code.sponsor.managers.all(): send_conference_mail( code.conference, manager.email, "Discount code {0} expired".format(code.code), 'confsponsor/mail/discount_expired.txt', { 'code': code, 'sponsor': code.sponsor, 'conference': code.conference, }, sender=code.conference.sponsoraddr, receivername='{0} {1}'.format(manager.first_name, manager.last_name)) else: # At least one use, so we generate the invoice invoicerows = [] for r in code.registrations.all(): if code.discountamount: # Fixed amount discount. Always apply discountvalue = code.discountamount else: # Percentage discount, so we need to calculate it. Ordered discount codes will # only support a registration-only style discount code, so only count it # against that. discountvalue = r.regtype.cost * code.discountpercentage / 100 invoicerows.append([ 'Attendee "{0}"'.format(r.fullname), 1, discountvalue, r.conference.vat_registrations ]) # All invoices are always due immediately manager = InvoiceManager() code.invoice = manager.create_invoice( code.sponsor_rep, code.sponsor_rep.email, "{0} {1}".format(code.sponsor_rep.first_name, code.sponsor_rep.last_name), '%s\n%s' % (code.sponsor.name, code.sponsor.invoiceaddr), '{0} discount code {1}'.format(code.conference, code.code), timezone.now(), timezone.now() + timedelta(days=1), invoicerows, accounting_account=settings.ACCOUNTING_CONFREG_ACCOUNT, accounting_object=code.conference.accounting_object, paymentmethods=code.conference.paymentmethods.all(), ) code.invoice.save() code.is_invoiced = True code.save() wrapper = InvoiceWrapper(code.invoice) wrapper.email_invoice() # Now also fire off emails, both to the admins and to all the managers of the sponsor # (so they know where the invoice was sent). send_conference_sponsor_notification( code.conference, "[{0}] Discount code {1} has been invoiced".format( code.conference, code.code), "The discount code {0} has been closed,\nand an invoice has been sent to {1}.\n\nA total of {2} registrations used this code, and the total amount was {3}.\n" .format( code.code, code.sponsor, len(invoicerows), code.invoice.total_amount, ), ) for manager in code.sponsor.managers.all(): send_conference_mail( code.conference, manager.email, "Discount code {0} has been invoiced".format( code.code), 'confsponsor/mail/discount_invoiced.txt', { 'code': code, 'conference': code.conference, 'sponsor': code.sponsor, 'invoice': code.invoice, 'curr': settings.CURRENCY_ABBREV, 'expired_time': code.validuntil < today_global(), }, sender=code.conference.sponsoraddr, receivername='{0} {1}'.format(manager.first_name, manager.last_name))
def sponsor_scanning(request, sponsorid): sponsor, is_admin = _get_sponsor_and_admin(sponsorid, request, False) if not sponsor.conference.askbadgescan: return HttpResponse( "Badge scanning questions are not enabled on this conference", status=403) if not SponsorClaimedBenefit.objects.filter( sponsor=sponsor, benefit__benefit_class=get_benefit_id( 'badgescanning.BadgeScanning'), declined=False, confirmed=True).exists(): return HttpResponse( "Badge scanning not a claimed benefit for this sponsor", status=403) if request.method == 'POST': if request.POST.get('what', '') == 'add': if not request.POST.get('email', ''): messages.warning(request, "Cannot add empty address") return HttpResponseRedirect(".") try: reg = ConferenceRegistration.objects.get( conference=sponsor.conference, email=request.POST.get('email').lower()) if not reg.payconfirmedat: messages.error(request, "Attendee is not confirmed") return HttpResponseRedirect(".") if sponsor.sponsorscanner_set.filter(scanner=reg).exists(): messages.warning( request, "Attendee already registered as a scanner") return HttpResponseRedirect(".") scanner = SponsorScanner(sponsor=sponsor, scanner=reg, token=generate_random_token()) scanner.save() sponsor.sponsorscanner_set.add(scanner) return HttpResponseRedirect(".") except ConferenceRegistration.DoesNotExist: messages.error(request, "Attendee not found") return HttpResponseRedirect(".") elif request.POST.get('what', '') == 'del': # There should only be one remove-<something> for k in request.POST.keys(): if k.startswith('remove-'): rid = k[len('remove-'):] try: scanner = SponsorScanner.objects.get(sponsor=sponsor, pk=rid) n = scanner.scanner.fullname if scanner.scanner.scanned_attendees.exists(): messages.warning( request, "Attende {0} has scanned badges already, cannot be removed" .format(n)) else: scanner.delete() messages.info( request, "Attendee {0} removed from scanning".format(n)) except SponsorScanner.DoesNotExist: messges.error(request, "Attendee not found") return HttpResponseRedirect(".") elif k.startswith('email-'): rid = k[len('email-'):] try: scanner = SponsorScanner.objects.get(sponsor=sponsor, pk=rid) send_conference_mail( sponsor.conference, scanner.scanner.email, "Attendee badge scanning", "confsponsor/mail/badge_scanning_intro.txt", { 'conference': sponsor.conference, 'sponsor': sponsor, 'scanner': scanner, }, sender=sponsor.conference.sponsoraddr, receivername=scanner.scanner.fullname, ) messages.info( request, "Instructions email sent to {0}".format( scanner.scanner.fullname)) except SponsorScanner.DoesNotExist: messages.error(request, "Attendee not found") return HttpResponseRedirect(".") else: messages.error(request, "Invalid form submit") return HttpResponseRedirect(".") elif request.POST.get('what', '') == 'delscan': # There should only be one delete-scan-<id> for k in request.POST.keys(): if k.startswith('delete-scan-'): scid = int(k[len('delete-scan-'):]) try: scan = ScannedAttendee.objects.get(sponsor=sponsor, pk=scid) scan.delete() except ScannedAttendee.DoesNotExist: messages.error( request, "Scan has already been removed or permission denied" ) break else: messages.error(request, "Invalid form submit") return HttpResponseRedirect(".") else: # Unknown form, so just return return HttpResponseRedirect(".") scanned = ScannedAttendee.objects.select_related( 'attendee', 'scannedby', 'attendee__country').filter(sponsor=sponsor) return render(request, 'confsponsor/sponsor_scanning.html', { 'scanners': sponsor.sponsorscanner_set.all(), 'scanned': scanned, })