示例#1
0
文件: models.py 项目: taavi/pycon
class Sponsor(models.Model):

    applicant = models.ForeignKey(User,
                                  related_name="sponsorships",
                                  verbose_name=_("applicant"),
                                  null=True)

    name = models.CharField(_("Sponsor Name"), max_length=100)
    display_url = models.URLField(_("display URL"), blank=True)
    external_url = models.URLField(_("external URL"))
    annotation = models.TextField(_("annotation"), blank=True)
    contact_name = models.CharField(_("Contact Name"), max_length=100)
    contact_email = models.EmailField(_(u"Contact Email"))
    level = models.ForeignKey(SponsorLevel, verbose_name=_("level"))
    added = models.DateTimeField(_("added"), default=datetime.datetime.now)
    active = models.BooleanField(_("active"), default=False)

    # Denormalization (this assumes only one logo)
    sponsor_logo = models.ForeignKey("SponsorBenefit",
                                     related_name="+",
                                     null=True,
                                     blank=True,
                                     editable=False)

    # Whether things are complete
    # True = complete, False = incomplate, Null = n/a for this sponsor level
    web_logo_benefit = models.NullBooleanField(
        help_text=_(u"Web logo benefit is complete"))
    print_logo_benefit = models.NullBooleanField(
        help_text=_(u"Print logo benefit is complete"))
    print_description_benefit = models.NullBooleanField(
        help_text=_(u"Print description benefit is complete"))
    company_description_benefit = models.NullBooleanField(
        help_text=_(u"Company description benefit is complete"))
    advertisement_benefit = models.NullBooleanField(
        help_text=_(u"Advertisement benefit is complete"))

    objects = SponsorManager()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _("sponsor")
        verbose_name_plural = _("sponsors")
        ordering = ['name']

    def save(self, *args, **kwargs):
        # Set fields related to benefits being complete
        for benefit in BENEFITS:
            field_name = benefit['field_name']
            benefit_name = benefit['name']
            setattr(self, field_name, self.benefit_is_complete(benefit_name))
        super(Sponsor, self).save(*args, **kwargs)

    def get_absolute_url(self):
        if self.active:
            return reverse("sponsor_detail", kwargs={"pk": self.pk})
        return reverse("sponsor_list")

    def get_display_url(self):
        if self.display_url:
            return self.display_url
        else:
            return self.external_url

    def render_email(self, text):
        """Replace special strings in text with values from the sponsor.

        %%NAME%% --> Sponsor name
        """
        return text.replace("%%NAME%%", self.name)

    @property
    def website_logo_url(self):
        if not hasattr(self, "_website_logo_url"):
            self._website_logo_url = None
            benefits = self.sponsor_benefits.filter(benefit__type="weblogo",
                                                    upload__isnull=False)
            if benefits.exists():
                # @@@ smarter handling of multiple weblogo benefits?
                # shouldn't happen
                if benefits[0].upload:
                    self._website_logo_url = benefits[0].upload.url
        return self._website_logo_url

    @property
    def listing_text(self):
        if not hasattr(self, "_listing_text"):
            self._listing_text = None
            benefits = self.sponsor_benefits.filter(benefit__id=7)
            if benefits.count():
                self._listing_text = benefits[0].text
        return self._listing_text

    @property
    def joblisting_text(self):
        if not hasattr(self, "_joblisting_text"):
            self._joblisting_text = None
            benefits = self.sponsor_benefits.filter(benefit__id=8)
            if benefits.count():
                self._joblisting_text = benefits[0].text
        return self._joblisting_text

    @property
    def website_logo(self):
        if self.sponsor_logo is None:
            benefits = self.sponsor_benefits.filter(benefit__type="weblogo",
                                                    upload__isnull=False)[:1]
            if benefits.count():
                if benefits[0].upload:
                    self.sponsor_logo = benefits[0]
                    self.save()
        return self.sponsor_logo.upload

    def reset_benefits(self):
        """
        Reset all benefits for this sponsor to the defaults for their
        sponsorship level.
        """
        level = None

        try:
            level = self.level
        except SponsorLevel.DoesNotExist:
            pass

        allowed_benefits = []
        if level:
            for benefit_level in level.benefit_levels.all():
                # Create all needed benefits if they don't exist already
                sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
                    sponsor=self, benefit=benefit_level.benefit)

                # and set to default limits for this level.
                sponsor_benefit.max_words = benefit_level.max_words
                sponsor_benefit.other_limits = benefit_level.other_limits

                # and set to active
                sponsor_benefit.active = True

                # @@@ We don't call sponsor_benefit.clean here. This means
                # that if the sponsorship level for a sponsor is adjusted
                # downwards, an existing too-long text entry can remain,
                # and won't raise a validation error until it's next
                # edited.
                sponsor_benefit.save()

                allowed_benefits.append(sponsor_benefit.pk)

        # Any remaining sponsor benefits that don't normally belong to
        # this level are set to inactive
        self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(
            active=False, max_words=None, other_limits="")

    # @@@ should this just be done centrally?
    def send_coordinator_emails(self):
        for user in User.objects.filter(groups__name=SPONSOR_COORDINATORS):
            send_email([user.email],
                       "sponsor_signup",
                       context={"sponsor": self})

    def benefit_is_complete(self, name):
        """Return True - benefit is complete, False - benefit is not complete,
         or None - benefit not applicable for this sponsor's level """
        if BenefitLevel.objects.filter(level=self.level,
                                       benefit__name=name).exists():
            try:
                benefit = self.sponsor_benefits.get(benefit__name=name)
            except SponsorBenefit.DoesNotExist:
                return False
            else:
                return benefit.is_complete
        else:
            return None  # Not an applicable benefit for this sponsor's level
