Merged in feature/reset-licenses (pull request #89)

Feature/reset licenses

Approved-by: Ramon Wenger
This commit is contained in:
Christian Cueni 2021-09-22 09:58:07 +00:00
commit 19f44483f1
10 changed files with 216 additions and 7 deletions

View File

@ -77,7 +77,7 @@ class UserSettingAdmin(admin.ModelAdmin):
@admin.register(License) @admin.register(License)
class LicenseAdmin(admin.ModelAdmin): class LicenseAdmin(admin.ModelAdmin):
list_display = ('licensee', 'for_role', 'expire_date', 'isbn') list_display = ('licensee', 'for_role', 'expire_date', 'isbn', 'hep_created_at')
list_filter = ('licensee', 'expire_date') list_filter = ('licensee', 'expire_date')
raw_id_fields = ('licensee',) raw_id_fields = ('licensee',)

View File

@ -9,7 +9,7 @@ MYSKILLBOX_LICENSES = {
}, },
"978-3-0355-1860-3": { "978-3-0355-1860-3": {
'edition': STUDENT_KEY, 'edition': STUDENT_KEY,
'duration': 455, 'duration': 365,
'name': 'Student 1 year' 'name': 'Student 1 year'
}, },
"978-3-0355-1862-7": { "978-3-0355-1862-7": {
@ -24,7 +24,7 @@ MYSKILLBOX_LICENSES = {
}, },
"978-3-0355-1823-8": { "978-3-0355-1823-8": {
'edition': TEACHER_KEY, 'edition': TEACHER_KEY,
'duration': 455, 'duration': 365,
'name': 'Teacher 1 year' 'name': 'Teacher 1 year'
} }
} }

View File

@ -0,0 +1,26 @@
from django.core.management.base import BaseCommand
from users.models import License
class Command(BaseCommand):
help = """
Move raw data from new API to own field on License model
"""
def add_arguments(self, parser):
parser.add_argument('--dry-run', action='store_true', dest='dry_run', default=False, help='Make dry-run')
def handle(self, *args, **options):
for license in License.objects.filter(new_api_raw=''):
if license.is_old_api():
continue
license.new_api_raw = license.raw
license.raw = ''
print(f'Moving raw to new API field for licenseID {license.id}')
if not options['dry_run']:
license.save()

View File

@ -0,0 +1,67 @@
from datetime import date, timedelta
from django.utils.timezone import now
from django.core.management.base import BaseCommand
from users.licenses import MYSKILLBOX_LICENSES
from users.models import License, NO_DATE
YEARLY_ISBNS = ["978-3-0355-1860-3", "978-3-0355-1823-8"]
LONGER_DURATION = 455
class Command(BaseCommand):
help = """
Reset licenses that have a duration of one year
Uses the duration of MYSKILLBOX_LICENSES as reference.
"""
def add_arguments(self, parser):
parser.add_argument('--dry-run', action='store_true', dest='dry_run', default=False, help='Make dry-run')
def handle(self, *args, **options):
today = now().date()
for license in License.objects.filter(expire_date__gte=today):
isbn = license.get_hep_isbn()
if isbn not in YEARLY_ISBNS:
continue
standard_duration = MYSKILLBOX_LICENSES[isbn]['duration'] # use isbn from hep due to our bug
start_date = license.get_hep_start_date()
duration_in_days = (license.expire_date - start_date).days
if duration_in_days == standard_duration:
continue
user_join_delta = (license.expire_date - license.licensee.date_joined.date()).days
if self._is_change_candidate(user_join_delta, duration_in_days, start_date):
self._update_expiry_date(license, standard_duration, options['dry_run'])
def _is_change_candidate(self, user_join_delta: int, duration_in_days: int, start_date: date) -> bool:
"""
user_join_delta == LONGER_DURATION: user joined the same number of days as the old duration was
duration_in_days == LONGER_DURATION: delta from start date and expiry date is the same as the old duration
start_date == NO_DATE: license does not have a start date -> was created after moving to new api as
such it the longer duration was set as duration
"""
return user_join_delta == LONGER_DURATION or duration_in_days == LONGER_DURATION \
or start_date == NO_DATE
def _update_expiry_date(self, license: License, standard_duration: int, dry_run: bool):
new_expiry_date = license.expire_date - timedelta(LONGER_DURATION - standard_duration)
user_expire_date = license.licensee.license_expiry_date
if user_expire_date == license.expire_date:
print(f'Resetting user expire_date for userID {license.licensee.id} from'
f' {license.licensee.license_expiry_date} to {new_expiry_date}')
if not dry_run:
license.licensee.license_expiry_date = new_expiry_date
license.licensee.save()
print(f'Resetting expiry date for license ID {license.id} ({license.expire_date}) to {new_expiry_date}')
if not dry_run:
license.expire_date = new_expiry_date
license.save()

View File

@ -0,0 +1,26 @@
from django.core.management.base import BaseCommand
from users.models import License
class Command(BaseCommand):
help = """
Overwrites the License model's IBAN with the IBAN from the hep-JSON
"""
def add_arguments(self, parser):
parser.add_argument('--dry-run', action='store_true', dest='dry_run', default=False, help='Make dry-run')
def handle(self, *args, **options):
for license in License.objects.all():
isbn = license.get_hep_isbn()
if isbn == license.isbn:
continue
print(f'Setting ISBN for licenseID {license.id} from {license.isbn} to {isbn}')
if not options['dry_run']:
license.isbn = isbn
license.save()

