Beispiel #1
0
class OrganizationChangeLog(models.Model):
    """Track important changes to organizations"""

    CREATED = 0
    UPDATED = 1
    FAILED = 2

    created_at = AutoCreatedField(
        _("created at"), help_text=_("When the organization was changed"))

    organization = models.ForeignKey(
        verbose_name=_("organization"),
        to="organizations.Organization",
        on_delete=models.CASCADE,
        related_name="change_logs",
        help_text=_("The organization which changed"),
    )
    user = models.ForeignKey(
        verbose_name=_("user"),
        to="users.User",
        related_name="change_logs",
        on_delete=models.PROTECT,
        blank=True,
        null=True,
        help_text=_("The user who changed the organization"),
    )
    reason = models.PositiveSmallIntegerField(
        _("reason"),
        choices=(
            (CREATED, _("Created")),
            (UPDATED, _("Updated")),
            (FAILED, _("Payment failed")),
        ),
        help_text=_("Which category of change occurred"),
    )

    from_plan = models.ForeignKey(
        verbose_name=_("from plan"),
        to="organizations.Plan",
        on_delete=models.PROTECT,
        related_name="+",
        blank=True,
        null=True,
        help_text=_("The organization's plan before the change occurred"),
    )
    from_next_plan = models.ForeignKey(
        verbose_name=_("from next plan"),
        to="organizations.Plan",
        on_delete=models.PROTECT,
        related_name="+",
        blank=True,
        null=True,
        help_text=_("The organization's next_plan before the change occurred"),
    )
    from_max_users = models.IntegerField(
        _("maximum users"),
        blank=True,
        null=True,
        help_text=_("The organization's max_users before the change occurred"),
    )

    to_plan = models.ForeignKey(
        verbose_name=_("to plan"),
        to="organizations.Plan",
        on_delete=models.PROTECT,
        related_name="+",
        help_text=_("The organization's plan after the change occurred"),
    )
    to_next_plan = models.ForeignKey(
        verbose_name=_("to next plan"),
        to="organizations.Plan",
        on_delete=models.PROTECT,
        related_name="+",
        help_text=_("The organization's plan after the change occurred"),
    )
    to_max_users = models.IntegerField(
        _("maximum users"),
        help_text=_("The organization's max_users after the change occurred"),
    )
Beispiel #2
0
class Invitation(models.Model):
    """An invitation for a user to join an organization"""

    objects = InvitationQuerySet.as_manager()

    organization = models.ForeignKey(
        verbose_name=_("organization"),
        to="organizations.Organization",
        related_name="invitations",
        on_delete=models.CASCADE,
        help_text=_("The organization this invitation is for"),
    )
    uuid = models.UUIDField(
        _("UUID"),
        default=uuid.uuid4,
        editable=False,
        help_text=_(
            "This UUID serves as a secret token for this invitation in URLs"),
    )
    email = models.EmailField(
        _("email"),
        help_text=_("The email address to send this invitation to"))
    user = models.ForeignKey(
        verbose_name=_("user"),
        to="users.User",
        related_name="invitations",
        on_delete=models.PROTECT,
        blank=True,
        null=True,
        help_text=_(
            "The user this invitation is for.  Used if a user requested an "
            "invitation directly as opposed to an admin inviting them via email."
        ),
    )
    request = models.BooleanField(
        _("request"),
        help_text=
        "Is this a request for an invitation from the user or an invitation "
        "to the user from an admin?",
        default=False,
    )
    created_at = AutoCreatedField(
        _("created at"), help_text=_("When this invitation was created"))
    accepted_at = models.DateTimeField(
        _("accepted at"),
        blank=True,
        null=True,
        help_text=_(
            "When this invitation was accepted.  NULL signifies it has not been "
            "accepted yet"),
    )
    rejected_at = models.DateTimeField(
        _("rejected at"),
        blank=True,
        null=True,
        help_text=_(
            "When this invitation was rejected.  NULL signifies it has not been "
            "rejected yet"),
    )

    class Meta:
        ordering = ("created_at", )

    def __str__(self):
        return f"Invitation: {self.uuid}"

    def send(self):
        send_mail(
            subject=_(f"Invitation to join {self.organization.name}"),
            template="organizations/email/invitation.html",
            to=[self.email],
            extra_context={"invitation": self},
        )

    @transaction.atomic
    def accept(self, user=None):
        """Accept the invitation"""
        if self.user is None and user is None:
            raise ValueError(
                "Must give a user when accepting if invitation has no user")
        if self.accepted_at or self.rejected_at:
            raise ValueError("This invitation has already been closed")
        if self.user is None:
            self.user = user
        self.accepted_at = timezone.now()
        self.save()
        if not self.organization.has_member(self.user):
            Membership.objects.create(organization=self.organization,
                                      user=self.user)

    def reject(self):
        """Reject or revoke the invitation"""
        if self.accepted_at or self.rejected_at:
            raise ValueError("This invitation has already been closed")
        self.rejected_at = timezone.now()
        self.save()

    def get_name(self):
        """Returns the name or email if no name is set"""
        if self.user is not None and self.user.name:
            return self.user.name
        else:
            return self.email