示例#2
0
文件: models.py 项目: babykick/pycon
class Sponsor(models.Model):

    applicant = models.ForeignKey(User,
                                  related_name="sponsorships",
                                  verbose_name=_("applicant"),
                                  null=True)

    name = models.CharField(_("Sponsor Name"), max_length=100)
    display_url = models.URLField(_("display URL"), blank=True)
    external_url = models.URLField(_("external URL"))
    annotation = models.TextField(_("annotation"), blank=True)
    contact_name = models.CharField(_("Contact Name"), max_length=100)
    contact_email = models.EmailField(_(u"Contact Email"))
    level = models.ForeignKey(SponsorLevel, verbose_name=_("level"))
    added = models.DateTimeField(_("added"), default=datetime.datetime.now)
    active = models.BooleanField(_("active"), default=False)

    # Denormalization (this assumes only one logo)
    sponsor_logo = models.ForeignKey("SponsorBenefit",
                                     related_name="+",
                                     null=True,
                                     blank=True,
                                     editable=False)

    objects = SponsorManager()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _("sponsor")
        verbose_name_plural = _("sponsors")

    def get_absolute_url(self):
        if self.active:
            return reverse("sponsor_detail", kwargs={"pk": self.pk})
        return reverse("sponsor_list")

    def get_display_url(self):
        if self.display_url:
            return self.display_url
        else:
            return self.external_url

    @property
    def website_logo_url(self):
        if not hasattr(self, "_website_logo_url"):
            self._website_logo_url = None
            benefits = self.sponsor_benefits.filter(benefit__type="weblogo",
                                                    upload__isnull=False)
            if benefits.exists():
                # @@@ smarter handling of multiple weblogo benefits?
                # shouldn't happen
                if benefits[0].upload:
                    self._website_logo_url = benefits[0].upload.url
        return self._website_logo_url

    @property
    def listing_text(self):
        if not hasattr(self, "_listing_text"):
            self._listing_text = None
            benefits = self.sponsor_benefits.filter(benefit__id=7)
            if benefits.count():
                self._listing_text = benefits[0].text
        return self._listing_text

    @property
    def joblisting_text(self):
        if not hasattr(self, "_joblisting_text"):
            self._joblisting_text = None
            benefits = self.sponsor_benefits.filter(benefit__id=8)
            if benefits.count():
                self._joblisting_text = benefits[0].text
        return self._joblisting_text

    @property
    def website_logo(self):
        if self.sponsor_logo is None:
            benefits = self.sponsor_benefits.filter(benefit__type="weblogo",
                                                    upload__isnull=False)[:1]
            if benefits.count():
                if benefits[0].upload:
                    self.sponsor_logo = benefits[0]
                    self.save()
        return self.sponsor_logo.upload

    def reset_benefits(self):
        """
        Reset all benefits for this sponsor to the defaults for their
        sponsorship level.
        """
        level = None

        try:
            level = self.level
        except SponsorLevel.DoesNotExist:
            pass

        allowed_benefits = []
        if level:
            for benefit_level in level.benefit_levels.all():
                # Create all needed benefits if they don't exist already
                sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
                    sponsor=self, benefit=benefit_level.benefit)

                # and set to default limits for this level.
                sponsor_benefit.max_words = benefit_level.max_words
                sponsor_benefit.other_limits = benefit_level.other_limits

                # and set to active
                sponsor_benefit.active = True

                # @@@ We don't call sponsor_benefit.clean here. This means
                # that if the sponsorship level for a sponsor is adjusted
                # downwards, an existing too-long text entry can remain,
                # and won't raise a validation error until it's next
                # edited.
                sponsor_benefit.save()

                allowed_benefits.append(sponsor_benefit.pk)

        # Any remaining sponsor benefits that don't normally belong to
        # this level are set to inactive
        self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(
            active=False, max_words=None, other_limits="")

    # @@@ should this just be done centrally?
    def send_coordinator_emails(self):
        for user in User.objects.filter(groups__name=SPONSOR_COORDINATORS):
            send_email([user.email],
                       "sponsor_signup",
                       context={"sponsor": self})
