Commit 394c6ad8 by Ahsan Ulhaq Committed by Ahsan Ul Haq

Remove usage of simple history

LEARNER-3348
parent df1b87c3
from __future__ import unicode_literals
import logging
import time
from dateutil.parser import parse
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from oscar.core.loading import get_model
from ecommerce.courses.models import Course
from ecommerce.invoice.models import Invoice
logger = logging.getLogger(__name__)
Order = get_model('order', 'Order')
OrderLine = get_model('order', 'Line')
Product = get_model('catalogue', 'Product')
ProductAttributeValue = get_model('catalogue', 'ProductAttributeValue')
Refund = get_model('refund', 'Refund')
RefundLine = get_model('refund', 'RefundLine')
StockRecord = get_model('partner', 'StockRecord')
class Command(BaseCommand):
help = 'Clean history data'
def add_arguments(self, parser):
parser.add_argument('--cutoff_date',
action='store',
dest='cutoff_date',
type=str,
required=True,
help='Cutoff date before which the history data should be cleaned. '
'format is YYYY-MM-DD')
parser.add_argument('--batch_size',
action='store',
dest='batch_size',
type=int,
default=1000,
help='Maximum number of database rows to delete per query. '
'This helps avoid locking the database when deleting large amounts of data.')
parser.add_argument('--sleep_time',
action='store',
dest='sleep_time',
type=int,
default=10,
help='Sleep time between deletion of batches')
def handle(self, *args, **options):
cutoff_date = options['cutoff_date']
batch_size = options['batch_size']
sleep_time = options['sleep_time']
try:
cutoff_date = parse(cutoff_date)
except: # pylint: disable=bare-except
msg = 'Failed to parse cutoff date: {}'.format(cutoff_date)
logger.exception(msg)
raise CommandError(msg)
models = (
Order, OrderLine, Refund, RefundLine, ProductAttributeValue, Product, StockRecord, Course, Invoice,
)
for model in models:
qs = model.history.filter(history_date__lte=cutoff_date).order_by('-pk')
message = 'Cleaning {} rows from {} table'.format(qs.count(), model.__name__)
logger.info(message)
try:
# use Primary keys sorting to make sure unique batching as
# filtering batch does not work for huge data
max_pk = qs[0].pk
batch_start = qs.reverse()[0].pk
batch_stop = batch_start + batch_size
except IndexError:
continue
logger.info(message)
while batch_start <= max_pk:
queryset = model.history.filter(pk__gte=batch_start, pk__lt=batch_stop)
with transaction.atomic():
queryset.delete()
logger.info(
'Deleted instances of %s with PKs between %d and %d',
model.__name__, batch_start, batch_stop
)
if batch_stop < max_pk:
time.sleep(sleep_time)
batch_start = batch_stop
batch_stop += batch_size
import datetime
from django.core.management import call_command
from django.core.management.base import CommandError
from django.db.models import QuerySet
from django.utils.timezone import now
from oscar.core.loading import get_model
from oscar.test.factories import OrderFactory
from testfixtures import LogCapture
from ecommerce.tests.testcases import TestCase
LOGGER_NAME = 'ecommerce.core.management.commands.clean_history'
Order = get_model('order', 'Order')
def counter(fn):
"""
Adds a call counter to the given function.
Source: http://code.activestate.com/recipes/577534-counting-decorator/
"""
def _counted(*largs, **kargs):
_counted.invocations += 1
fn(*largs, **kargs)
_counted.invocations = 0
return _counted
class CleanHistoryTests(TestCase):
def test_invalid_cutoff_date(self):
with LogCapture(LOGGER_NAME) as log:
with self.assertRaises(CommandError):
call_command('clean_history', '--cutoff_date=YYYY-MM-DD')
log.check(
(
LOGGER_NAME,
'EXCEPTION',
'Failed to parse cutoff date: YYYY-MM-DD'
)
)
def test_clean_history(self):
initial_count = 5
OrderFactory.create_batch(initial_count)
cutoff_date = now() + datetime.timedelta(days=1)
self.assertEqual(Order.history.filter(history_date__lte=cutoff_date).count(), initial_count)
QuerySet.delete = counter(QuerySet.delete)
call_command(
'clean_history', '--cutoff_date={}'.format(cutoff_date.strftime('%Y-%m-%d')), batch_size=1, sleep_time=1
)
self.assertEqual(QuerySet.delete.invocations, initial_count)
self.assertEqual(Order.history.filter(history_date__lte=cutoff_date).count(), 0)
from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from ecommerce.courses.models import Course from ecommerce.courses.models import Course
class CourseAdmin(SimpleHistoryAdmin): class CourseAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'site',) list_display = ('id', 'name', 'site',)
search_fields = ('id', 'name', 'site', ) search_fields = ('id', 'name', 'site', )
list_filter = ('site', ) list_filter = ('site', )
def get_urls(self):
"""Returns the additional urls used by the Reversion admin."""
urls = super(SimpleHistoryAdmin, self).get_urls() # pylint: disable=bad-super-call
admin_site = self.admin_site
opts = self.model._meta # pylint: disable=protected-access
try:
info = opts.app_label, opts.model_name
except AttributeError: # Django < 1.7
info = opts.app_label, opts.module_name
history_urls = [
# Note: We use a custom URL pattern to match against course IDs.
url("^(.+)/history/([^/]+)/$",
admin_site.admin_view(self.history_form_view),
name='%s_%s_simple_history' % info),
]
return history_urls + urls
admin.site.register(Course, CourseAdmin) admin.site.register(Course, CourseAdmin)
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-04 10:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
def add_created_modified_date(apps, schema_editor):
Course = apps.get_model('courses', 'Course')
courses = Course.objects.all()
for course in courses:
course.created = course.history.earliest().history_date
course.modified = course.history.latest().history_date
course.save()
dependencies = [
('courses', '0005_auto_20170525_0131'),
]
operations = [
migrations.AddField(
model_name='course',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AddField(
model_name='course',
name='modified',
field=models.DateTimeField(auto_now=True, null=True),
),
migrations.AddField(
model_name='historicalcourse',
name='created',
field=models.DateTimeField(blank=True, editable=False, null=True),
),
migrations.AddField(
model_name='historicalcourse',
name='modified',
field=models.DateTimeField(blank=True, editable=False, null=True),
),
migrations.RunPython(add_created_modified_date, migrations.RunPython.noop),
]
...@@ -40,6 +40,8 @@ class Course(models.Model): ...@@ -40,6 +40,8 @@ class Course(models.Model):
help_text=_('Last date/time on which verification for this product can be submitted.') help_text=_('Last date/time on which verification for this product can be submitted.')
) )
history = HistoricalRecords() history = HistoricalRecords()
created = models.DateTimeField(null=True, auto_now_add=True)
modified = models.DateTimeField(null=True, auto_now=True)
thumbnail_url = models.URLField(null=True, blank=True) thumbnail_url = models.URLField(null=True, blank=True)
def __unicode__(self): def __unicode__(self):
......
...@@ -7,7 +7,6 @@ from decimal import Decimal ...@@ -7,7 +7,6 @@ from decimal import Decimal
import waffle import waffle
from dateutil.parser import parse from dateutil.parser import parse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction from django.db import transaction
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -298,10 +297,7 @@ class CourseSerializer(serializers.HyperlinkedModelSerializer): ...@@ -298,10 +297,7 @@ class CourseSerializer(serializers.HyperlinkedModelSerializer):
self.fields.pop('products', None) self.fields.pop('products', None)
def get_last_edited(self, obj): def get_last_edited(self, obj):
try: return obj.modified.strftime(ISO_8601_FORMAT) if obj.modified else None
return obj.history.latest().history_date.strftime(ISO_8601_FORMAT)
except ObjectDoesNotExist:
return None
def get_products_url(self, obj): def get_products_url(self, obj):
return reverse('api:v2:course-product-list', kwargs={'parent_lookup_course_id': obj.id}, return reverse('api:v2:course-product-list', kwargs={'parent_lookup_course_id': obj.id},
...@@ -637,11 +633,7 @@ class CouponSerializer(ProductPaymentInfoMixin, serializers.ModelSerializer): ...@@ -637,11 +633,7 @@ class CouponSerializer(ProductPaymentInfoMixin, serializers.ModelSerializer):
return offer_range.enterprise_customer if offer_range else None return offer_range.enterprise_customer if offer_range else None
def get_last_edited(self, obj): def get_last_edited(self, obj):
try: return None, obj.date_updated
history = obj.history.latest()
return history.history_user.username, history.history_date
except ObjectDoesNotExist:
return None
def get_max_uses(self, obj): def get_max_uses(self, obj):
offer = retrieve_offer(obj) offer = retrieve_offer(obj)
......
...@@ -490,13 +490,6 @@ class CouponViewSetFunctionalTest(CouponMixin, DiscoveryTestMixin, DiscoveryMock ...@@ -490,13 +490,6 @@ class CouponViewSetFunctionalTest(CouponMixin, DiscoveryTestMixin, DiscoveryMock
self.assertEqual(coupon_data['category']['name'], self.data['category']['name']) self.assertEqual(coupon_data['category']['name'], self.data['category']['name'])
self.assertEqual(coupon_data['client'], self.data['client']) self.assertEqual(coupon_data['client'], self.data['client'])
def test_list_coupons_no_history(self):
self.coupon.history.all().delete()
response = self.client.get(COUPONS_LINK)
self.assertEqual(response.status_code, status.HTTP_200_OK)
coupon_data = json.loads(response.content)['results'][0]
self.assertEqual(coupon_data.get('last_edited'), None)
def test_list_and_details_endpoint_return_custom_code(self): def test_list_and_details_endpoint_return_custom_code(self):
"""Test that the list and details endpoints return the correct code.""" """Test that the list and details endpoints return the correct code."""
self.data.update({ self.data.update({
...@@ -523,11 +516,6 @@ class CouponViewSetFunctionalTest(CouponMixin, DiscoveryTestMixin, DiscoveryMock ...@@ -523,11 +516,6 @@ class CouponViewSetFunctionalTest(CouponMixin, DiscoveryTestMixin, DiscoveryMock
self.client.post(COUPONS_LINK, json.dumps(self.data), 'application/json') self.client.post(COUPONS_LINK, json.dumps(self.data), 'application/json')
self.assert_post_response_status(self.data) self.assert_post_response_status(self.data)
def test_coupon_details_no_history(self):
self.coupon.history.all().delete()
detail_response = self.get_response_json('GET', reverse('api:v2:coupons-detail', args=[self.coupon.id]))
self.assertEqual(detail_response['last_edited'], None)
def test_update(self): def test_update(self):
"""Test updating a coupon.""" """Test updating a coupon."""
response_data = self.get_response_json( response_data = self.get_response_json(
......
...@@ -44,7 +44,7 @@ class CourseViewSetTests(ProductSerializerMixin, DiscoveryTestMixin, TestCase): ...@@ -44,7 +44,7 @@ class CourseViewSetTests(ProductSerializerMixin, DiscoveryTestMixin, TestCase):
last_edited = None last_edited = None
try: try:
last_edited = course.history.latest().history_date.strftime(ISO_8601_FORMAT) last_edited = course.modified.strftime(ISO_8601_FORMAT)
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
enrollment_code = course.enrollment_code_product enrollment_code = course.enrollment_code_product
...@@ -114,14 +114,6 @@ class CourseViewSetTests(ProductSerializerMixin, DiscoveryTestMixin, TestCase): ...@@ -114,14 +114,6 @@ class CourseViewSetTests(ProductSerializerMixin, DiscoveryTestMixin, TestCase):
response = self.client.get(self.list_path) response = self.client.get(self.list_path)
self.assertDictEqual(json.loads(response.content), {'count': 0, 'next': None, 'previous': None, 'results': []}) self.assertDictEqual(json.loads(response.content), {'count': 0, 'next': None, 'previous': None, 'results': []})
def test_list_without_history(self):
course = Course.objects.all()[0]
course.history.all().delete()
response = self.client.get(self.list_path)
self.assertEqual(response.status_code, 200)
self.assertListEqual(json.loads(response.content)['results'], [self.serialize_course(self.course)])
def test_create(self): def test_create(self):
""" Verify the view can create a new Course.""" """ Verify the view can create a new Course."""
Course.objects.all().delete() Course.objects.all().delete()
......
from oscar.apps.catalogue.admin import * # pylint: disable=unused-import,wildcard-import,unused-wildcard-import from oscar.apps.catalogue.admin import * # pylint: disable=unused-import,wildcard-import,unused-wildcard-import
from simple_history.admin import SimpleHistoryAdmin
admin.site.unregister((Product, ProductAttributeValue,)) admin.site.unregister((Product, ProductAttributeValue,))
@admin.register(Product) @admin.register(Product)
class ProductAdminExtended(SimpleHistoryAdmin): class ProductAdminExtended(admin.ModelAdmin):
list_display = ('get_title', 'upc', 'get_product_class', 'structure', 'attribute_summary', 'date_created', 'course', list_display = ('get_title', 'upc', 'get_product_class', 'structure', 'attribute_summary', 'date_created', 'course',
'expires',) 'expires',)
prepopulated_fields = {"slug": ("title",)} prepopulated_fields = {"slug": ("title",)}
...@@ -15,6 +14,6 @@ class ProductAdminExtended(SimpleHistoryAdmin): ...@@ -15,6 +14,6 @@ class ProductAdminExtended(SimpleHistoryAdmin):
@admin.register(ProductAttributeValue) @admin.register(ProductAttributeValue)
class ProductAttributeValueAdminExtended(SimpleHistoryAdmin): class ProductAttributeValueAdminExtended(admin.ModelAdmin):
list_display = ('product', 'attribute', 'value') list_display = ('product', 'attribute', 'value')
show_full_result_count = False show_full_result_count = False
...@@ -2,8 +2,7 @@ from django.db import models ...@@ -2,8 +2,7 @@ from django.db import models
from django.db.models.signals import post_init, post_save from django.db.models.signals import post_init, post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from oscar.apps.catalogue.abstract_models import AbstractProduct, AbstractProductAttributeValue from oscar.apps.catalogue.abstract_models import AbstractProduct
from simple_history.models import HistoricalRecords
from ecommerce.core.constants import ( from ecommerce.core.constants import (
COUPON_PRODUCT_CLASS_NAME, COUPON_PRODUCT_CLASS_NAME,
...@@ -20,7 +19,6 @@ class Product(AbstractProduct): ...@@ -20,7 +19,6 @@ class Product(AbstractProduct):
) )
expires = models.DateTimeField(null=True, blank=True, expires = models.DateTimeField(null=True, blank=True,
help_text=_('Last date/time on which this product can be purchased.')) help_text=_('Last date/time on which this product can be purchased.'))
history = HistoricalRecords()
original_expires = None original_expires = None
@property @property
...@@ -82,10 +80,6 @@ def update_enrollment_code(sender, **kwargs): # pylint: disable=unused-argument ...@@ -82,10 +80,6 @@ def update_enrollment_code(sender, **kwargs): # pylint: disable=unused-argument
instance.original_expires = instance.expires instance.original_expires = instance.expires
class ProductAttributeValue(AbstractProductAttributeValue):
history = HistoricalRecords()
class Catalog(models.Model): class Catalog(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
partner = models.ForeignKey('partner.Partner', related_name='catalogs', on_delete=models.CASCADE) partner = models.ForeignKey('partner.Partner', related_name='catalogs', on_delete=models.CASCADE)
......
...@@ -31,7 +31,7 @@ def create_basket(owner, product, site): ...@@ -31,7 +31,7 @@ def create_basket(owner, product, site):
class Command(BaseCommand): class Command(BaseCommand):
help = 'Clean history data' help = 'Added Fake orders for testing'
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('--count', parser.add_argument('--count',
......
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from oscar.apps.order.abstract_models import AbstractLine, AbstractOrder, AbstractPaymentEvent from oscar.apps.order.abstract_models import AbstractOrder, AbstractPaymentEvent
from simple_history.models import HistoricalRecords
from ecommerce.extensions.fulfillment.status import ORDER from ecommerce.extensions.fulfillment.status import ORDER
class Order(AbstractOrder): class Order(AbstractOrder):
history = HistoricalRecords()
@property @property
def is_fulfillable(self): def is_fulfillable(self):
...@@ -24,10 +22,6 @@ class PaymentEvent(AbstractPaymentEvent): ...@@ -24,10 +22,6 @@ class PaymentEvent(AbstractPaymentEvent):
processor_name = models.CharField(_('Payment Processor'), max_length=32, blank=True, null=True) processor_name = models.CharField(_('Payment Processor'), max_length=32, blank=True, null=True)
class Line(AbstractLine):
history = HistoricalRecords()
# If two models with the same name are declared within an app, Django will only use the first one. # If two models with the same name are declared within an app, Django will only use the first one.
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from oscar.apps.order.models import * # noqa isort:skip pylint: disable=wildcard-import,unused-wildcard-import,wrong-import-position,wrong-import-order,ungrouped-imports from oscar.apps.order.models import * # noqa isort:skip pylint: disable=wildcard-import,unused-wildcard-import,wrong-import-position,wrong-import-order,ungrouped-imports
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from oscar.apps.partner.admin import * # noqa pylint: disable=wildcard-import,unused-wildcard-import from oscar.apps.partner.admin import * # noqa pylint: disable=wildcard-import,unused-wildcard-import
from oscar.core.loading import get_class from oscar.core.loading import get_class
from simple_history.admin import SimpleHistoryAdmin
Catalog = get_class('ecommerce.extensions.catalogue.models', 'Catalog') Catalog = get_class('ecommerce.extensions.catalogue.models', 'Catalog')
...@@ -9,7 +8,7 @@ admin.site.unregister((StockRecord, Partner,)) ...@@ -9,7 +8,7 @@ admin.site.unregister((StockRecord, Partner,))
@admin.register(StockRecord) @admin.register(StockRecord)
class StockRecordAdminExtended(SimpleHistoryAdmin): class StockRecordAdminExtended(admin.ModelAdmin):
list_display = ('product', 'partner', 'partner_sku', 'price_excl_tax', 'cost_price', 'num_in_stock') list_display = ('product', 'partner', 'partner_sku', 'price_excl_tax', 'cost_price', 'num_in_stock')
list_filter = ('partner',) list_filter = ('partner',)
raw_id_fields = ('product',) raw_id_fields = ('product',)
......
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from oscar.apps.partner.abstract_models import AbstractPartner, AbstractStockRecord from oscar.apps.partner.abstract_models import AbstractPartner
from simple_history.models import HistoricalRecords
class StockRecord(AbstractStockRecord):
history = HistoricalRecords()
class Partner(AbstractPartner): class Partner(AbstractPartner):
......
...@@ -10,7 +10,6 @@ from ecommerce_worker.sailthru.v1.tasks import send_course_refund_email ...@@ -10,7 +10,6 @@ from ecommerce_worker.sailthru.v1.tasks import send_course_refund_email
from oscar.apps.payment.exceptions import PaymentError from oscar.apps.payment.exceptions import PaymentError
from oscar.core.loading import get_class, get_model from oscar.core.loading import get_class, get_model
from oscar.core.utils import get_default_currency from oscar.core.utils import get_default_currency
from simple_history.models import HistoricalRecords
from ecommerce.core.constants import SEAT_PRODUCT_CLASS_NAME from ecommerce.core.constants import SEAT_PRODUCT_CLASS_NAME
from ecommerce.extensions.analytics.utils import audit_log from ecommerce.extensions.analytics.utils import audit_log
...@@ -82,7 +81,6 @@ class Refund(StatusMixin, TimeStampedModel): ...@@ -82,7 +81,6 @@ class Refund(StatusMixin, TimeStampedModel):
] ]
) )
history = HistoricalRecords()
pipeline_setting = 'OSCAR_REFUND_STATUS_PIPELINE' pipeline_setting = 'OSCAR_REFUND_STATUS_PIPELINE'
@classmethod @classmethod
...@@ -310,7 +308,6 @@ class RefundLine(StatusMixin, TimeStampedModel): ...@@ -310,7 +308,6 @@ class RefundLine(StatusMixin, TimeStampedModel):
] ]
) )
history = HistoricalRecords()
pipeline_setting = 'OSCAR_REFUND_LINE_STATUS_PIPELINE' pipeline_setting = 'OSCAR_REFUND_LINE_STATUS_PIPELINE'
def deny(self): def deny(self):
......
...@@ -77,7 +77,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -77,7 +77,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
max_uses=1, max_uses=1,
voucher_type=Voucher.MULTI_USE voucher_type=Voucher.MULTI_USE
) )
self.coupon.history.all().update(history_user=self.user)
self.coupon_vouchers = CouponVouchers.objects.filter(coupon=self.coupon) self.coupon_vouchers = CouponVouchers.objects.filter(coupon=self.coupon)
self.data = { self.data = {
...@@ -354,8 +353,7 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -354,8 +353,7 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
self.assertEqual(row['Discount Amount'], discount_amount) self.assertEqual(row['Discount Amount'], discount_amount)
self.assertEqual(row['Client'], coupon.client.name) self.assertEqual(row['Client'], coupon.client.name)
self.assertEqual(row['Note'], coupon.attr.note) self.assertEqual(row['Note'], coupon.attr.note)
self.assertEqual(row['Created By'], coupon.history.first().history_user.full_name) self.assertEqual(row['Create Date'], coupon.date_updated.strftime("%b %d, %y"))
self.assertEqual(row['Create Date'], coupon.history.latest().history_date.strftime("%b %d, %y"))
self.assertEqual(row['Coupon Start Date'], voucher.start_datetime.strftime("%b %d, %y")) self.assertEqual(row['Coupon Start Date'], voucher.start_datetime.strftime("%b %d, %y"))
self.assertEqual(row['Coupon Expiry Date'], voucher.end_datetime.strftime("%b %d, %y")) self.assertEqual(row['Coupon Expiry Date'], voucher.end_datetime.strftime("%b %d, %y"))
...@@ -418,7 +416,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -418,7 +416,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
'Status', 'Status',
'Order Number', 'Order Number',
'Redeemed By Username', 'Redeemed By Username',
'Created By',
'Create Date', 'Create Date',
'Coupon Start Date', 'Coupon Start Date',
'Coupon Expiry Date', 'Coupon Expiry Date',
...@@ -436,22 +433,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -436,22 +433,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
self.assertNotIn('Course Seat Types', field_names) self.assertNotIn('Course Seat Types', field_names)
self.assertNotIn('Redeemed For Course ID', field_names) self.assertNotIn('Redeemed For Course ID', field_names)
def test_report_with_no_coupon_history(self):
self.setup_coupons_for_report()
self.coupon.history.all().delete()
client = UserFactory()
basket = Basket.get_basket(client, self.site)
basket.add_product(self.coupon)
vouchers = self.coupon_vouchers.first().vouchers.all()
self.use_voucher('TESTORDER1', vouchers[1], self.user)
self.mock_course_api_response(course=self.course)
_, rows = generate_coupon_report(self.coupon_vouchers)
first_row = rows.pop(0)
self.assertEqual(first_row.get('Created By'), 'N/A')
self.assertEqual(first_row.get('Create Date'), 'N/A')
def test_report_for_dynamic_coupon_with_fixed_benefit_type(self): def test_report_for_dynamic_coupon_with_fixed_benefit_type(self):
""" Verify the coupon report contains correct data for coupon with fixed benefit type. """ """ Verify the coupon report contains correct data for coupon with fixed benefit type. """
dynamic_coupon = self.create_coupon( dynamic_coupon = self.create_coupon(
...@@ -465,7 +446,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -465,7 +446,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
title='Tešt product', title='Tešt product',
voucher_type=Voucher.MULTI_USE voucher_type=Voucher.MULTI_USE
) )
dynamic_coupon.history.all().update(history_user=self.user)
coupon_voucher = CouponVouchers.objects.get(coupon=dynamic_coupon) coupon_voucher = CouponVouchers.objects.get(coupon=dynamic_coupon)
__, rows = generate_coupon_report([coupon_voucher]) __, rows = generate_coupon_report([coupon_voucher])
voucher = coupon_voucher.vouchers.first() voucher = coupon_voucher.vouchers.first()
...@@ -494,7 +474,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -494,7 +474,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
catalog_query = 'course:*' catalog_query = 'course:*'
self.mock_course_runs_endpoint(self.site_configuration.discovery_api_url) self.mock_course_runs_endpoint(self.site_configuration.discovery_api_url)
query_coupon = self.create_catalog_coupon(catalog_query=catalog_query) query_coupon = self.create_catalog_coupon(catalog_query=catalog_query)
query_coupon.history.all().update(history_user=self.user)
field_names, rows = generate_coupon_report([query_coupon.attr.coupon_vouchers]) field_names, rows = generate_coupon_report([query_coupon.attr.coupon_vouchers])
empty_fields = ( empty_fields = (
...@@ -582,7 +561,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -582,7 +561,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
catalog=self.catalog, catalog=self.catalog,
quantity=2 quantity=2
) )
coupon.history.all().update(history_user=self.user)
vouchers = coupon.attr.coupon_vouchers.vouchers.all() vouchers = coupon.attr.coupon_vouchers.vouchers.all()
self.use_voucher('TEST', vouchers[0], self.user) self.use_voucher('TEST', vouchers[0], self.user)
__, rows = generate_coupon_report([coupon.attr.coupon_vouchers]) __, rows = generate_coupon_report([coupon.attr.coupon_vouchers])
...@@ -607,7 +585,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -607,7 +585,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
discovery_api_url=self.site_configuration.discovery_api_url discovery_api_url=self.site_configuration.discovery_api_url
) )
query_coupon = self.create_catalog_coupon(catalog_query=catalog_query) query_coupon = self.create_catalog_coupon(catalog_query=catalog_query)
query_coupon.history.all().update(history_user=self.user)
voucher = query_coupon.attr.coupon_vouchers.vouchers.first() voucher = query_coupon.attr.coupon_vouchers.vouchers.first()
voucher.offers.first().condition.range.add_product(self.verified_seat) voucher.offers.first().condition.range.add_product(self.verified_seat)
self.use_voucher('TESTORDER4', voucher, self.user) self.use_voucher('TESTORDER4', voucher, self.user)
...@@ -633,7 +610,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -633,7 +610,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
OrderLineFactory(product=course2.create_or_update_seat('verified', False, 110, self.partner)) OrderLineFactory(product=course2.create_or_update_seat('verified', False, 110, self.partner))
) )
query_coupon = self.create_catalog_coupon(catalog_query='*:*') query_coupon = self.create_catalog_coupon(catalog_query='*:*')
query_coupon.history.all().update(history_user=self.user)
voucher = query_coupon.attr.coupon_vouchers.vouchers.first() voucher = query_coupon.attr.coupon_vouchers.vouchers.first()
voucher.record_usage(order, self.user) voucher.record_usage(order, self.user)
field_names, rows = generate_coupon_report([query_coupon.attr.coupon_vouchers]) field_names, rows = generate_coupon_report([query_coupon.attr.coupon_vouchers])
...@@ -697,7 +673,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -697,7 +673,6 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
title='Program Coupon Report', title='Program Coupon Report',
program_uuid=program_uuid, program_uuid=program_uuid,
) )
program_coupon.history.update(history_user=self.user)
field_names, rows = generate_coupon_report([program_coupon.attr.coupon_vouchers]) field_names, rows = generate_coupon_report([program_coupon.attr.coupon_vouchers])
for field in ('Discount Amount', 'Price'): for field in ('Discount Amount', 'Price'):
......
...@@ -34,12 +34,10 @@ class CouponReportCSVViewTest(CouponMixin, DiscoveryTestMixin, LmsApiMockMixin, ...@@ -34,12 +34,10 @@ class CouponReportCSVViewTest(CouponMixin, DiscoveryTestMixin, LmsApiMockMixin,
catalog1 = Catalog.objects.create(name="Test catalog 1", partner=partner1) catalog1 = Catalog.objects.create(name="Test catalog 1", partner=partner1)
catalog1.stock_records.add(self.stock_record) catalog1.stock_records.add(self.stock_record)
self.coupon1 = self.create_coupon(partner=partner1, catalog=catalog1) self.coupon1 = self.create_coupon(partner=partner1, catalog=catalog1)
self.coupon1.history.all().update(history_user=self.user)
partner2 = PartnerFactory(name='Tester2') partner2 = PartnerFactory(name='Tester2')
catalog2 = Catalog.objects.create(name="Test catalog 2", partner=partner2) catalog2 = Catalog.objects.create(name="Test catalog 2", partner=partner2)
catalog2.stock_records.add(self.stock_record) catalog2.stock_records.add(self.stock_record)
self.coupon2 = self.create_coupon(partner=partner2, catalog=catalog2) self.coupon2 = self.create_coupon(partner=partner2, catalog=catalog2)
self.coupon2.history.all().update(history_user=self.user)
def request_specific_voucher_report(self, coupon): def request_specific_voucher_report(self, coupon):
client = factories.UserFactory() client = factories.UserFactory()
......
...@@ -95,12 +95,7 @@ def _get_discount_info(discount_data): ...@@ -95,12 +95,7 @@ def _get_discount_info(discount_data):
def _get_info_for_coupon_report(coupon, voucher): def _get_info_for_coupon_report(coupon, voucher):
author = 'N/A' created_date = coupon.date_updated.strftime("%b %d, %y") if coupon.date_updated else 'N/A'
created_date = 'N/A'
history = coupon.history.first()
if history:
author = history.history_user.full_name
created_date = history.history_date.strftime("%b %d, %y")
category_name = ProductCategory.objects.get(product=coupon).category.name category_name = ProductCategory.objects.get(product=coupon).category.name
try: try:
...@@ -150,7 +145,6 @@ def _get_info_for_coupon_report(coupon, voucher): ...@@ -150,7 +145,6 @@ def _get_info_for_coupon_report(coupon, voucher):
'Coupon Name': voucher.name, 'Coupon Name': voucher.name,
'Coupon Start Date': voucher.start_datetime.strftime("%b %d, %y"), 'Coupon Start Date': voucher.start_datetime.strftime("%b %d, %y"),
'Coupon Type': coupon_type, 'Coupon Type': coupon_type,
'Created By': author,
'Create Date': created_date, 'Create Date': created_date,
'Discount Percentage': discount_percentage, 'Discount Percentage': discount_percentage,
'Discount Amount': discount_amount, 'Discount Amount': discount_amount,
...@@ -239,7 +233,6 @@ def generate_coupon_report(coupon_vouchers): ...@@ -239,7 +233,6 @@ def generate_coupon_report(coupon_vouchers):
_('Redeemed By Username'), _('Redeemed By Username'),
_('Redeemed For Course ID'), _('Redeemed For Course ID'),
_('Redeemed For Course IDs'), _('Redeemed For Course IDs'),
_('Created By'),
_('Create Date'), _('Create Date'),
_('Coupon Start Date'), _('Coupon Start Date'),
_('Coupon Expiry Date'), _('Coupon Expiry Date'),
......
from django.contrib import admin from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from ecommerce.invoice.models import Invoice from ecommerce.invoice.models import Invoice
@admin.register(Invoice) @admin.register(Invoice)
class InvoiceAdmin(SimpleHistoryAdmin): class InvoiceAdmin(admin.ModelAdmin):
list_display = ('id', 'basket', 'order', 'business_client', 'state',) list_display = ('id', 'basket', 'order', 'business_client', 'state',)
list_filter = ('state',) list_filter = ('state',)
search_fields = ['order__number', 'business_client__name'] search_fields = ['order__number', 'business_client__name']
...@@ -2,7 +2,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator ...@@ -2,7 +2,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
from simple_history.models import HistoricalRecords
class Invoice(TimeStampedModel): class Invoice(TimeStampedModel):
...@@ -47,8 +46,6 @@ class Invoice(TimeStampedModel): ...@@ -47,8 +46,6 @@ class Invoice(TimeStampedModel):
null=True, blank=True null=True, blank=True
) )
history = HistoricalRecords()
@property @property
def total(self): def total(self):
"""Total amount paid for this Invoice""" """Total amount paid for this Invoice"""
......
...@@ -228,7 +228,6 @@ MIDDLEWARE_CLASSES = ( ...@@ -228,7 +228,6 @@ MIDDLEWARE_CLASSES = (
'ecommerce.extensions.basket.middleware.BasketMiddleware', 'ecommerce.extensions.basket.middleware.BasketMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
'social_django.middleware.SocialAuthExceptionMiddleware', 'social_django.middleware.SocialAuthExceptionMiddleware',
'simple_history.middleware.HistoryRequestMiddleware',
'threadlocals.middleware.ThreadLocalMiddleware', 'threadlocals.middleware.ThreadLocalMiddleware',
'ecommerce.theming.middleware.CurrentSiteThemeMiddleware', 'ecommerce.theming.middleware.CurrentSiteThemeMiddleware',
'ecommerce.theming.middleware.ThemePreviewMiddleware', 'ecommerce.theming.middleware.ThemePreviewMiddleware',
......
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