View File

@ -147,14 +147,16 @@ class LicenseManager(models.Manager):
else: else:
user_role = Role.objects.get_default_student_role() user_role = Role.objects.get_default_student_role()
new_license = self._create_license_for_role(licensee, expiry_date, raw, user_role, order_id) new_license = self._create_license_for_role(licensee, expiry_date, raw, user_role, order_id, isbn,
activation_date)
new_license.licensee.license_expiry_date = new_license.expire_date new_license.licensee.license_expiry_date = new_license.expire_date
new_license.licensee.save() new_license.licensee.save()
return new_license return new_license
def _create_license_for_role(self, licensee, expiry_date, raw, role, order_id): def _create_license_for_role(self, licensee, expiry_date, raw, role, order_id, isbn, activation_date):
return self.create(licensee=licensee, expire_date=expiry_date, raw=raw, for_role=role, order_id=order_id) return self.create(licensee=licensee, expire_date=expiry_date, new_api_raw=raw, for_role=role, order_id=order_id,
isbn=isbn, hep_created_at=activation_date)
def get_active_license_for_user(self, user): def get_active_license_for_user(self, user):
licenses = self.filter(licensee=user, expire_date__gte=timezone.now()).order_by('-expire_date') licenses = self.filter(licensee=user, expire_date__gte=timezone.now()).order_by('-expire_date')

View File

@ -0,0 +1,25 @@
# Generated by Django 2.2.24 on 2021-09-22 05:50
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('users', '0027_auto_20210414_2116'),
]
operations = [
migrations.AddField(
model_name='license',
name='hep_created_at',
field=models.DateTimeField(default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=utc)),
),
migrations.AddField(
model_name='license',
name='new_api_raw',
field=models.TextField(default=''),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 2.2.24 on 2021-09-22 05:50
from django.db import migrations, models
from users.models import AWARE_NO_DATETIME
class Migration(migrations.Migration):
dependencies = [
('users', '0028_auto_20210922_0550'),
]
operations = [
migrations.AddField(
model_name='license',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=AWARE_NO_DATETIME),
preserve_default=False,
),
]

View File

@ -1,7 +1,8 @@
import json
import random import random
import re import re
import string import string
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta, MINYEAR
from typing import Union from typing import Union
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -17,6 +18,9 @@ from users.licenses import MYSKILLBOX_LICENSES
from users.managers import LicenseManager, RoleManager, UserManager, UserRoleManager from users.managers import LicenseManager, RoleManager, UserManager, UserRoleManager
DEFAULT_SCHOOL_ID = 1 DEFAULT_SCHOOL_ID = 1
NO_DATE = date(MINYEAR, 1, 1) # date to tag licenses without date
NO_DATETIME = datetime.combine(NO_DATE, datetime.min.time())
AWARE_NO_DATETIME = make_aware(NO_DATETIME)
class User(AbstractUser): class User(AbstractUser):
@ -305,6 +309,9 @@ class License(models.Model):
raw = models.TextField(default='') raw = models.TextField(default='')
isbn = models.CharField(max_length=50, blank=False, null=False, isbn = models.CharField(max_length=50, blank=False, null=False,
default=list(MYSKILLBOX_LICENSES.keys())[0]) # student license default=list(MYSKILLBOX_LICENSES.keys())[0]) # student license
created_at = models.DateTimeField(auto_now_add=True)
hep_created_at = models.DateTimeField(default=AWARE_NO_DATETIME)
new_api_raw = models.TextField(default='')
objects = LicenseManager() objects = LicenseManager()
@ -315,6 +322,41 @@ class License(models.Model):
date = make_aware(datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day)) date = make_aware(datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day))
return License.is_product_active(date, self.isbn) return License.is_product_active(date, self.isbn)
def _read_as_json(self, raw_string: str) -> dict:
return json.loads(raw_string.replace("'", '"').replace('True', 'true').replace('False', 'false')
.replace('None', 'null'))
def get_hep_start_date(self) -> date:
hep_data = self._read_as_json(self.raw)
if 'updated_at' in hep_data: # old key from Magento. Format: 2020-06-22 18:45:13
date_string = hep_data['created_at']
return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S').date()
return NO_DATE
def get_hep_isbn(self) -> str:
hep_data = self._read_as_json(self.raw)
if self._is_old_api(hep_data):
return hep_data['sku']
elif self._is_new_api(hep_data):
return hep_data['isbn']
else:
return ''
def is_new_api(self) -> bool:
hep_data = self._read_as_json(self.raw)
return self._is_new_api(hep_data)
def is_old_api(self) -> bool:
hep_data = self._read_as_json(self.raw)
return self._is_old_api(hep_data)
def _is_new_api(self, hep_data: dict) -> bool:
return 'isbn' in hep_data
def _is_old_api(self, hep_data: dict) -> bool:
return 'sku' in hep_data
@staticmethod @staticmethod
def is_product_active(expiry_date, isbn): def is_product_active(expiry_date, isbn):
now = timezone.now() now = timezone.now()