示例#3
0
文件: models.py 项目: Diwahars/pycon
class Sponsor(models.Model):

    applicant = models.ForeignKey(User,
                                  related_name="sponsorships",
                                  verbose_name=_("applicant"),
                                  null=True,
                                  on_delete=SET_NULL)

    name = models.CharField(_("Sponsor Name"), max_length=100)
    display_url = models.URLField(_(
        "Link text - text to display on link to sponsor page, if different from the actual link"
    ),
                                  blank=True)
    external_url = models.URLField(_("Link to sponsor web page"))
    annotation = models.TextField(_("annotation"), blank=True)
    contact_name = models.CharField(_("Contact Name"), max_length=100)
    contact_emails = MultiEmailField(
        _(u"Contact Emails"),
        default='',
        help_text=_(u"Please enter one email address per line."))
    contact_phone = models.CharField(_(u"Contact Phone"), max_length=32)
    contact_address = models.TextField(_(u"Contact Address"))
    level = models.ForeignKey(SponsorLevel, verbose_name=_("level"))
    added = models.DateTimeField(_("added"), default=datetime.datetime.now)

    active = models.BooleanField(_("active"), default=False)
    approval_time = models.DateTimeField(null=True, blank=True, editable=False)

    wants_table = models.BooleanField(
        _("Does your organization want a table at the job fair?"),
        default=False)
    wants_booth = models.BooleanField(
        _("Does your organization want a booth on the expo floor?"),
        default=False)

    # Whether things are complete
    # True = complete, False = incomplate, Null = n/a for this sponsor level
    print_logo_benefit = models.NullBooleanField(
        help_text=_(u"Print logo benefit is complete"))
    advertisement_benefit = models.NullBooleanField(
        help_text=_(u"Advertisement benefit is complete"))

    registration_promo_codes = models.CharField(max_length=200,
                                                blank=True,
                                                default='')
    booth_number = models.IntegerField(blank=True, null=True, default=None)
    job_fair_table_number = models.IntegerField(blank=True,
                                                null=True,
                                                default=None)

    web_description = models.TextField(
        _(u"Company description (to show on the web site)"), )
    web_logo = models.ImageField(
        _(u"Company logo (to show on the web site)"),
        upload_to="sponsor_files",
        null=True,  # This is nullable in case old data doesn't have a web logo
        # We enforce it on all new or edited sponsors though.
    )

    objects = SponsorManager()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _("sponsor")
        verbose_name_plural = _("sponsors")
        ordering = ['name']

    def save(self, *args, **kwargs):
        # Set fields related to benefits being complete
        for benefit in BENEFITS:
            field_name = benefit['field_name']
            benefit_name = benefit['name']
            setattr(self, field_name, self.benefit_is_complete(benefit_name))
        super(Sponsor, self).save(*args, **kwargs)

    def get_absolute_url(self):
        if self.active:
            return reverse("sponsor_detail", kwargs={"pk": self.pk})
        return reverse("sponsor_list")

    def get_display_url(self):
        """
        Return the text to display on the sponsor's link
        """
        if self.display_url:
            return self.display_url
        else:
            return self.external_url

    def render_email(self, text):
        """Replace special strings in text with values from the sponsor.

        %%NAME%% --> Sponsor name
        %%REGISTRATION_PROMO_CODES%% --> Registration promo codes, or empty string
        %%BOOTH_NUMBER%% --> Booth number, or empty string if not set
        %%JOB_FAIR_TABLE_NUMBER%%" --> Job fair tabl number, or empty string if not set
        """
        text = text.replace("%%NAME%%", self.name)
        text = text.replace("%%REGISTRATION_PROMO_CODES%%",
                            self.registration_promo_codes)

        # The next two are numbers, or if not set, None.  We don't want to
        # display "None" :-), but we might want to display "0".
        booth = str(self.booth_number) if self.booth_number is not None else ""
        text = text.replace("%%BOOTH_NUMBER%%", booth)
        table = str(self.job_fair_table_number
                    ) if self.job_fair_table_number is not None else ""
        text = text.replace("%%JOB_FAIR_TABLE_NUMBER%%", table)
        return text

    @cached_property
    def website_logo_url(self):
        if self.web_logo:
            return self.web_logo.url

    @property
    def joblisting_text(self):
        if not hasattr(self, "_joblisting_text"):
            self._joblisting_text = None
            benefits = self.sponsor_benefits.filter(benefit__id=8)
            if benefits.count():
                self._joblisting_text = benefits[0].text
        return self._joblisting_text

    @property
    def website_logo(self):
        return self.web_logo

    def reset_benefits(self):
        """
        Reset all benefits for this sponsor to the defaults for their
        sponsorship level.
        """
        level = None

        try:
            level = self.level
        except SponsorLevel.DoesNotExist:
            pass

        allowed_benefits = []
        if level:
            for benefit_level in level.benefit_levels.all():
                # Create all needed benefits if they don't exist already
                sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
                    sponsor=self, benefit=benefit_level.benefit)

                # and set to default limits for this level.
                sponsor_benefit.max_words = benefit_level.max_words
                sponsor_benefit.other_limits = benefit_level.other_limits

                # and set to active
                sponsor_benefit.active = True

                # @@@ We don't call sponsor_benefit.clean here. This means
                # that if the sponsorship level for a sponsor is adjusted
                # downwards, an existing too-long text entry can remain,
                # and won't raise a validation error until it's next
                # edited.
                sponsor_benefit.save()

                allowed_benefits.append(sponsor_benefit.pk)

        # Any remaining sponsor benefits that don't normally belong to
        # this level are set to inactive
        self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(
            active=False, max_words=None, other_limits="")

    # @@@ should this just be done centrally?
    def send_coordinator_emails(self):
        for user in User.objects.filter(groups__name=SPONSOR_COORDINATORS):
            send_email([user.email],
                       "sponsor_signup",
                       context={"sponsor": self})

    def benefit_is_complete(self, name):
        """Return True - benefit is complete, False - benefit is not complete,
         or None - benefit not applicable for this sponsor's level """
        if BenefitLevel.objects.filter(level=self.level,
                                       benefit__name=name).exists():
            try:
                benefit = self.sponsor_benefits.get(benefit__name=name)
            except SponsorBenefit.DoesNotExist:
                return False
            else:
                return benefit.is_complete
        else:
            return None  # Not an applicable benefit for this sponsor's level