Beispiel #3
0
class User(AvatarMixin, AbstractBaseUser, PermissionsMixin):
    """User model for squarelet

    This is a general user model which should only store information applicable
    to all of the different services which will be authenticating against
    squarelet

    Attributes:
        # AbstractBaseUser
        password (CharField): the hashed password
        last_login (DateTimeField): date time of last login

        # PermissionsMixin
        is_superuser (BooleanField): designates this user as having all permissions
        groups (ManyToManyField): groups the user are in - they will receive
            permissions from their groups
        user_permissions (ManyToManyField): permissions for this user
    """

    individual_organization = models.OneToOneField(
        verbose_name=_("individual organization"),
        to="organizations.Organization",
        on_delete=models.PROTECT,
        editable=False,
        to_field="uuid",
        help_text=_(
            "This is both the UUID for the user, as well as a foreign key to the "
            "corresponding individual organization, which has the same UUID. "
            "This is used to uniquely identify the user across services."),
    )
    name = models.CharField(_("name of user"),
                            max_length=255,
                            help_text=_("The user's full name"))
    email = CIEmailField(_("email"),
                         unique=True,
                         null=True,
                         help_text=_("The user's email address"))
    username = CICharField(
        _("username"),
        max_length=150,
        unique=True,
        help_text=_(
            "Required. 150 characters or fewer. Letters, digits and ./-/_ only.  "
            "May only be changed once."),
        validators=[UsernameValidator()],
        error_messages={
            "unqiue": _("A user with that username already exists.")
        },
    )
    avatar = ImageField(
        _("avatar"),
        upload_to=user_file_path,
        blank=True,
        max_length=255,
        help_text=_("An image to represent the user"),
    )
    can_change_username = models.BooleanField(
        _("can change username"),
        default=True,
        help_text=_(
            "Keeps track of whether or not the user has used their one "
            "username change"),
    )
    is_staff = models.BooleanField(
        _("staff status"),
        default=False,
        help_text=_(
            "Designates whether the user can log into this admin site."),
    )
    is_active = models.BooleanField(
        _("active"),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as active. "
            "Unselect this instead of deleting accounts."),
    )
    is_agency = models.BooleanField(
        _("agency user"),
        default=False,
        help_text=
        _("This is an account used for allowing agencies to log in to the site"
          ),
    )
    source = models.CharField(
        _("source"),
        max_length=13,
        choices=(
            ("muckrock", _("MuckRock")),
            ("documentcloud", _("DocumentCloud")),
            ("foiamachine", _("FOIA Machine")),
            ("quackbot", _("QuackBot")),
            ("squarelet", _("Squarelet")),
            ("presspass", _("PressPass")),
        ),
        default="squarelet",
        help_text=_("Which service did this user originally sign up for?"),
    )
    email_failed = models.BooleanField(
        _("email failed"),
        default=False,
        help_text=_(
            "Has an email we sent to this user's email address failed?"),
    )

    created_at = AutoCreatedField(_("created at"),
                                  help_text=_("When this user was created"))
    updated_at = AutoLastModifiedField(
        _("updated at"), help_text=_("When this user was last updated"))

    # preferences
    use_autologin = models.BooleanField(
        _("use autologin"),
        default=True,
        help_text=("Links you receive in emails from us will contain"
                   " a token to automatically log you in"),
    )

    USERNAME_FIELD = "username"
    EMAIL_FIELD = "email"
    REQUIRED_FIELDS = ["email"]

    default_avatar = static("images/avatars/profile.png")

    objects = UserManager()

    class Meta:
        ordering = ("username", )

    def __str__(self):
        return self.username

    @property
    def uuid(self):
        return self.individual_organization_id

    @property
    def date_joined(self):
        """Alias date joined to create_at for third party apps"""
        return self.created_at

    def save(self, *args, **kwargs):
        with transaction.atomic():
            super().save(*args, **kwargs)
            transaction.on_commit(
                lambda: send_cache_invalidations("user", self.uuid))

    def get_absolute_url(self):
        return reverse("users:detail", kwargs={"username": self.username})

    def get_full_name(self):
        return self.name

    def safe_name(self):
        if self.name:
            return self.name
        return self.username

    @mproperty
    def primary_email(self):
        """A user's primary email object"""
        return self.emailaddress_set.filter(primary=True).first()

    def wrap_url(self, url, **extra):
        """Wrap a URL for autologin"""
        if self.use_autologin:
            extra.update(sesame.utils.get_parameters(self))

        return "{}?{}".format(url, urlencode(extra))

    def verified_journalist(self):
        return self.organizations.filter(verified_journalist=True).exists()
