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), )
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
def perform_destroy(self, instance): set_change_reason(instance, self.get_change_reason()) super().perform_destroy(instance)
def perform_update(self, serializer): set_change_reason(serializer.instance, self.get_change_reason()) obj = serializer.save() return obj
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
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)