示例#4
0
文件: models.py 项目: gridl/pycon-1
class Sponsor(models.Model):

    applicant = models.ForeignKey(User,
                                  related_name="sponsorships",
                                  verbose_name=_("applicant"),
                                  null=True,
                                  on_delete=SET_NULL)

    name = models.CharField(_("Sponsor Name"), max_length=100)
    display_url = models.CharField(_(
        "Link text - text to display on link to sponsor webpage, if different from the actual link"
    ),
                                   max_length=200,
                                   default='',
                                   blank=True)
    external_url = models.URLField(
        _("Link to sponsor webpage"),
        help_text=_("(Must include https:// or http://.)"))
    twitter_username = models.CharField(
        _("Twitter username"),
        blank=True,
        max_length=15,
    )
    annotation = models.TextField(_("annotation"), blank=True)
    contact_name = models.CharField(_("Contact Name"), max_length=100)
    contact_emails = MultiEmailField(
        _(u"Contact Emails"),
        default='',
        help_text=_(u"Please enter one email address per line."))
    contact_phone = models.CharField(_(u"Contact Phone"), max_length=32)
    contact_address = models.TextField(_(u"Contact Address"))
    level = models.ForeignKey(SponsorLevel, verbose_name=_("level"))
    packages = models.ManyToManyField(SponsorPackage,
                                      verbose_name=_("packages"),
                                      blank=True)
    added = models.DateTimeField(_("added"), default=datetime.datetime.now)

    active = models.BooleanField(_("active"), default=False)
    approval_time = models.DateTimeField(null=True, blank=True, editable=False)

    wants_table = models.BooleanField(_(
        'Does your organization want a table at the job fair? '
        '(See <a href="/2019/sponsors/fees/">Estimated Sponsor Fees</a> '
        'for costs that might be involved.)'),
                                      default=False)
    wants_booth = models.BooleanField(_(
        'Does your organization want a booth on the expo floor? '
        '(See <a href="/2019/sponsors/fees/">Estimated Sponsor Fees</a> '
        'for costs that might be involved.)'),
                                      default=False)
    small_entity_discount = models.BooleanField(_(
        'Does your organization have fewer than 25 employees,'
        ' which qualifies you for our Small Entity Discount of 30%?'),
                                                default=False)

    # Whether things are complete
    # True = complete, False = incomplate, Null = n/a for this sponsor level
    print_logo_benefit = models.NullBooleanField(
        help_text=_(u"Print logo benefit is complete"))
    advertisement_benefit = models.NullBooleanField(
        help_text=_(u"Advertisement benefit is complete"))

    registration_promo_codes = models.CharField(max_length=200,
                                                blank=True,
                                                default='')
    expo_promo_codes = models.CharField(max_length=200, blank=True, default='')
    additional_discounted_registration_promo_codes = models.CharField(
        max_length=200, blank=True, default='')
    a_la_carte_registration_promo_codes = models.CharField(max_length=200,
                                                           blank=True,
                                                           default='')
    booth_number = models.CharField(max_length=5,
                                    blank=True,
                                    null=True,
                                    default=None)
    job_fair_participant = models.BooleanField(default=False)
    job_fair_table_number = models.CharField(max_length=5,
                                             blank=True,
                                             null=True,
                                             default=None)

    web_description = models.TextField(
        _(u"Company description (to show on the web site)"), )
    web_logo = models.ImageField(
        _(u"Web logo"),
        help_text=
        _("For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px"
          ),
        upload_to="sponsor_files",
        null=True,  # This is nullable in case old data doesn't have a web logo
        # We enforce it on all new or edited sponsors though.
    )
    print_logo = models.FileField(
        _(u"Print logo"),
        help_text=_(
            "For printed materials, signage, and projection. SVG or EPS"),
        upload_to="sponsor_files",
        blank=True,
        null=
        True,  # This is nullable in case old data doesn't have a printed logo
        # We enforce it on all new or edited sponsors though.
    )

    objects = SponsorManager()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _("sponsor")
        verbose_name_plural = _("sponsors")
        ordering = ['name']

    def save(self, *args, **kwargs):
        # Set fields related to benefits being complete
        for benefit in BENEFITS:
            field_name = benefit['field_name']
            benefit_name = benefit['name']
            setattr(self, field_name, self.benefit_is_complete(benefit_name))
        super(Sponsor, self).save(*args, **kwargs)

    def get_absolute_url(self):
        if self.active:
            return reverse("sponsor_detail", kwargs={"pk": self.pk})
        return reverse("sponsor_list")

    def get_display_url(self):
        """
        Return the text to display on the sponsor's link
        """
        if self.display_url:
            return self.display_url
        else:
            return self.external_url

    def render_email(self, text):
        """Replace special strings in text with values from the sponsor.

        %%NAME%% --> Sponsor name
        %%REGISTRATION_PROMO_CODES%% --> Registration promo codes, or empty string
        %%EXPO_PROMO_CODES%% --> Expo Hall only promo codes, or empty string
        %%ADDITIONAL_DISCOUNTED_REGISTRATION_PROMO_CODES%% --> Additional Discounted Registration promo codes, or empty string
        %%A_LA_CARTE_REGISTRATION_PROMO_CODES%% --> A la Carte Registration promo codes, or empty string
        %%BOOTH_NUMBER%% --> Booth number, or empty string if not set
        %%JOB_FAIR_TABLE_NUMBER%%" --> Job fair table number, or empty string if not set

        Flags:
          JOB_FAIR_PARTICIPANT: Use with {% if JOB_FAIR_PARTICIPANT %} block to
                                include some content for Job Fair Participants
        """
        text = text.replace("%%NAME%%", self.name)
        text = text.replace("%%REGISTRATION_PROMO_CODES%%",
                            self.registration_promo_codes or 'N/A')
        text = text.replace("%%EXPO_PROMO_CODES%%", self.expo_promo_codes
                            or 'N/A')
        text = text.replace(
            "%%ADDITIONAL_DISCOUNTED_REGISTRATION_PROMO_CODES%%",
            self.additional_discounted_registration_promo_codes or 'N/A')
        text = text.replace("%%A_LA_CARTE_REGISTRATION_PROMO_CODES%%",
                            self.a_la_carte_registration_promo_codes or 'N/A')

        # The next two are numbers, or if not set, None.  We don't want to
        # display "None" :-), but we might want to display "0".
        booth = str(self.booth_number) if self.booth_number is not None else ""
        text = text.replace("%%BOOTH_NUMBER%%", booth)
        table = str(self.job_fair_table_number
                    ) if self.job_fair_table_number is not None else ""
        text = text.replace("%%JOB_FAIR_TABLE_NUMBER%%", table)
        email_template = Template(text)
        email_context = Context({
            'JOB_FAIR_PARTICIPANT':
            self.job_fair_participant,
        })
        text = email_template.render(email_context)
        return text

    @cached_property
    def website_logo_url(self):
        if self.web_logo:
            return self.web_logo.url

    @property
    def joblisting_text(self):
        if not hasattr(self, "_joblisting_text"):
            self._joblisting_text = None
            benefits = self.sponsor_benefits.filter(
                benefit__name__startswith='Job Listing', )
            if benefits.count():
                self._joblisting_text = benefits[0].text
        return self._joblisting_text

    @property
    def website_logo(self):
        return self.web_logo

    def reset_benefits(self):
        """
        Reset all benefits for this sponsor to the defaults for their
        sponsorship level.
        """
        level = None

        try:
            level = self.level
        except SponsorLevel.DoesNotExist:
            pass

        try:
            packages = self.packages
        except SponsorPackage.DoesNotExist:
            pass

        allowed_benefits = []
        if level:
            for benefit_level in level.benefit_levels.all():
                # Create all needed benefits if they don't exist already
                sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
                    sponsor=self, benefit=benefit_level.benefit)

                # and set to default limits for this level.
                sponsor_benefit.max_words = benefit_level.max_words
                sponsor_benefit.other_limits = benefit_level.other_limits

                # and set to active
                sponsor_benefit.active = True

                # @@@ We don't call sponsor_benefit.clean here. This means
                # that if the sponsorship level for a sponsor is adjusted
                # downwards, an existing too-long text entry can remain,
                # and won't raise a validation error until it's next
                # edited.
                sponsor_benefit.save()

                allowed_benefits.append(sponsor_benefit.pk)

        if packages:
            for package in packages.all():
                for benefit_package in package.benefit_packages.all():
                    # Create all needed benefits if they don't exist already
                    sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
                        sponsor=self, benefit=benefit_package.benefit)

                    # and set to default limits for this level.
                    sponsor_benefit.max_words = benefit_package.max_words
                    sponsor_benefit.other_limits = benefit_package.other_limits

                    # and set to active
                    sponsor_benefit.active = True

                    # @@@ We don't call sponsor_benefit.clean here. This means
                    # that if the sponsorship level for a sponsor is adjusted
                    # downwards, an existing too-long text entry can remain,
                    # and won't raise a validation error until it's next
                    # edited.
                    sponsor_benefit.save()

                    allowed_benefits.append(sponsor_benefit.pk)

        # Any remaining sponsor benefits that don't normally belong to
        # this level are set to inactive
        self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(
            active=False, max_words=None, other_limits="")

    # @@@ should this just be done centrally?
    def send_coordinator_emails(self):
        for user in User.objects.filter(groups__name=SPONSOR_COORDINATORS):
            send_email([user.email],
                       "sponsor_signup",
                       context={"sponsor": self})

    def benefit_is_complete(self, name):
        """Return True - benefit is complete, False - benefit is not complete,
         or None - benefit not applicable for this sponsor's level """
        if BenefitLevel.objects.filter(level=self.level,
                                       benefit__name=name).exists():
            try:
                benefit = self.sponsor_benefits.get(benefit__name=name)
            except SponsorBenefit.DoesNotExist:
                return False
            else:
                return benefit.is_complete
        else:
            return None  # Not an applicable benefit for this sponsor's level