Beispiel #4
0
class Organization(AvatarMixin, models.Model):
    """Orginization to allow pooled requests and collaboration"""

    objects = OrganizationQuerySet.as_manager()

    uuid = models.UUIDField(
        _("UUID"),
        default=uuid.uuid4,
        editable=False,
        unique=True,
        help_text=_("Uniquely identify the organization across services"),
    )

    name = models.CharField(_("name"),
                            max_length=255,
                            help_text=_("The name of the organization"))
    slug = AutoSlugField(
        _("slug"),
        populate_from="name",
        unique=True,
        help_text=_("A unique slug for use in URLs"),
    )
    created_at = AutoCreatedField(
        _("created at"), help_text=_("When this organization was created"))
    updated_at = AutoLastModifiedField(
        _("updated at"),
        help_text=_("When this organization was last updated"))

    avatar = ImageField(
        _("avatar"),
        upload_to=organization_file_path,
        blank=True,
        help_text=_("An image to represent the organization"),
    )

    users = models.ManyToManyField(
        verbose_name=_("users"),
        to="users.User",
        through="organizations.Membership",
        related_name="organizations",
        help_text=_("The user's in this organization"),
    )

    plan = models.ForeignKey(
        verbose_name=_("plan"),
        to="organizations.Plan",
        on_delete=models.PROTECT,
        related_name="organizations",
        help_text=_("The current plan this organization is subscribed to"),
    )
    next_plan = models.ForeignKey(
        verbose_name=_("next plan"),
        to="organizations.Plan",
        on_delete=models.PROTECT,
        related_name="pending_organizations",
        help_text=_(
            "The pending plan to be updated to on the next billing cycle - "
            "used when downgrading a plan to let the organization finish out a "
            "subscription is paid for"),
    )
    individual = models.BooleanField(
        _("individual organization"),
        default=False,
        help_text=_("This organization is solely for the use of one user"),
    )
    private = models.BooleanField(
        _("private organization"),
        default=False,
        help_text=_(
            "This organization's information and membership is not made public"
        ),
    )

    # Book keeping
    max_users = models.IntegerField(
        _("maximum users"),
        default=5,
        help_text=_("The maximum number of users in this organization"),
    )
    update_on = models.DateField(
        _("date update"),
        null=True,
        blank=True,
        help_text=_("Date when monthly requests are restored"),
    )

    # stripe
    customer_id = models.CharField(
        _("customer id"),
        max_length=255,
        unique=True,
        blank=True,
        null=True,
        help_text=_("The organization's corresponding ID on stripe"),
    )
    subscription_id = models.CharField(
        _("subscription id"),
        max_length=255,
        unique=True,
        blank=True,
        null=True,
        help_text=_(
            "The organization's corresponding subscription ID on stripe"),
    )
    payment_failed = models.BooleanField(
        _("payment failed"),
        default=False,
        help_text=_(
            "A payment for this organization has failed - they should update their "
            "payment information"),
    )

    default_avatar = static("images/avatars/organization.png")

    class Meta:
        ordering = ("slug", )

    def __str__(self):
        if self.individual:
            return f"{self.name} (Individual)"
        else:
            return self.name

    def save(self, *args, **kwargs):
        # pylint: disable=arguments-differ
        with transaction.atomic():
            super().save(*args, **kwargs)
            transaction.on_commit(
                lambda: send_cache_invalidations("organization", self.uuid))

    def get_absolute_url(self):
        """The url for this object"""
        if self.individual:
            # individual orgs do not have a detail page, use the user's page
            return self.user.get_absolute_url()
        else:
            return reverse("organizations:detail", kwargs={"slug": self.slug})

    @property
    def email(self):
        """Get an email for this organization"""
        if self.individual:
            return self.user.email

        receipt_email = self.receipt_emails.first()
        if receipt_email:
            return receipt_email.email

        return self.users.filter(memberships__admin=True).first().email

    # User Management
    def has_admin(self, user):
        """Is the given user an admin of this organization"""
        return self.users.filter(pk=user.pk, memberships__admin=True).exists()

    def has_member(self, user):
        """Is the user a member?"""
        return self.users.filter(pk=user.pk).exists()

    def user_count(self):
        """Count the number of users, including pending invitations"""
        return self.users.count() + self.invitations.get_pending().count()

    def add_creator(self, user):
        """Add user as the creator of the organization"""
        # add creator to the organization as an admin by default
        self.memberships.create(user=user, admin=True)
        # add the creators email as a receipt recipient by default
        # agency users may not have an email
        if user.email:
            self.receipt_emails.create(email=user.email)

    @mproperty
    def reference_name(self):
        if self.individual:
            return _("Your account")
        return self.name

    # Payment Management
    @mproperty
    def customer(self):
        """Retrieve the customer from Stripe or create one if it doesn't exist"""
        if self.customer_id:
            try:
                return stripe.Customer.retrieve(self.customer_id)
            except stripe.error.InvalidRequestError:  # pragma: no cover
                pass

        customer = stripe.Customer.create(description=self.name,
                                          email=self.email)
        self.customer_id = customer.id
        self.save()
        return customer

    @mproperty
    def subscription(self):
        if self.subscription_id:
            try:
                return stripe.Subscription.retrieve(self.subscription_id)
            except stripe.error.InvalidRequestError:  # pragma: no cover
                return None
        else:
            return None

    @mproperty
    def card(self):
        """Retrieve the customer's default credit card on file, if there is one"""
        if self.customer.default_source:
            source = self.customer.sources.retrieve(
                self.customer.default_source)
            if source.object == "card":
                return source
            else:
                return None
        else:
            return None

    @property
    def card_display(self):
        if self.customer_id and self.card:
            return f"{self.card.brand}: {self.card.last4}"
        else:
            return ""

    def save_card(self, token):
        self.payment_failed = False
        self.save()
        self.customer.source = token
        self.customer.save()
        send_cache_invalidations("organization", self.uuid)

    def set_subscription(self, token, plan, max_users, user):
        if self.individual:
            max_users = 1
        if token:
            self.save_card(token)

        # store so we can log
        from_plan, from_next_plan, from_max_users = (
            self.plan,
            self.next_plan,
            self.max_users,
        )

        if self.plan.free() and not plan.free():
            # create a subscription going from free to non-free
            self._create_subscription(self.customer, plan, max_users)
        elif not self.plan.free() and plan.free():
            # cancel a subscription going from non-free to free
            self._cancel_subscription(plan)
        elif not self.plan.free() and not plan.free():
            # modify a subscription going from non-free to non-free
            self._modify_subscription(self.customer, plan, max_users)
        else:
            # just change the plan without touching stripe if going free to free
            self._modify_plan(plan, max_users)

        self.change_logs.create(
            user=user,
            reason=OrganizationChangeLog.UPDATED,
            from_plan=from_plan,
            from_next_plan=from_next_plan,
            from_max_users=from_max_users,
            to_plan=self.plan,
            to_next_plan=self.next_plan,
            to_max_users=self.max_users,
        )

    @transaction.atomic
    def _create_subscription(self, customer, plan, max_users):
        """Create a subscription on stripe for the new plan"""
        def stripe_create_subscription():
            """Call this after the current transaction is committed,
            to ensure the organization is in the database before we
            receive the charge succeeded webhook
            """
            if not customer.email:  # pragma: no cover
                customer.email = self.email
                customer.save()
            subscription = customer.subscriptions.create(
                items=[{
                    "plan": plan.stripe_id,
                    "quantity": max_users
                }],
                billing="send_invoice"
                if plan.annual else "charge_automatically",
                days_until_due=30 if plan.annual else None,
            )
            self.subscription_id = subscription.id
            self.save()

        self.plan = plan
        self.next_plan = plan
        self.max_users = max_users
        self.update_on = date.today() + relativedelta(months=1)
        self.save()
        transaction.on_commit(stripe_create_subscription)

    def _cancel_subscription(self, plan):
        """Cancel the subscription at period end on stripe for the new plan"""
        if self.subscription is not None:
            self.subscription.cancel_at_period_end = True
            self.subscription.save()
            self.subscription_id = None
        else:  # pragma: no cover
            logger.error(
                "Attempting to cancel subscription for organization: %s %s "
                "but no subscription was found",
                self.name,
                self.pk,
            )

        self.next_plan = plan
        self.save()

    def _modify_subscription(self, customer, plan, max_users):
        """Modify the subscription on stripe for the new plan"""

        # if we are trying to modify the subscription, one should already exist
        # if for some reason it does not, then just create a new one
        if self.subscription is None:  # pragma: no cover
            logger.warning(
                "Trying to modify non-existent subscription for organization - %d - %s",
                self.pk,
                self,
            )
            self._create_subscription(customer, plan, max_users)
            return

        if not customer.email:
            customer.email = self.email
            customer.save()
        stripe.Subscription.modify(
            self.subscription_id,
            cancel_at_period_end=False,
            items=[{
                # pylint: disable=unsubscriptable-object
                "id": self.subscription["items"]["data"][0].id,
                "plan": plan.stripe_id,
                "quantity": max_users,
            }],
            billing="send_invoice" if plan.annual else "charge_automatically",
            days_until_due=30 if plan.annual else None,
        )

        self._modify_plan(plan, max_users)

    def _modify_plan(self, plan, max_users):
        """Modify the plan without affecting stripe, for free to free transitions"""

        if plan.feature_level >= self.plan.feature_level:
            # upgrade immediately
            self.plan = plan
            self.next_plan = plan
        else:
            # downgrade at end of billing cycle
            self.next_plan = plan

        self.max_users = max_users

        self.save()

    def subscription_cancelled(self):
        """The subsctription was cancelled due to payment failure"""
        free_plan = Plan.objects.get(slug="free")
        self.change_logs.create(
            reason=OrganizationChangeLog.FAILED,
            from_plan=self.plan,
            from_next_plan=self.next_plan,
            from_max_users=self.max_users,
            to_plan=free_plan,
            to_next_plan=free_plan,
            to_max_users=self.max_users,
        )
        self.subscription_id = None
        self.plan = self.next_plan = free_plan
        self.save()

    def charge(self,
               amount,
               description,
               fee_amount=0,
               token=None,
               save_card=False):
        """Charge the organization and optionally save their credit card"""
        if save_card:
            self.save_card(token)
            token = None
        charge = Charge.objects.make_charge(self, token, amount, fee_amount,
                                            description)
        return charge

    def set_receipt_emails(self, emails):
        new_emails = set(emails)
        old_emails = {r.email for r in self.receipt_emails.all()}
        self.receipt_emails.filter(email__in=old_emails - new_emails).delete()
        ReceiptEmail.objects.bulk_create([
            ReceiptEmail(organization=self, email=e)
            for e in new_emails - old_emails
        ])
