Beispiel #1
0
    def ban_owner(self, request, queryset):
        """
        Ban project owner.

        This will only ban single owners, because a malicious user could add a
        user as a co-owner of the project. We don't want to induce and
        collateral damage when flagging users.
        """
        total = 0
        for project in queryset:
            if project.users.count() == 1:
                user = project.users.first()
                user.profile.banned = True
                set_change_reason(user.profile, self.get_change_reason())
                user.profile.save()
                total += 1
            else:
                messages.add_message(
                    request,
                    messages.ERROR,
                    'Project has multiple owners: {}'.format(project),
                )
        if total == 0:
            messages.add_message(request, messages.ERROR, 'No users banned')
        else:
            messages.add_message(
                request,
                messages.INFO,
                'Banned {} user(s)'.format(total),
            )
Beispiel #2
0
 def save(self, commit=True):
     first_name = self.cleaned_data.pop('first_name', None)
     last_name = self.cleaned_data.pop('last_name', None)
     profile = super().save(commit=commit)
     if commit:
         user = profile.user
         user.first_name = first_name
         user.last_name = last_name
         # SimpleHistoryModelForm isn't used here
         # because the model of this form is `UserProfile`, not `User`.
         set_change_reason(user, self.get_change_reason())
         user.save()
     return profile
Beispiel #3
0
 def perform_destroy(self, instance):
     set_change_reason(instance, self.get_change_reason())
     super().perform_destroy(instance)
Beispiel #4
0
 def perform_update(self, serializer):
     set_change_reason(serializer.instance, self.get_change_reason())
     obj = serializer.save()
     return obj
Beispiel #5
0
    def update_from_stripe(self, *, rtd_subscription, stripe_subscription):
        """
        Update the RTD subscription object with the information of the stripe subscription.

        :param subscription: Subscription object to update.
        :param stripe_subscription: Stripe subscription object from API
        :type stripe_subscription: stripe.Subscription
        """
        # Documentation doesn't say what will be this value once the
        # subscription is ``canceled``. I'm assuming that ``current_period_end``
        # will have the same value than ``ended_at``
        # https://stripe.com/docs/api/subscriptions/object?lang=python#subscription_object-current_period_end
        start_date = getattr(stripe_subscription, 'current_period_start', None)
        end_date = getattr(stripe_subscription, 'current_period_end', None)

        try:
            start_date = timezone.make_aware(
                datetime.fromtimestamp(start_date), )
            end_date = timezone.make_aware(datetime.fromtimestamp(end_date), )
        except TypeError:
            log.error(
                'Stripe subscription invalid date.',
                start_date=start_date,
                end_date=end_date,
                stripe_subscription=stripe_subscription.id,
            )
            start_date = None
            end_date = None
            trial_end_date = None

        rtd_subscription.status = stripe_subscription.status

        # This should only happen if an existing user creates a new subscription,
        # after their previous subscription was cancelled.
        if stripe_subscription.id != rtd_subscription.stripe_id:
            log.info(
                'Replacing stripe subscription.',
                old_stripe_subscription=rtd_subscription.stripe_id,
                new_stripe_subscription=stripe_subscription.id,
            )
            rtd_subscription.stripe_id = stripe_subscription.id

        # Update trial end date if it's present
        trial_end_date = getattr(stripe_subscription, 'trial_end', None)
        if trial_end_date:
            try:
                trial_end_date = timezone.make_aware(
                    datetime.fromtimestamp(trial_end_date), )
                rtd_subscription.trial_end_date = trial_end_date
            except TypeError:
                log.error(
                    'Stripe subscription trial end date invalid. ',
                    trial_end_date=trial_end_date,
                    stripe_subscription=stripe_subscription.id,
                )

        # Update the plan in case it was changed from the Portal.
        # Try our best to match a plan that is not custom. This mostly just
        # updates the UI now that we're using the Stripe Portal. A miss here
        # just won't update the UI, but this shouldn't happen for most users.
        from readthedocs.subscriptions.models import Plan
        try:
            plan = (
                Plan.objects
                # Exclude "custom" here, as we historically reused Stripe plan
                # id for custom plans. We don't have a better attribute to
                # filter on here.
                .exclude(slug__contains='custom').exclude(
                    name__icontains='Custom').get(
                        stripe_id=stripe_subscription.plan.id))
            rtd_subscription.plan = plan
        except (Plan.DoesNotExist, Plan.MultipleObjectsReturned):
            log.error(
                'Plan lookup failed, skipping plan update.',
                stripe_subscription=stripe_subscription.id,
                stripe_plan=stripe_subscription.plan.id,
            )

        if stripe_subscription.status == 'canceled':
            # Remove ``stripe_id`` when canceled so the customer can
            # re-subscribe using our form.
            rtd_subscription.stripe_id = None

        elif stripe_subscription.status == 'active' and end_date:
            # Save latest active date (end_date) to notify owners about their subscription
            # is ending and disable this organization after N days of unpaid. We check for
            # ``active`` here because Stripe will continue sending updates for the
            # subscription, with a new ``end_date``, even after the subscription enters
            # an unpaid state.
            rtd_subscription.end_date = end_date

        elif stripe_subscription.status == 'past_due' and start_date:
            # When Stripe marks the subscription as ``past_due``,
            # it means the usage of RTD service for the current period/month was not paid at all.
            # At this point, we need to update our ``end_date`` to the last period the customer paid
            # (which is the start date of the current ``past_due`` period --it could be the end date
            # of the trial or the end date of the last paid period).
            rtd_subscription.end_date = start_date

        klass = self.__class__.__name__
        change_reason = f'origin=stripe-subscription class={klass}'

        # Ensure that the organization is in the correct state.
        # We want to always ensure the organization is never disabled
        # if the subscription is valid.
        organization = rtd_subscription.organization
        if stripe_subscription.status == 'active' and organization.disabled:
            log.warning(
                'Re-enabling organization with valid subscription.',
                organization_slug=organization.slug,
                stripe_subscription=rtd_subscription.id,
            )
            organization.disabled = False
            set_change_reason(organization, change_reason)
            organization.save()

        set_change_reason(rtd_subscription, change_reason)
        rtd_subscription.save()
        return rtd_subscription
Beispiel #6
0
 def form_valid(self, form):
     user = self.get_object()
     logout(self.request)
     set_change_reason(user, self.get_change_reason())
     user.delete()
     return super().form_valid(form)