-
Notifications
You must be signed in to change notification settings - Fork 36
Sprint2__Subscriber Features 2 of 2 (Client dependent) #44
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,22 +20,23 @@ | |
import time | ||
import uuid | ||
|
||
import django.utils.timezone | ||
import itsdangerous | ||
import pytz | ||
import stripe | ||
from django.conf import settings | ||
from django.contrib.auth.models import Group, User | ||
from django.contrib.gis.db import models as geomodels | ||
from django.core.validators import MinValueValidator | ||
from django.contrib.postgres.fields import ArrayField | ||
from django.core.exceptions import ValidationError | ||
from django.core.validators import MinValueValidator | ||
from django.db import connection | ||
from django.db import models | ||
from django.db import transaction | ||
from django.db.models import F | ||
from django.db.models.signals import post_save | ||
from django.db.models.signals import post_save, pre_save | ||
from guardian.shortcuts import (assign_perm, get_users_with_perms) | ||
from rest_framework.authtoken.models import Token | ||
import django.utils.timezone | ||
import itsdangerous | ||
import pytz | ||
import stripe | ||
|
||
from ccm.common import crdt, logger | ||
from ccm.common.currency import humanize_credits, CURRENCIES | ||
|
@@ -47,7 +48,6 @@ | |
|
||
stripe.api_key = settings.STRIPE_API_KEY | ||
|
||
|
||
# These UsageEvent kinds do not count towards subscriber activity. | ||
NON_ACTIVITIES = ( | ||
'deactivate_number', 'deactivate_subscriber', 'add_money', | ||
|
@@ -58,6 +58,9 @@ | |
OUTBOUND_ACTIVITIES = ( | ||
'outside_call', 'outside_sms', 'local_call', 'local_sms', | ||
) | ||
# These UsageEvent events are not allowed block the Subscriber if repeated | ||
# for 3 times | ||
INVALID_EVENTS = ('error_call', 'error_sms') | ||
|
||
|
||
class UserProfile(models.Model): | ||
|
@@ -525,6 +528,19 @@ class Subscriber(models.Model): | |
# When toggled, this will protect a subsriber from getting "vacuumed." You | ||
# can still delete subs with the usual "deactivate" button. | ||
prevent_automatic_deactivation = models.BooleanField(default=False) | ||
# Block subscriber if repeated unauthorized events. | ||
is_blocked = models.BooleanField(default=False) | ||
block_reason = models.TextField(default='No reason to block yet!', | ||
max_length=255) | ||
block_time = models.DateTimeField(null=True, blank=True) | ||
|
||
class Meta: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are there new permissions introduced here? |
||
default_permissions = () | ||
permissions = ( | ||
('view_subscriber', 'View subscriber list'), | ||
('change_subscriber', 'Edit subscriber'), | ||
('deactive_subscriber', 'Deactive subscriber'), | ||
) | ||
|
||
@classmethod | ||
def update_balance(cls, imsi, other_bal): | ||
|
@@ -765,7 +781,7 @@ class UsageEvent(models.Model): | |
downloaded_bytes: number of downloaded bytes for a GPRS event | ||
timespan: the duration of time over which the GPRS data was sampled | ||
""" | ||
transaction_id = models.UUIDField(editable=False, default=uuid.uuid4) | ||
transaction_id = models.TextField() | ||
subscriber = models.ForeignKey(Subscriber, null=True, | ||
on_delete=models.SET_NULL) | ||
subscriber_imsi = models.TextField(null=True) | ||
|
@@ -868,10 +884,73 @@ def set_subscriber_last_active(sender, instance=None, created=False, | |
event.subscriber.last_active = event.date | ||
event.subscriber.save() | ||
|
||
@staticmethod | ||
def if_invalid_events(sender, instance=None, created=False, **kwargs): | ||
# Check for any invalid event and make an entry | ||
if not created: | ||
return | ||
event = instance | ||
if event.kind in INVALID_EVENTS: | ||
subscriber = Subscriber.objects.get(imsi=event.subscriber_imsi) | ||
if SubscriberInvalidEvents.objects.filter( | ||
subscriber=event.subscriber).exists(): | ||
# Subscriber is blocked after 3 counts i.e there won't be UEs | ||
# unless unblocked | ||
subscriber_event = SubscriberInvalidEvents.objects.get( | ||
subscriber=event.subscriber) | ||
# if it is a 3rd event in 24hr block the subscriber | ||
negative_transactions_ids = subscriber_event.negative_transactions + [ | ||
event.transaction_id] | ||
subscriber_event.count = subscriber_event.count + 1 | ||
subscriber_event.event_time = event.date | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should only update the event_time of the first event so you can do the 24 hour check |
||
subscriber_event.negative_transactions = negative_transactions_ids | ||
subscriber_event.save() | ||
|
||
max_transactions = event.subscriber.network.max_failure_transaction | ||
if subscriber_event.count == max_transactions: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 24 hour check missing? |
||
subscriber.is_blocked = True | ||
subscriber.block_reason = 'Repeated %s within 24 hours ' % ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need block reason? Can we just list out the negative_transactions in the UI? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Block reason is kept for REPORTING purpose, and we are already listing negative transactions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we need the reports, we have the usageevents too |
||
'/'.join(INVALID_EVENTS),) | ||
subscriber.block_time = django.utils.timezone.now() | ||
subscriber.save() | ||
logger.info('Subscriber %s blocked for 30 minutes, ' | ||
'repeated invalid transactions within 24 ' | ||
'hours' % ( | ||
subscriber.imsi)) | ||
else: | ||
subscriber_event = SubscriberInvalidEvents.objects.create( | ||
subscriber=event.subscriber, count=1) | ||
subscriber_event.event_time = event.date | ||
subscriber_event.negative_transactions = [event.transaction_id] | ||
subscriber_event.save() | ||
elif SubscriberInvalidEvents.objects.filter( | ||
subscriber=event.subscriber).count() > 0: | ||
# Delete the event if events are non-consecutive | ||
if not event.subscriber.is_blocked: | ||
subscriber_event = SubscriberInvalidEvents.objects.get( | ||
subscriber=event.subscriber) | ||
logger.info('Subscriber %s invalid event removed' % ( | ||
event.subscriber_imsi)) | ||
subscriber_event.delete() | ||
|
||
@staticmethod | ||
def set_transaction_id(sender, instance=None, **kwargs): | ||
""" | ||
Create transaction id to some readable format | ||
Set transaction as negative transaction if error event | ||
""" | ||
event = instance | ||
if event.kind in INVALID_EVENTS: | ||
negative = True | ||
else: | ||
negative = False | ||
event.transaction_id = dbutils.format_transaction(instance.date, | ||
negative) | ||
|
||
post_save.connect(UsageEvent.set_imsi_and_uuid_and_network, sender=UsageEvent) | ||
post_save.connect(UsageEvent.set_subscriber_last_active, sender=UsageEvent) | ||
|
||
post_save.connect(UsageEvent.if_invalid_events, sender=UsageEvent) | ||
pre_save.connect(UsageEvent.set_transaction_id, sender=UsageEvent) | ||
|
||
class PendingCreditUpdate(models.Model): | ||
"""A credit update that has yet to be acked by a BTS. | ||
|
@@ -1769,3 +1848,11 @@ class FileUpload(models.Model): | |
created_time = models.DateTimeField(auto_now_add=True) | ||
modified_time = models.DateTimeField(auto_now_add=True) | ||
accessed_time = models.DateTimeField(auto_now=True) | ||
|
||
|
||
class SubscriberInvalidEvents(models.Model): | ||
""" Invalid Events logs by Subscriber""" | ||
subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE) | ||
count = models.PositiveIntegerField() | ||
event_time = models.DateTimeField(auto_now_add=True) | ||
negative_transactions = ArrayField(models.TextField(), null=True) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -439,3 +439,20 @@ def req_bts_log(self, obj, retry_delay=60*10, max_retries=432): | |
raise | ||
finally: | ||
obj.save() | ||
|
||
|
||
@app.task(bind=True) | ||
def unblock_blocked_subscribers(self): | ||
"""Unblock subscribers who are blocked for past 24 hrs. | ||
|
||
This runs this as a periodic task managed by celerybeat. | ||
""" | ||
unblock_time = django.utils.timezone.now() - datetime.timedelta(days=1) | ||
subscribers = Subscriber.objects.filter(is_blocked=True, | ||
block_time__lte=unblock_time) | ||
if not subscribers: | ||
return # Do nothing | ||
print 'Unblocking subscribers %s blocked for past 24 hours' % ( | ||
[subscriber.imsi for subscriber in subscribers], ) | ||
subscribers.update(is_blocked=False, block_time=None, | ||
block_reason='No reason to block yet!') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should also have another task to delete There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are intentionally not deleting SubscriberInvalidEvents for reporting purpose. We are not updating 2nd invalid event time, as if we do we won't be able to calculate the 24 hour time span of the transactions for that particular subscriber. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its still not clear how we are blocking the subscriber only if it is a 3rd event in 24hr block. Can you perhaps explain the flow? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should keep this list empty/commented out until we define the use cases and implement/validate the dialplan/chatplan event generation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, we have kept this as dummy for demo, as soon that functionality gets added we will replace with new EVENT.