Beispiel #5
0
class Organization(AvatarMixin, models.Model):
    """Orginization to allow pooled requests and collaboration"""

    objects = OrganizationQuerySet.as_manager()

    uuid = models.UUIDField(
        _("UUID"),
        default=uuid.uuid4,
        editable=False,
        unique=True,
        help_text=_("Uniquely identify the organization across services"),
    )

    name = models.CharField(_("name"),
                            max_length=255,
                            help_text=_("The name of the organization"))
    slug = AutoSlugField(
        _("slug"),
        populate_from="name",
        unique=True,
        help_text=_("A unique slug for use in URLs"),
    )
    created_at = AutoCreatedField(
        _("created at"), help_text=_("When this organization was created"))
    updated_at = AutoLastModifiedField(
        _("updated at"),
        help_text=_("When this organization was last updated"))

    avatar = ImageField(
        _("avatar"),
        upload_to=organization_file_path,
        blank=True,
        help_text=_("An image to represent the organization"),
    )

    users = models.ManyToManyField(
        verbose_name=_("users"),
        to="users.User",
        through="organizations.Membership",
        related_name="organizations",
        help_text=_("The user's in this organization"),
    )

    subtypes = models.ManyToManyField(
        verbose_name=_("subtypes"),
        to="organizations.OrganizationSubtype",
        related_name="organizations",
        help_text=_("The subtypes of this organization"),
        blank=True,
    )

    # remove these
    _plan = models.ForeignKey(
        verbose_name=_("plan"),
        to="organizations.Plan",
        on_delete=models.PROTECT,
        related_name="+",
        help_text=_("The current plan this organization is subscribed to"),
        blank=True,
        null=True,
        db_column="plan_id",
    )
    next_plan = models.ForeignKey(
        verbose_name=_("next plan"),
        to="organizations.Plan",
        on_delete=models.PROTECT,
        related_name="pending_organizations",
        help_text=_(
            "The pending plan to be updated to on the next billing cycle - "
            "used when downgrading a plan to let the organization finish out a "
            "subscription is paid for"),
        blank=True,
        null=True,
    )
    # end remove these

    plans = models.ManyToManyField(
        verbose_name=_("plans"),
        to="organizations.Plan",
        through="organizations.Subscription",
        related_name="organizations",
        help_text=_("Plans this organization is subscribed to"),
        blank=True,
    )

    individual = models.BooleanField(
        _("individual organization"),
        default=False,
        help_text=_("This organization is solely for the use of one user"),
    )
    private = models.BooleanField(
        _("private organization"),
        default=False,
        help_text=_(
            "This organization's information and membership is not made public"
        ),
    )
    verified_journalist = models.BooleanField(
        _("verified journalist"),
        default=False,
        help_text=_(
            "This organization is a verified jorunalistic organization"),
    )

    # Book keeping
    max_users = models.IntegerField(
        _("maximum users"),
        default=5,
        help_text=_("The maximum number of users in this organization"),
    )
    # this moved to subscription, remove
    update_on = models.DateField(
        _("date update"),
        null=True,
        blank=True,
        help_text=_("Date when monthly requests are restored"),
    )

    # stripe
    # moved to customer, remove
    customer_id = models.CharField(
        _("customer id"),
        max_length=255,
        unique=True,
        blank=True,
        null=True,
        help_text=_("The organization's corresponding ID on stripe"),
    )
    # move to subscription, remove
    subscription_id = models.CharField(
        _("subscription id"),
        max_length=255,
        unique=True,
        blank=True,
        null=True,
        help_text=_(
            "The organization's corresponding subscription ID on stripe"),
    )
    # should this be moved to customer?
    payment_failed = models.BooleanField(
        _("payment failed"),
        default=False,
        help_text=_(
            "A payment for this organization has failed - they should update their "
            "payment information"),
    )

    default_avatar = static("images/avatars/organization.png")

    class Meta:
        ordering = ("slug", )

    def __str__(self):
        if self.individual:
            return f"{self.name} (Individual)"
        else:
            return self.name

    def save(self, *args, **kwargs):
        # pylint: disable=arguments-differ
        with transaction.atomic():
            super().save(*args, **kwargs)
            transaction.on_commit(
                lambda: send_cache_invalidations("organization", self.uuid))

    def get_absolute_url(self):
        """The url for this object"""
        if self.individual:
            # individual orgs do not have a detail page, use the user's page
            return self.user.get_absolute_url()
        else:
            return reverse("organizations:detail", kwargs={"slug": self.slug})

    @property
    def email(self):
        """Get an email for this organization"""
        if self.individual:
            return self.user.email

        receipt_email = self.receipt_emails.first()
        if receipt_email:
            return receipt_email.email

        user = self.users.filter(memberships__admin=True).first()
        if user:
            return user.email

        return None

    # User Management
    def has_admin(self, user):
        """Is the given user an admin of this organization"""
        return self.users.filter(pk=user.pk, memberships__admin=True).exists()

    def has_member(self, user):
        """Is the user a member?"""
        return self.users.filter(pk=user.pk).exists()

    def user_count(self):
        """Count the number of users, including pending invitations"""
        return self.users.count() + self.invitations.get_pending().count()

    def add_creator(self, user):
        """Add user as the creator of the organization"""
        # add creator to the organization as an admin by default
        self.memberships.create(user=user, admin=True)
        # add the creators email as a receipt recipient by default
        # agency users may not have an email
        if user.email:
            self.receipt_emails.create(email=user.email)

    @mproperty
    def reference_name(self):
        if self.individual:
            return _("Your account")
        return self.name

    # Payment Management

    def customer(self, stripe_account):
        """Retrieve the customer from Stripe or create one if it doesn't exist"""
        customer, _ = self.customers.get_or_create(
            stripe_account=stripe_account)
        return customer

    def save_card(self, token, stripe_account):
        self.payment_failed = False
        self.save()
        self.customer(stripe_account).save_card(token)
        send_cache_invalidations("organization", self.uuid)

    def remove_card(self, stripe_account):
        self.customer(stripe_account).remove_card()
        send_cache_invalidations("organization", self.uuid)

    @mproperty
    def plan(self):
        return self.plans.muckrock().first()

    @mproperty
    def subscription(self):
        return self.subscriptions.muckrock().first()

    def create_subscription(self, token, plan):
        if token:
            self.save_card(token, plan.stripe_account)

        customer = self.customer(plan.stripe_account).stripe_customer
        if not customer.email:
            customer.email = self.email
            customer.save()

        self.subscriptions.start(organization=self, plan=plan)

    def set_subscription(self, token, plan, max_users, user):
        if self.individual:
            max_users = 1
        if token and plan:
            self.save_card(token, plan.stripe_account)

        # store so we can log
        from_plan, from_max_users = (self.plan, self.max_users)

        self.max_users = max_users
        self.save()

        if not self.plan and plan:
            # create a subscription going from no plan to plan
            self.create_subscription(token, plan)
        elif self.plan and not plan:
            # cancel a subscription going from plan to no plan
            self.subscription.cancel()
        elif self.plan and plan:
            # modify the subscription
            self.subscription.modify(plan)

        self.change_logs.create(
            user=user,
            reason=ChangeLogReason.updated,
            from_plan=from_plan,
            from_max_users=from_max_users,
            to_plan=plan,
            to_max_users=self.max_users,
        )

    def subscription_cancelled(self):
        """The subsctription was cancelled due to payment failure"""
        self.change_logs.create(
            reason=ChangeLogReason.failed,
            from_plan=self.plan,
            from_max_users=self.max_users,
            to_max_users=self.max_users,
        )
        self.subscription.delete()

    def charge(
        self,
        amount,
        description,
        fee_amount=0,
        token=None,
        save_card=False,
        stripe_account=StripeAccounts.muckrock,
    ):
        """Charge the organization and optionally save their credit card"""
        if save_card:
            self.save_card(token, stripe_account)
            token = None
        charge = Charge.objects.make_charge(self, token, amount, fee_amount,
                                            description, stripe_account)
        return charge

    def set_receipt_emails(self, emails):
        new_emails = set(emails)
        old_emails = {r.email for r in self.receipt_emails.all()}
        self.receipt_emails.filter(email__in=old_emails - new_emails).delete()
        ReceiptEmail.objects.bulk_create([
            ReceiptEmail(organization=self, email=e)
            for e in new_emails - old_emails
        ])