Commit 4dec867e authored by Birin Sanchez's avatar Birin Sanchez

Add forced_update method with several changes:

* Move common methods from limitmonitor/task_resources/subscription.py
  to limitmonitor/common.py
* Add disable() and is_enabled() methods for Limit class.
* Rise exception when we cannot terminate a Tunnel account.
* Add get_enabled_limits() method for User class.
* Remove redundant code in process_pending_registrations_user.
* Use limit.is_enabled() instead of limit.is_active.
Signed-off-by: Birin Sanchez's avatarBirin Sanchez <birin.sanchez@puri.sm>
parent f0e978f9
Pipeline #11473 passed with stage
in 42 seconds
import logging
import datetime
from django.conf import settings
from django.utils import timezone
from purist.models import get_woo_connection
from purist.limitmonitor import ParserContainer
from limitmonitor.models import ExternalCredit, ExternalBundle
from limitmonitor.tunnel import TunnelManager
logger = logging.getLogger(__name__)
def get_username_from_woo_customer_id(customer_id, woo=None):
if woo is None:
woo = get_woo_connection()
try:
query = "customers/" + str(customer_id)
result = woo.get(query).json()
return result["username"]
except Exception:
logger.exception(
"Could not retrieve username for customer_id " + str(customer_id))
return "invalid"
def parse_subscription(json_entry):
subscription_id = str(json_entry["id"])
# validation
if len(json_entry["line_items"]) != 1:
raise Exception("Too many line items in subscription %s" % (id, ))
line_item = json_entry["line_items"][0]
quantity = line_item["quantity"]
if quantity != 1:
raise Exception("Bad quantity %s in subscription %s"
% (quantity, id, ))
# calculate next renewal date
next_renewal_naive = datetime.datetime.strptime(
json_entry["next_payment_date"], "%Y-%m-%dT%H:%M:%S")
next_renewal = timezone.make_aware(next_renewal_naive)
# get account name
username = get_username_from_woo_customer_id(json_entry["customer_id"])
# bundle_key value is different depending on the subscription
# having variations or not
if line_item.get('variation_id') != 0:
bundle_key = line_item.get('variation_id')
else:
bundle_key = line_item.get('product_id')
# create result
result = {
"parser": ParserContainer.WOO_SUBSCRIPTION_V1,
"external_key": subscription_id,
"label": json_entry["number"],
"bundle_key": bundle_key,
"bundle_label": str(line_item["name"]),
"quantity": 1,
"account": username,
"next_renewal": next_renewal,
"status": json_entry["status"]
}
return result
def get_user_subscriptions(user):
user_wc_id = user.get_woocommerce_id()
if user_wc_id is None:
# The user does not have WC account
return None
woo = get_woo_connection()
subscriptions = woo.get(
'subscriptions?customer={}'.format(user_wc_id)).json()
if len(subscriptions) >= 1:
return subscriptions
else:
return None
def subscription_was_processed(parsed_sub, overwrite=False):
"""Returns an ExternalCredit from the DB if the subscription was
already processed. If the subscription was not already processed
it returns a new ExternalCredit created from parsed_sub data with
is_converted=False.
If overwrite is set to True the existing ExternalCredit is
overwritten with new values from parsed_sub.
"""
try:
ext_cred = ExternalCredit.objects.get(
external_key=parsed_sub['external_key'])
if overwrite:
ext_cred.parser = parsed_sub["parser"]
ext_cred.external_key = parsed_sub["external_key"]
ext_cred.label = parsed_sub["label"]
ext_cred.bundle_key = parsed_sub["bundle_key"]
ext_cred.bundle_label = parsed_sub["bundle_label"]
ext_cred.quantity = parsed_sub["quantity"]
ext_cred.account_name = parsed_sub["account"]
ext_cred.additional_data = "None"
ext_cred.is_converted = False
except ExternalCredit.DoesNotExist:
ext_cred = ExternalCredit(
parser=parsed_sub["parser"],
external_key=parsed_sub["external_key"],
label=parsed_sub["label"],
bundle_key=parsed_sub["bundle_key"],
bundle_label=parsed_sub["bundle_label"],
quantity=parsed_sub["quantity"],
account_name=parsed_sub["account"],
additional_data="None",
is_converted=False,
)
return ext_cred
def get_services_from_bundle(bundle_key):
ext_buns = ExternalBundle.objects.filter(external_key=bundle_key)
result = []
for ext_bun in ext_buns:
result.append(ext_bun.service)
return result
def forced_update(user):
subscriptions = get_user_subscriptions(user)
if subscriptions is None:
logger.info('User {} has no subscriptions.'.format(user.username))
return
for sub in subscriptions:
parsed_sub = parse_subscription(sub)
if parsed_sub['bundle_key'] not in settings.WOOSUB1_PRODUCT_LIST:
logger.info('Subscription {} does not belong '
'to Librem One'.format(parsed_sub['bundle_key']))
continue # Skip this subscription as is not a product we care of
ext_cred = subscription_was_processed(parsed_sub, overwrite=True)
error = 'Subscription successfully enabled through forced_update'
ext_cred.updated_date = timezone.now()
bndl_srvs = set(get_services_from_bundle(parsed_sub['bundle_key']))
cur_srvs = set(user.get_enabled_limits())
if parsed_sub['status'] == 'active':
srv_to_enable = bndl_srvs - cur_srvs
srv_to_disable = cur_srvs - bndl_srvs
srv_to_reenable = bndl_srvs & cur_srvs
is_pending = False
is_converted = True
try:
# Disable services
for srvn in srv_to_disable:
serv = user.limit_set.get(service=srvn)
serv.disable()
serv.save()
# Enable services
for srvn in srv_to_enable | srv_to_reenable:
serv = user.limit_set.get(service=srvn)
serv.enable(parsed_sub['next_renewal'])
serv.save()
except TunnelManager.TerminateAccountError as e1:
error = '{}: {}'.format(ext_cred.external_key, repr(e1))
is_pending = True
is_converted = False
except Exception as e2:
is_pending = True
is_converted = False
error = '{}: {}'.format(ext_cred.external_key, repr(e2))
finally:
if hasattr(user, 'chosenreward'):
user.chosenreward.is_pending = is_pending
user.chosenreward.save()
ext_cred.error_message = error
ext_cred.is_converted = is_converted
ext_cred.save()
# TODO: process other subscription statuses
else:
logger.info('Subscription {} for user {} status is: {}'.format(
parsed_sub['label'], user.username, parsed_sub['status']))
from django.core.management.base import BaseCommand
from django.core.validators import EmailValidator, ValidationError
from django.conf import settings
from limitmonitor.common import forced_update
from purist.models import User
class Command(BaseCommand):
help = 'Update user enabled services according to WooCommerce \
subscription.'
def add_arguments(self, parser):
parser.add_argument('email',
type=str,
help='This is the email of the user that will get \
services updated according to subscription.')
def handle(self, *args, **options):
ev = EmailValidator()
email = options['email']
try:
ev(email)
except ValidationError as e:
self.stdout.write(repr(e))
return
(username, domain) = email.split('@')
if domain != settings.SITE_DOMAIN:
msg = '{} does not belong to {} domain'
self.stdout.write(msg.format(email, settings.SITE_DOMAIN))
return
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
msg = 'User {} does not exist.'.format(email)
self.stdout.write(msg)
return
forced_update(user)
msg = 'Services update forced on user {}.'.format(email)
self.stdout.write(msg)
......@@ -40,7 +40,7 @@ class Limit(models.Model):
def active_label(self):
if self.is_active:
if self.is_enabled():
return "Yes"
else:
return "No"
......@@ -156,9 +156,42 @@ class Limit(models.Model):
self.is_active = True
else:
raise Exception(
"Invalid activation attempt. Service not recognised.")
"Invalid attempt to enable. Service not recognised.")
self.renewal_date = ren_date
def disable(self):
if self.service == settings.LM_SERVICES.TUNNEL:
self.user.terminate_tunnel_account()
self.is_active = False
elif self.service == settings.LM_SERVICES.CHAT:
self.user.remove_ldap_group("chat")
self.is_active = False
elif self.service == settings.LM_SERVICES.MAIL:
self.user.remove_ldap_group("mail")
self.is_active = False
elif self.service == settings.LM_SERVICES.SOCIAL:
self.user.remove_ldap_group("social")
self.is_active = False
elif self.service == settings.LM_SERVICES.XMPP:
self.user.remove_ldap_group("xmpp")
self.is_active = False
elif self.service == settings.LM_SERVICES.GROUP:
self.is_active = False
else:
raise Exception(
"Invalid attempt to disable. Service not recognised.")
self.renewal_date = None
def is_enabled(self):
"""If the limit is not TUNNEL returns the value of is_acitve. For
TUNNEL limit returns True if there is time remaining to use
the service.
"""
if self.service == settings.LM_SERVICES.TUNNEL:
return self.remaining_use_time().total_seconds() > 0
return self.is_active
class ExternalBundle(models.Model):
"""Class used to represent external bundles that exist in external
entities like WooCommerce
......
import datetime
from django.utils import timezone
from django.conf import settings
from purist.models import get_woo_connection
from celery.utils.log import get_task_logger
from limitmonitor.models import ExternalCredit, ExternalBundle
from cart.models import ChosenReward
from limitmonitor.common import parse_subscription, get_user_subscriptions, \
subscription_was_processed, get_services_from_bundle
logger = get_task_logger(__name__)
def get_username_from_woo_customer_id(customer_id, woo=None):
if woo is None:
woo = get_woo_connection()
try:
query = "customers/" + str(customer_id)
result = woo.get(query).json()
return result["username"]
except Exception:
logger.exception(
"Could not retrieve username for customer_id " + str(customer_id))
return "invalid"
def parse_subscription(json_entry):
subscription_id = str(json_entry["id"])
# validation
if len(json_entry["line_items"]) != 1:
raise Exception("Too many line items in subscription %s" % (id, ))
line_item = json_entry["line_items"][0]
quantity = line_item["quantity"]
if quantity != 1:
raise Exception("Bad quantity %s in subscription %s"
% (quantity, id, ))
# calculate next renewal date
next_renewal_naive = datetime.datetime.strptime(
json_entry["next_payment_date"], "%Y-%m-%dT%H:%M:%S")
next_renewal = timezone.make_aware(next_renewal_naive)
# get account name
username = get_username_from_woo_customer_id(json_entry["customer_id"])
# bundle_key value is different depending on the subscription
# having variations or not
if line_item.get('variation_id') != 0:
bundle_key = line_item.get('variation_id')
else:
bundle_key = line_item.get('product_id')
# create result
result = {
# FIXME: this is WOO_SUBSCRIPTION_V1 from purist.limitmonitor
"parser": 2,
"external_key": subscription_id,
"label": json_entry["number"],
"bundle_key": bundle_key,
"bundle_label": str(line_item["name"]),
"quantity": 1,
"account": username,
"next_renewal": next_renewal,
"status": json_entry["status"]
}
return result
def get_user_subscriptions(user):
user_wc_id = user.get_woocommerce_id()
if user_wc_id is None:
# The user does not have WC account
return None
woo = get_woo_connection()
subscriptions = woo.get(
'subscriptions?customer={}'.format(user_wc_id)).json()
if len(subscriptions) >= 1:
return subscriptions
else:
return None
def subscription_was_processed(parsed_sub):
"""Returns an ExternalCredit fron the DB if the subscription was
already processed. If the subscription was not already
processed it returns a new ExternalCredit created from parsed_sub
data with is_converted=False
"""
try:
ext_cred = ExternalCredit.objects.get(
external_key=parsed_sub['external_key'])
except ExternalCredit.DoesNotExist:
ext_cred = ExternalCredit(
parser=parsed_sub["parser"],
external_key=parsed_sub["external_key"],
label=parsed_sub["label"],
bundle_key=parsed_sub["bundle_key"],
bundle_label=parsed_sub["bundle_label"],
quantity=parsed_sub["quantity"],
account_name=parsed_sub["account"],
additional_data="None",
is_converted=False,
)
return ext_cred
def get_services_from_bundle(bundle_key):
ext_buns = ExternalBundle.objects.filter(external_key=bundle_key)
result = []
for ext_bun in ext_buns:
result.append(ext_bun.service)
return result
def process_pending_registrations_user(user):
subscriptions = get_user_subscriptions(user)
......@@ -153,10 +39,7 @@ def process_pending_registrations_user(user):
ext_cred.is_converted = True
is_pending = False
except Exception as e:
error = repr(e)
ext_cred.error_message = '{}: {}'.format(
ext_cred.external_key,
error)
error = '{}: {}'.format(ext_cred.external_key, repr(e))
ext_cred.is_converted = False
is_pending = True
finally:
......
......@@ -6,6 +6,14 @@ BASE_URL = '{host}/resellers/api/v1/{operation}'
class TunnelManager(object):
class TerminateAccountError(Exception):
"""Raised when we cannot terminate an account. This need to be
propagated through the stack so the program using
terminate_account() can decide what to do.
"""
pass
def __init__(self, partner_id, secret, host):
self.partner_id = partner_id
......@@ -50,7 +58,8 @@ class TunnelManager(object):
r = requests.post(url, data=payload)
if r.status_code == 200:
return r.json()
return None
raise self.TerminateAccountError(
"Account {} couldn't be terminated.".format(username))
def find_account(self):
pass
......
......@@ -21,7 +21,7 @@ def userlimit(request):
none_limit = True
for limit in limits:
label = limit.service_label().upper()
has_limit[label] = limit.is_active
has_limit[label] = limit.is_enabled()
# Default action for services
action_function[label] = '.'
if limit.is_active:
......
......@@ -421,6 +421,14 @@ class User(AbstractUser):
return self.username + '@' + settings.EMAIL_DOMAIN
return self.email
def get_enabled_limits(self):
limits = self.limit_set.all()
result = []
for l in limits:
if l.is_enabled():
result.append(l.service)
return result
def encrypt(data):
# Encrypt data with Fernet
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment