Commit 80f51474 by Uman Shahzad Committed by Douglas Hall

Administration UI for Enterprise Offers.

parent 8779027b
...@@ -41,11 +41,11 @@ ...@@ -41,11 +41,11 @@
exclude: ['js/common'] exclude: ['js/common']
}, },
{ {
name: 'js/pages/program_offer_list_page', name: 'js/pages/offer_list_page',
exclude: ['js/common'] exclude: ['js/common']
}, },
{ {
name: 'js/pages/program_offer_form_page', name: 'js/pages/offer_form_page',
exclude: ['js/common'] exclude: ['js/common']
}, },
{ {
......
...@@ -268,10 +268,10 @@ class CouponOfferViewTests(ApiMockMixin, CouponMixin, DiscoveryTestMixin, Enterp ...@@ -268,10 +268,10 @@ class CouponOfferViewTests(ApiMockMixin, CouponMixin, DiscoveryTestMixin, Enterp
) )
@ddt.data( @ddt.data(
('', 'If you have concerns about sharing your data, please contact your administrator at TestShib.'), ('', 'If you have concerns about sharing your data, please contact your administrator at BigEnterprise.'),
( (
'contact@example.com', 'contact@example.com',
'If you have concerns about sharing your data, please contact your administrator at TestShib at ' 'If you have concerns about sharing your data, please contact your administrator at BigEnterprise at '
'contact@example.com.', 'contact@example.com.',
), ),
) )
...@@ -282,7 +282,6 @@ class CouponOfferViewTests(ApiMockMixin, CouponMixin, DiscoveryTestMixin, Enterp ...@@ -282,7 +282,6 @@ class CouponOfferViewTests(ApiMockMixin, CouponMixin, DiscoveryTestMixin, Enterp
self.mock_access_token_response() self.mock_access_token_response()
self.mock_specific_enterprise_customer_api( self.mock_specific_enterprise_customer_api(
ENTERPRISE_CUSTOMER, ENTERPRISE_CUSTOMER,
name='TestShib',
contact_email=contact_email contact_email=contact_email
) )
base_url = self.prepare_url_for_credit_seat(enterprise_customer=ENTERPRISE_CUSTOMER) base_url = self.prepare_url_for_credit_seat(enterprise_customer=ENTERPRISE_CUSTOMER)
...@@ -551,7 +550,7 @@ class CouponRedeemViewTests(CouponMixin, DiscoveryTestMixin, LmsApiMockMixin, En ...@@ -551,7 +550,7 @@ class CouponRedeemViewTests(CouponMixin, DiscoveryTestMixin, LmsApiMockMixin, En
@httpretty.activate @httpretty.activate
def test_enterprise_customer_successful_redemption_message(self): def test_enterprise_customer_successful_redemption_message(self):
""" Verify the info message appears on successful redemption. """ """ Verify the info message appears on successful redemption. """
expected_message = '<i class="fa fa-info-circle"></i> A discount has been applied, courtesy of TestShib.' expected_message = '<i class="fa fa-info-circle"></i> A discount has been applied, courtesy of BigEnterprise.'
# Setting benefit value to a low amount to ensure the basket is not free, # Setting benefit value to a low amount to ensure the basket is not free,
# and calls to the checkout page do not redirect away from the checkout page. # and calls to the checkout page do not redirect away from the checkout page.
......
...@@ -11,7 +11,6 @@ from slumber.exceptions import SlumberHttpBaseException ...@@ -11,7 +11,6 @@ from slumber.exceptions import SlumberHttpBaseException
from ecommerce.core.utils import get_cache_key from ecommerce.core.utils import get_cache_key
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -88,7 +87,7 @@ def fetch_enterprise_learner_data(site, user): ...@@ -88,7 +87,7 @@ def fetch_enterprise_learner_data(site, user):
{ {
"enterprise_customer": { "enterprise_customer": {
"uuid": "cf246b88-d5f6-4908-a522-fc307e0b0c59", "uuid": "cf246b88-d5f6-4908-a522-fc307e0b0c59",
"name": "TestShib", "name": "BigEnterprise",
"catalog": 2, "catalog": 2,
"active": true, "active": true,
"site": { "site": {
......
from django.utils.translation import ugettext_lazy as _
from oscar.core.loading import get_model
from ecommerce.enterprise.benefits import EnterpriseAbsoluteDiscountBenefit, EnterprisePercentageDiscountBenefit
Benefit = get_model('offer', 'Benefit')
BENEFIT_MAP = {
Benefit.FIXED: EnterpriseAbsoluteDiscountBenefit,
Benefit.PERCENTAGE: EnterprisePercentageDiscountBenefit,
}
BENEFIT_TYPE_CHOICES = (
(Benefit.PERCENTAGE, _('Percentage')),
(Benefit.FIXED, _('Absolute')),
)
# Waffle switch used to enable/disable Enterprise offers. # Waffle switch used to enable/disable Enterprise offers.
ENTERPRISE_OFFERS_SWITCH = 'enable_enterprise_offers' ENTERPRISE_OFFERS_SWITCH = 'enable_enterprise_offers'
# -*- coding: utf-8 -*-
# TODO: Refactor this to consolidate it with `ecommerce.programs.forms`.
from django import forms
from django.forms.utils import ErrorList
from django.utils.translation import ugettext_lazy as _
from oscar.core.loading import get_model
from ecommerce.enterprise.conditions import EnterpriseCustomerCondition
from ecommerce.enterprise.constants import BENEFIT_MAP, BENEFIT_TYPE_CHOICES
from ecommerce.enterprise.utils import get_enterprise_customer
from ecommerce.programs.custom import class_path, create_condition
Benefit = get_model('offer', 'Benefit')
Condition = get_model('offer', 'Condition')
ConditionalOffer = get_model('offer', 'ConditionalOffer')
Range = get_model('offer', 'Range')
class EnterpriseOfferForm(forms.ModelForm):
enterprise_customer_uuid = forms.UUIDField(required=True, label=_('Enterprise Customer UUID'))
enterprise_customer_catalog_uuid = forms.UUIDField(required=False, label=_('Enterprise Customer Catalog UUID'))
benefit_type = forms.ChoiceField(choices=BENEFIT_TYPE_CHOICES, label=_('Discount Type'))
benefit_value = forms.DecimalField(
required=True, decimal_places=2, max_digits=12, min_value=0, label=_('Discount Value')
)
class Meta(object):
model = ConditionalOffer
fields = [
'enterprise_customer_uuid', 'enterprise_customer_catalog_uuid', 'start_datetime', 'end_datetime',
'benefit_type', 'benefit_value'
]
help_texts = {
'end_datetime': '',
}
labels = {
'start_datetime': _('Start Date'),
'end_datetime': _('End Date'),
}
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=ErrorList,
label_suffix=None, empty_permitted=False, instance=None, request=None):
initial = initial or {}
self.request = request
if instance:
initial.update({
'enterprise_customer_uuid': instance.condition.enterprise_customer_uuid,
'enterprise_customer_catalog_uuid': instance.condition.enterprise_customer_catalog_uuid,
'benefit_type': instance.benefit.proxy().benefit_class_type,
'benefit_value': instance.benefit.value,
})
super(EnterpriseOfferForm, self).__init__(data, files, auto_id, prefix, initial, error_class, label_suffix,
empty_permitted, instance)
date_ui_class = {'class': 'add-pikaday'}
self.fields['start_datetime'].widget.attrs.update(date_ui_class)
self.fields['end_datetime'].widget.attrs.update(date_ui_class)
def clean(self):
cleaned_data = super(EnterpriseOfferForm, self).clean()
start_datetime = cleaned_data.get('start_datetime')
end_datetime = cleaned_data.get('end_datetime')
enterprise_customer_uuid = cleaned_data.get('enterprise_customer_uuid')
enterprise_customer_catalog_uuid = cleaned_data.get('enterprise_customer_catalog_uuid')
if not self.instance.pk and enterprise_customer_uuid and enterprise_customer_catalog_uuid:
enterprise_offer_exists = ConditionalOffer.objects.filter(
offer_type=ConditionalOffer.SITE,
condition__enterprise_customer_uuid=enterprise_customer_uuid,
condition__enterprise_customer_catalog_uuid=enterprise_customer_catalog_uuid,
).exists()
if enterprise_offer_exists:
for field in ['enterprise_customer_uuid', 'enterprise_customer_catalog_uuid']:
self.add_error(field, _('An offer already exists for this Enterprise & Catalog combination.'))
if cleaned_data['benefit_type'] == Benefit.PERCENTAGE and cleaned_data.get('benefit_value') > 100:
self.add_error('benefit_value', _('Percentage discounts cannot be greater than 100%.'))
if end_datetime and not start_datetime:
self.add_error('start_datetime', _('A start date must be specified when specifying an end date.'))
if start_datetime and end_datetime and start_datetime > end_datetime:
self.add_error('start_datetime', _('The start date must occur before the end date.'))
return cleaned_data
def save(self, commit=True):
enterprise_customer_uuid = self.cleaned_data['enterprise_customer_uuid']
enterprise_customer_catalog_uuid = self.cleaned_data['enterprise_customer_catalog_uuid']
site = self.request.site
enterprise_customer = get_enterprise_customer(site, enterprise_customer_uuid)
enterprise_customer_name = enterprise_customer['name']
self.instance.name = _(u'Discount provided by {enterprise_customer_name}.'.format(
enterprise_customer_name=enterprise_customer_name
))
self.instance.status = ConditionalOffer.OPEN
self.instance.offer_type = ConditionalOffer.SITE
self.instance.max_basket_applications = 1
self.instance.site = site
self.instance.priority = 10 # This will ensure that Enterprise Offers are applied before Program Offers.
if commit:
benefit = getattr(self.instance, 'benefit', Benefit())
benefit.proxy_class = class_path(BENEFIT_MAP[self.cleaned_data['benefit_type']])
benefit.value = self.cleaned_data['benefit_value']
benefit.save()
self.instance.benefit = benefit
if hasattr(self.instance, 'condition'):
self.instance.condition.enterprise_customer_uuid = enterprise_customer_uuid
self.instance.condition.enterprise_customer_name = enterprise_customer_name
self.instance.condition.enterprise_customer_catalog_uuid = enterprise_customer_catalog_uuid
self.instance.condition.save()
else:
self.instance.condition = create_condition(
EnterpriseCustomerCondition,
enterprise_customer_uuid=enterprise_customer_uuid,
enterprise_customer_name=enterprise_customer_name,
enterprise_customer_catalog_uuid=enterprise_customer_catalog_uuid,
)
return super(EnterpriseOfferForm, self).save(commit)
{% extends 'edx/base.html' %}
{% load crispy_forms_tags %}
{% load i18n %}
{% load staticfiles %}
{% block title %}
{% if editing %}
{% blocktrans trimmed with enterprise_customer_name=enterprise_customer.name %}
Edit Enterprise Offer: {{ enterprise_customer_name }}
{% endblocktrans %}
{% else %}
{% trans "Create Enterprise Offer" %}
{% endif %}
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" href="{% static 'bower_components/pikaday/css/pikaday.css' %}" type="text/x-scss">
{% endblock %}
{% block navbar %}
{% include "edx/partials/_staff_navbar.html" %}
{% include "edx/partials/_administration_menu.html" %}
{% endblock navbar %}
{% block content %}
<div class="container">
<ol class="breadcrumb">
<li><a href="{% url 'enterprise:offers:list' %}">{% trans "Enterprise Offers" %}</a></li>
{% if editing %}
<li>{{ enterprise_customer.name }}</li>
<li>{% trans "Edit" %}</li>
{% else %}
<li>{% trans "Create" %}</li>
{% endif %}
</ol>
{% include 'partials/alert_messages.html' %}
<div class="page-header">
<h1 class="hd-1 emphasized">
{% if editing %}
{% trans "Edit Enterprise Offer" %}
{% else %}
{% trans "Create Enterprise Offer" %}
{% endif %}
</h1>
</div>
<form id="offerForm" method="post">
{% csrf_token %}
{{ form|crispy }}
<div class="form-actions">
<input type="submit" class="btn btn-primary" value="{% if editing %}
{% trans "Save Changes" %}
{% else %}
{% trans "Create Enterprise Offer" %}
{% endif %}">
<a class="btn btn-default" href="{% url 'enterprise:offers:list' %}">{% trans "Cancel" %}</a>
</div>
</form>
</div>
{% endblock %}
{% block footer %}
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-xs-12 text-right">
<em>{% blocktrans %}{{ platform_name }} Enterprise Offer Administration Tool{% endblocktrans %}</em>
</div>
</div>
</div>
</footer>
{% endblock footer %}
{% block javascript %}
<script src="{% static 'js/pages/offer_form_page.js' %}"></script>
{% endblock %}
{% extends 'edx/base.html' %}
{% load i18n %}
{% load offer_tags %}
{% load staticfiles %}
{% block title %}{% trans "Enterprise Offers" %}{% endblock %}
{% block navbar %}
{% include "edx/partials/_staff_navbar.html" %}
{% include "edx/partials/_administration_menu.html" %}
{% endblock navbar %}
{% block content %}
<div class="container">
<div class="page-header">
<h1 class="hd-1 emphasized">
{% trans "Enterprise Offers" %}
<div class="pull-right">
<a href="{% url 'enterprise:offers:new' %}"
class="btn btn-primary btn-small">{% trans "Create Enterprise Offer" %}</a>
</div>
</h1>
</div>
<table id="offerTable" class="copy copy-base table table-striped table-bordered" cellspacing="0">
<caption class="sr-only">{% trans "Current enterprise offers" %}</caption>
<thead>
<tr>
<th>{% trans 'Enterprise Customer Name' %}</th>
<th>{% trans 'Enterprise Customer UUID' %}</th>
<th>{% trans 'Enterprise Customer Catalog UUID' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Value' %}</th>
<th>{% trans 'Start' %}</th>
<th>{% trans 'End' %}</th>
</tr>
</thead>
<tbody>
{% for offer in object_list %}
<tr>
<td>
<a href="{% url 'enterprise:offers:edit' pk=offer.pk %}">{{ offer.condition.enterprise_customer_name }}</a>
</td>
<td>{{ offer.condition.enterprise_customer_uuid }}</td>
<td>{{ offer.condition.enterprise_customer_catalog_uuid }}</td>
<td>{{ offer.benefit|benefit_type|capfirst }}</td>
<td>{{ offer.benefit.value }}</td>
<td>{{ offer.start_datetime|default_if_none:'--' }}</td>
<td>{{ offer.end_datetime|default_if_none:'--' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
{% block footer %}
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-xs-12 text-right">
<em>{% blocktrans %}{{ platform_name }} Enterprise Offer Administration Tool{% endblocktrans %}</em>
</div>
</div>
</div>
</footer>
{% endblock footer %}
{% block javascript %}
<script src="{% static 'js/pages/offer_list_page.js' %}"></script>
{% endblock %}
...@@ -67,6 +67,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -67,6 +67,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_customer_api_response_json = json.dumps(enterprise_customer_api_response) enterprise_customer_api_response_json = json.dumps(enterprise_customer_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri=self.ENTERPRISE_CUSTOMER_URL, uri=self.ENTERPRISE_CUSTOMER_URL,
...@@ -74,12 +75,12 @@ class EnterpriseServiceMockMixin(object): ...@@ -74,12 +75,12 @@ class EnterpriseServiceMockMixin(object):
content_type='application/json' content_type='application/json'
) )
def mock_specific_enterprise_customer_api(self, uuid, name='TestShib', contact_email='', consent_enabled=True): def mock_specific_enterprise_customer_api(self, uuid, name='BigEnterprise', contact_email='', consent_enabled=True):
""" """
Helper function to register the enterprise customer API endpoint. Helper function to register the enterprise customer API endpoint.
""" """
enterprise_customer_api_response = { enterprise_customer_api_response = {
'uuid': uuid, 'uuid': str(uuid),
'name': name, 'name': name,
'catalog': 0, 'catalog': 0,
'active': True, 'active': True,
...@@ -103,6 +104,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -103,6 +104,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_customer_api_response_json = json.dumps(enterprise_customer_api_response) enterprise_customer_api_response_json = json.dumps(enterprise_customer_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri='{}{}/'.format(self.ENTERPRISE_CUSTOMER_URL, uuid), uri='{}{}/'.format(self.ENTERPRISE_CUSTOMER_URL, uuid),
...@@ -119,6 +121,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -119,6 +121,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_customer_api_response_json = json.dumps(enterprise_customer_api_response) enterprise_customer_api_response_json = json.dumps(enterprise_customer_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri='{}{}/'.format(self.ENTERPRISE_CUSTOMER_URL, uuid), uri='{}{}/'.format(self.ENTERPRISE_CUSTOMER_URL, uuid),
...@@ -149,7 +152,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -149,7 +152,7 @@ class EnterpriseServiceMockMixin(object):
'id': learner_id, 'id': learner_id,
'enterprise_customer': { 'enterprise_customer': {
'uuid': enterprise_customer_uuid, 'uuid': enterprise_customer_uuid,
'name': 'TestShib', 'name': 'BigEnterprise',
'catalog': catalog_id, 'catalog': catalog_id,
'active': True, 'active': True,
'site': { 'site': {
...@@ -197,6 +200,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -197,6 +200,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response) enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri=self.ENTERPRISE_LEARNER_URL, uri=self.ENTERPRISE_LEARNER_URL,
...@@ -214,6 +218,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -214,6 +218,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response) enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.POST, method=httpretty.POST,
uri=self.ENTERPRISE_LEARNER_URL, uri=self.ENTERPRISE_LEARNER_URL,
...@@ -237,6 +242,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -237,6 +242,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response) enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri=self.ENTERPRISE_LEARNER_URL, uri=self.ENTERPRISE_LEARNER_URL,
...@@ -258,7 +264,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -258,7 +264,7 @@ class EnterpriseServiceMockMixin(object):
'invalid-unexpected-key': { 'invalid-unexpected-key': {
'enterprise_customer': { 'enterprise_customer': {
'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59', 'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59',
'name': 'TestShib', 'name': 'BigEnterprise',
'catalog': 1, 'catalog': 1,
'active': True, 'active': True,
'site': { 'site': {
...@@ -281,6 +287,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -281,6 +287,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response) enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri=self.ENTERPRISE_LEARNER_URL, uri=self.ENTERPRISE_LEARNER_URL,
...@@ -302,7 +309,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -302,7 +309,7 @@ class EnterpriseServiceMockMixin(object):
{ {
'enterprise_customer': { 'enterprise_customer': {
'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59', 'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59',
'name': 'TestShib', 'name': 'BigEnterprise',
'catalog': 1, 'catalog': 1,
'active': True, 'active': True,
'site': { 'site': {
...@@ -324,6 +331,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -324,6 +331,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response) enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri=self.ENTERPRISE_LEARNER_URL, uri=self.ENTERPRISE_LEARNER_URL,
...@@ -347,6 +355,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -347,6 +355,7 @@ class EnterpriseServiceMockMixin(object):
Helper function to register enterprise learner API endpoint for a Helper function to register enterprise learner API endpoint for a
failure. failure.
""" """
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri=self.ENTERPRISE_LEARNER_URL, uri=self.ENTERPRISE_LEARNER_URL,
...@@ -357,6 +366,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -357,6 +366,7 @@ class EnterpriseServiceMockMixin(object):
""" """
Helper function to return 500 error while accessing learner entitlements api endpoint. Helper function to return 500 error while accessing learner entitlements api endpoint.
""" """
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri='{base_url}{learner_id}/entitlements/'.format( uri='{base_url}{learner_id}/entitlements/'.format(
...@@ -381,6 +391,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -381,6 +391,7 @@ class EnterpriseServiceMockMixin(object):
} }
learner_entitlements_json = json.dumps(enterprise_learner_entitlements_api_response) learner_entitlements_json = json.dumps(enterprise_learner_entitlements_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri='{base_url}{learner_id}/entitlements/'.format( uri='{base_url}{learner_id}/entitlements/'.format(
...@@ -426,6 +437,7 @@ class EnterpriseServiceMockMixin(object): ...@@ -426,6 +437,7 @@ class EnterpriseServiceMockMixin(object):
} }
enterprise_enrollment_api_response_json = json.dumps(enterprise_enrollment_api_response) enterprise_enrollment_api_response_json = json.dumps(enterprise_enrollment_api_response)
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=httpretty.GET, method=httpretty.GET,
uri=self.ENTERPRISE_COURSE_ENROLLMENT_URL, uri=self.ENTERPRISE_COURSE_ENROLLMENT_URL,
...@@ -452,6 +464,8 @@ class EnterpriseServiceMockMixin(object): ...@@ -452,6 +464,8 @@ class EnterpriseServiceMockMixin(object):
'consent_required': required, 'consent_required': required,
'exists': exists, 'exists': exists,
} }
self.mock_access_token_response()
httpretty.register_uri( httpretty.register_uri(
method=method, method=method,
uri=self.site.siteconfiguration.build_lms_url('/consent/api/v1/data_sharing_consent'), uri=self.site.siteconfiguration.build_lms_url('/consent/api/v1/data_sharing_consent'),
......
# -*- coding: utf-8 -*-
import uuid
import httpretty
from oscar.core.loading import get_model
from ecommerce.enterprise.constants import BENEFIT_MAP
from ecommerce.enterprise.forms import EnterpriseOfferForm
from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin
from ecommerce.extensions.test import factories
from ecommerce.programs.custom import class_path
from ecommerce.tests.testcases import TestCase
Benefit = get_model('offer', 'Benefit')
ConditionalOffer = get_model('offer', 'ConditionalOffer')
class EnterpriseOfferFormTests(EnterpriseServiceMockMixin, TestCase):
def generate_data(self, **kwargs):
data = {
'enterprise_customer_uuid': uuid.uuid4(),
'enterprise_customer_name': 'BigEnterprise',
'enterprise_customer_catalog_uuid': uuid.uuid4(),
'benefit_type': Benefit.PERCENTAGE,
'benefit_value': 22,
}
data.update(**kwargs)
return data
def assert_enterprise_offer_conditions(self, offer, enterprise_customer_uuid, enterprise_customer_name,
enterprise_customer_catalog_uuid, expected_benefit_value,
expected_benefit_type, expected_name):
""" Assert the given offer's parameters match the expected values. """
self.assertEqual(str(offer.name), expected_name)
self.assertEqual(offer.offer_type, ConditionalOffer.SITE)
self.assertEqual(offer.status, ConditionalOffer.OPEN)
self.assertEqual(offer.max_basket_applications, 1)
self.assertEqual(offer.site, self.site)
self.assertEqual(offer.condition.enterprise_customer_uuid, enterprise_customer_uuid)
self.assertEqual(offer.condition.enterprise_customer_name, enterprise_customer_name)
self.assertEqual(offer.condition.enterprise_customer_catalog_uuid, enterprise_customer_catalog_uuid)
self.assertEqual(offer.benefit.proxy_class, class_path(BENEFIT_MAP[expected_benefit_type]))
self.assertEqual(offer.benefit.value, expected_benefit_value)
def assert_form_errors(self, data, expected_errors):
""" Assert that form validation fails with the expected errors. """
form = EnterpriseOfferForm(data=data)
self.assertFalse(form.is_valid())
self.assertEqual(form.errors, expected_errors)
def test_init(self):
""" The constructor should pull initial data from the passed-in instance. """
enterprise_offer = factories.EnterpriseOfferFactory()
form = EnterpriseOfferForm(instance=enterprise_offer)
self.assertEqual(
form['enterprise_customer_uuid'].value(),
enterprise_offer.condition.enterprise_customer_uuid.hex
)
self.assertEqual(
form['enterprise_customer_catalog_uuid'].value(),
enterprise_offer.condition.enterprise_customer_catalog_uuid.hex
)
self.assertEqual(form['benefit_type'].value(), enterprise_offer.benefit.proxy().benefit_class_type)
self.assertEqual(form['benefit_value'].value(), enterprise_offer.benefit.value)
def test_clean_percentage(self):
""" If a percentage benefit type is specified, the benefit value must never be greater than 100. """
data = self.generate_data(benefit_type=Benefit.PERCENTAGE, benefit_value=101)
self.assert_form_errors(data, {'benefit_value': ['Percentage discounts cannot be greater than 100%.']})
def test_clean_with_missing_start_date(self):
""" If an end date is specified, a start date must also be specified. """
data = self.generate_data(end_datetime='2017-01-01 00:00:00')
self.assert_form_errors(
data,
{'start_datetime': ['A start date must be specified when specifying an end date.']}
)
def test_clean_with_invalid_date_ordering(self):
""" The start date must always occur before the end date. """
data = self.generate_data(start_datetime='2017-01-02 00:00:00', end_datetime='2017-01-01 00:00:00')
self.assert_form_errors(data, {'start_datetime': ['The start date must occur before the end date.']})
def test_clean_with_conflicting_enterprise_customer_and_catalog_uuids(self):
""" If an offer already exists for the given Enterprise and Catalog, an error should be raised. """
offer = factories.EnterpriseOfferFactory()
data = self.generate_data(
enterprise_customer_uuid=offer.condition.enterprise_customer_uuid,
enterprise_customer_name=offer.condition.enterprise_customer_name,
enterprise_customer_catalog_uuid=offer.condition.enterprise_customer_catalog_uuid,
)
self.assert_form_errors(
data,
{
'enterprise_customer_uuid': [
'An offer already exists for this Enterprise & Catalog combination.'
],
'enterprise_customer_catalog_uuid': [
'An offer already exists for this Enterprise & Catalog combination.'
]
}
)
@httpretty.activate
def test_save_create(self):
""" A new ConditionalOffer, Benefit, and Condition should be created. """
data = self.generate_data()
self.mock_specific_enterprise_customer_api(data['enterprise_customer_uuid'])
form = EnterpriseOfferForm(request=self.request, data=data)
form.is_valid()
offer = form.save()
self.assert_enterprise_offer_conditions(
offer,
data['enterprise_customer_uuid'],
data['enterprise_customer_name'],
data['enterprise_customer_catalog_uuid'],
data['benefit_value'],
data['benefit_type'],
'Discount provided by {}.'.format(data['enterprise_customer_name']),
)
@httpretty.activate
def test_save_create_special_char_title(self):
""" When the Enterprise's name is international, new objects should still be created."""
enterprise_customer_uuid = uuid.uuid4()
data = self.generate_data(
enterprise_customer_uuid=enterprise_customer_uuid,
enterprise_customer_name=u'Sp\xe1nish Enterprise',
)
self.mock_specific_enterprise_customer_api(data['enterprise_customer_uuid'], name=u'Sp\xe1nish Enterprise')
form = EnterpriseOfferForm(request=self.request, data=data)
form.is_valid()
offer = form.save()
self.assert_enterprise_offer_conditions(
offer,
data['enterprise_customer_uuid'],
data['enterprise_customer_name'],
data['enterprise_customer_catalog_uuid'],
data['benefit_value'],
data['benefit_type'],
'Discount provided by Spánish Enterprise.'
)
@httpretty.activate
def test_save_edit(self):
""" Previously-created ConditionalOffer, Benefit, and Condition instances should be updated. """
offer = factories.EnterpriseOfferFactory()
data = self.generate_data(
enterprise_customer_uuid=offer.condition.enterprise_customer_uuid,
benefit_type=Benefit.FIXED
)
self.mock_specific_enterprise_customer_api(data['enterprise_customer_uuid'])
form = EnterpriseOfferForm(request=self.request, data=data, instance=offer)
form.is_valid()
form.save()
offer.refresh_from_db()
self.assert_enterprise_offer_conditions(
offer,
data['enterprise_customer_uuid'],
data['enterprise_customer_name'],
data['enterprise_customer_catalog_uuid'],
data['benefit_value'],
data['benefit_type'],
'Discount provided by {}.'.format(data['enterprise_customer_name']),
)
@httpretty.activate
def test_save_without_commit(self):
""" No data should be persisted to the database if the commit kwarg is set to False. """
data = self.generate_data()
form = EnterpriseOfferForm(request=self.request, data=data)
self.mock_specific_enterprise_customer_api(data['enterprise_customer_uuid'])
form.is_valid()
instance = form.save(commit=False)
self.assertIsNone(instance.pk)
self.assertFalse(hasattr(instance, 'benefit'))
self.assertFalse(hasattr(instance, 'condition'))
@httpretty.activate
def test_save_offer_name(self):
""" If a request object is sent, the offer name should include the enterprise name. """
data = self.generate_data()
self.mock_specific_enterprise_customer_api(data['enterprise_customer_uuid'])
form = EnterpriseOfferForm(request=self.request, data=data)
form.is_valid()
offer = form.save()
self.assert_enterprise_offer_conditions(
offer,
data['enterprise_customer_uuid'],
data['enterprise_customer_name'],
data['enterprise_customer_catalog_uuid'],
data['benefit_value'],
data['benefit_type'],
'Discount provided by {}.'.format(data['enterprise_customer_name']),
)
def test_create_when_conditional_offer_with_uuid_exists(self):
"""
An Enterprise Offer can be created if a conditional offer with different type and same UUIDs already exists.
"""
data = self.generate_data()
factories.EnterpriseOfferFactory(
condition__enterprise_customer_uuid=data['enterprise_customer_uuid'],
condition__enterprise_customer_name=data['enterprise_customer_name'],
condition__enterprise_customer_catalog_uuid=data['enterprise_customer_catalog_uuid'],
offer_type=ConditionalOffer.VOUCHER,
)
form = EnterpriseOfferForm(request=self.request, data=data)
self.assertTrue(form.is_valid())
...@@ -2,6 +2,7 @@ import httpretty ...@@ -2,6 +2,7 @@ import httpretty
import mock import mock
from django.conf import settings from django.conf import settings
from django.template import Context, Template from django.template import Context, Template
from oscar.core.loading import get_model
from ecommerce.core.tests import toggle_switch from ecommerce.core.tests import toggle_switch
from ecommerce.coupons.tests.mixins import CouponMixin from ecommerce.coupons.tests.mixins import CouponMixin
...@@ -9,6 +10,7 @@ from ecommerce.enterprise.exceptions import EnterpriseDoesNotExist ...@@ -9,6 +10,7 @@ from ecommerce.enterprise.exceptions import EnterpriseDoesNotExist
from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin
from ecommerce.tests.testcases import TestCase from ecommerce.tests.testcases import TestCase
Benefit = get_model('offer', 'Benefit')
TEST_ENTERPRISE_CUSTOMER_UUID = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' TEST_ENTERPRISE_CUSTOMER_UUID = 'cf246b88-d5f6-4908-a522-fc307e0b0c59'
......
import uuid
import httpretty
from django.core.urlresolvers import reverse
from oscar.core.loading import get_model
from ecommerce.enterprise.benefits import EnterprisePercentageDiscountBenefit
from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin
from ecommerce.extensions.test import factories
from ecommerce.programs.custom import class_path
from ecommerce.tests.testcases import TestCase, ViewTestMixin
Benefit = get_model('offer', 'Benefit')
ConditionalOffer = get_model('offer', 'ConditionalOffer')
class EnterpriseOfferListViewTests(EnterpriseServiceMockMixin, ViewTestMixin, TestCase):
path = reverse('enterprise:offers:list')
def setUp(self):
super(EnterpriseOfferListViewTests, self).setUp()
httpretty.enable()
self.mock_access_token_response()
def tearDown(self):
super(EnterpriseOfferListViewTests, self).tearDown()
httpretty.disable()
httpretty.reset()
def test_get(self):
""" The context should contain a list of enterprise offers. """
# These should be ignored since their associated Condition objects do NOT have an Enterprise Customer UUID.
factories.ConditionalOfferFactory.create_batch(3)
enterprise_offers = factories.EnterpriseOfferFactory.create_batch(4, site=self.site)
for offer in enterprise_offers:
self.mock_specific_enterprise_customer_api(offer.condition.enterprise_customer_uuid)
response = self.assert_get_response_status(200)
self.assertEqual(list(response.context['object_list']), enterprise_offers)
# The page should load even if the Enterprise API is inaccessible
httpretty.disable()
response = self.assert_get_response_status(200)
self.assertEqual(list(response.context['object_list']), enterprise_offers)
def test_get_queryset(self):
""" Should return only Conditional Offers with Site offer type. """
# Conditional Offer should contain a condition with enterprise customer uuid set in order to be returned
site_conditional_offer = factories.EnterpriseOfferFactory(site=self.site)
# Conditional Offer with null Site or non-matching Site should not be returned
null_site_offer = factories.EnterpriseOfferFactory()
different_site_offer = factories.EnterpriseOfferFactory(site=factories.SiteConfigurationFactory().site)
enterprise_offers = [
site_conditional_offer,
factories.EnterpriseOfferFactory(offer_type=ConditionalOffer.VOUCHER),
factories.ConditionalOfferFactory(offer_type=ConditionalOffer.SITE),
null_site_offer,
different_site_offer
]
for offer in enterprise_offers:
self.mock_specific_enterprise_customer_api(offer.condition.enterprise_customer_uuid)
response = self.client.get(self.path)
self.assertEqual(list(response.context['object_list']), [site_conditional_offer])
class EnterpriseOfferUpdateViewTests(EnterpriseServiceMockMixin, ViewTestMixin, TestCase):
def setUp(self):
super(EnterpriseOfferUpdateViewTests, self).setUp()
self.enterprise_offer = factories.EnterpriseOfferFactory(site=self.site)
self.path = reverse('enterprise:offers:edit', kwargs={'pk': self.enterprise_offer.pk})
# NOTE: We activate httpretty here so that we don't have to decorate every test method.
httpretty.enable()
self.mock_specific_enterprise_customer_api(self.enterprise_offer.condition.enterprise_customer_uuid)
def tearDown(self):
super(EnterpriseOfferUpdateViewTests, self).tearDown()
httpretty.disable()
httpretty.reset()
def test_get(self):
""" The context should contain the enterprise offer. """
response = self.assert_get_response_status(200)
self.assertEqual(response.context['object'], self.enterprise_offer)
# The page should load even if the Enterprise API is inaccessible
httpretty.disable()
response = self.assert_get_response_status(200)
self.assertEqual(response.context['object'], self.enterprise_offer)
def test_post(self):
""" The enterprise offer should be updated. """
data = {
'enterprise_customer_uuid': self.enterprise_offer.condition.enterprise_customer_uuid,
'enterprise_customer_catalog_uuid': self.enterprise_offer.condition.enterprise_customer_catalog_uuid,
'benefit_type': self.enterprise_offer.benefit.proxy().benefit_class_type,
'benefit_value': self.enterprise_offer.benefit.value,
}
response = self.client.post(self.path, data, follow=False)
self.assertRedirects(response, self.path)
@httpretty.activate
class EnterpriseOfferCreateViewTests(EnterpriseServiceMockMixin, ViewTestMixin, TestCase):
path = reverse('enterprise:offers:new')
def test_post(self):
""" A new enterprise offer should be created. """
expected_ec_uuid = uuid.uuid4()
expected_ec_catalog_uuid = uuid.uuid4()
self.mock_specific_enterprise_customer_api(expected_ec_uuid)
expected_benefit_value = 10
data = {
'enterprise_customer_uuid': expected_ec_uuid,
'enterprise_customer_catalog_uuid': expected_ec_catalog_uuid,
'benefit_type': Benefit.PERCENTAGE,
'benefit_value': expected_benefit_value,
}
response = self.client.post(self.path, data, follow=False)
enterprise_offer = ConditionalOffer.objects.get()
self.assertRedirects(response, reverse('enterprise:offers:edit', kwargs={'pk': enterprise_offer.pk}))
self.assertIsNone(enterprise_offer.start_datetime)
self.assertIsNone(enterprise_offer.end_datetime)
self.assertEqual(enterprise_offer.condition.enterprise_customer_uuid, expected_ec_uuid)
self.assertEqual(enterprise_offer.condition.enterprise_customer_catalog_uuid, expected_ec_catalog_uuid)
self.assertEqual(enterprise_offer.benefit.type, '')
self.assertEqual(enterprise_offer.benefit.value, expected_benefit_value)
self.assertEqual(enterprise_offer.benefit.proxy_class, class_path(EnterprisePercentageDiscountBenefit))
from django.conf.urls import include, url
from ecommerce.enterprise import views
OFFER_URLS = [
url(r'^$', views.EnterpriseOfferListView.as_view(), name='list'),
url(r'^new/$', views.EnterpriseOfferCreateView.as_view(), name='new'),
url(r'^(?P<pk>[\d]+)/edit/$', views.EnterpriseOfferUpdateView.as_view(), name='edit'),
]
urlpatterns = [
url(r'^offers/', include(OFFER_URLS, namespace='offers')),
]
...@@ -13,7 +13,8 @@ from django.core.urlresolvers import reverse ...@@ -13,7 +13,8 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from edx_rest_api_client.client import EdxRestApiClient from edx_rest_api_client.client import EdxRestApiClient
from oscar.core.loading import get_model from oscar.core.loading import get_model
from slumber.exceptions import HttpNotFoundError from requests.exceptions import ConnectionError, Timeout
from slumber.exceptions import SlumberHttpBaseException
from ecommerce.core.utils import traverse_pagination from ecommerce.core.utils import traverse_pagination
from ecommerce.enterprise.exceptions import EnterpriseDoesNotExist from ecommerce.enterprise.exceptions import EnterpriseDoesNotExist
...@@ -62,7 +63,7 @@ def get_enterprise_customer(site, uuid): ...@@ -62,7 +63,7 @@ def get_enterprise_customer(site, uuid):
try: try:
response = client.get() response = client.get()
except HttpNotFoundError: except (ConnectionError, SlumberHttpBaseException, Timeout):
return None return None
return { return {
'name': response['name'], 'name': response['name'],
......
# TODO: Refactor this to consolidate it with `ecommerce.programs.views`.
import logging
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, UpdateView
from oscar.core.loading import get_model
from ecommerce.core.views import StaffOnlyMixin
from ecommerce.enterprise.forms import EnterpriseOfferForm
from ecommerce.enterprise.utils import get_enterprise_customer
Benefit = get_model('offer', 'Benefit')
ConditionalOffer = get_model('offer', 'ConditionalOffer')
logger = logging.getLogger(__name__)
class EnterpriseOfferViewMixin(StaffOnlyMixin):
model = ConditionalOffer
def get_context_data(self, **kwargs):
context = super(EnterpriseOfferViewMixin, self).get_context_data(**kwargs)
context['admin'] = 'enterprise_offers'
return context
def get_queryset(self):
return super(EnterpriseOfferViewMixin, self).get_queryset().filter(
site=self.request.site.id,
condition__enterprise_customer_uuid__isnull=False,
offer_type=ConditionalOffer.SITE
)
class EnterpriseOfferProcessFormViewMixin(EnterpriseOfferViewMixin):
form_class = EnterpriseOfferForm
success_message = _('Enterprise offer updated!')
def get_form_kwargs(self):
kwargs = super(EnterpriseOfferProcessFormViewMixin, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def get_context_data(self, **kwargs):
context = super(EnterpriseOfferProcessFormViewMixin, self).get_context_data(**kwargs)
context.update({
'editing': False,
})
return context
def get_success_url(self):
messages.add_message(self.request, messages.SUCCESS, self.success_message)
return reverse('enterprise:offers:edit', kwargs={'pk': self.object.pk})
class EnterpriseOfferCreateView(EnterpriseOfferProcessFormViewMixin, CreateView):
initial = {
'benefit_type': Benefit.PERCENTAGE,
}
success_message = _('Enterprise offer created!')
template_name = 'enterprise/enterpriseoffer_form.html'
class EnterpriseOfferUpdateView(EnterpriseOfferProcessFormViewMixin, UpdateView):
template_name = 'enterprise/enterpriseoffer_form.html'
def get_context_data(self, **kwargs):
context = super(EnterpriseOfferUpdateView, self).get_context_data(**kwargs)
context.update({
'editing': True,
'enterprise_customer': get_enterprise_customer(
self.request.site,
self.object.condition.enterprise_customer_uuid
)
})
return context
class EnterpriseOfferListView(EnterpriseOfferViewMixin, ListView):
template_name = 'enterprise/enterpriseoffer_list.html'
# NOTE (CCB): These functions are copied from oscar.apps.offer.custom due to a bug # NOTE (CCB): These functions are copied from oscar.apps.offer.custom due to a bug
# detailed at https://github.com/django-oscar/django-oscar/issues/2345. This file # detailed at https://github.com/django-oscar/django-oscar/issues/2345. This file
# should be removed after the fix for the bug is released. # should be removed after the fix for the bug is released.
# TODO: Issue above is fixed; we need to upgrade to django-oscar==1.5 and this can be removed.
# (https://github.com/django-oscar/django-oscar/commit/38367f9ca854cd21eaf19a174f24b59a0e65cf79)
from oscar.core.loading import get_model from oscar.core.loading import get_model
Condition = get_model('offer', 'Condition') Condition = get_model('offer', 'Condition')
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
</h1> </h1>
</div> </div>
<form id="programOfferForm" method="post"> <form id="offerForm" method="post">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
...@@ -78,5 +78,5 @@ ...@@ -78,5 +78,5 @@
{% block javascript %} {% block javascript %}
<script src="{% static 'js/pages/program_offer_form_page.js' %}"></script> <script src="{% static 'js/pages/offer_form_page.js' %}"></script>
{% endblock %} {% endblock %}
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</h1> </h1>
</div> </div>
<table id="programOfferTable" class="copy copy-base table table-striped table-bordered" cellspacing="0"> <table id="offerTable" class="copy copy-base table table-striped table-bordered" cellspacing="0">
<caption class="sr-only">{% trans "Current program offers" %}</caption> <caption class="sr-only">{% trans "Current program offers" %}</caption>
<thead> <thead>
<tr> <tr>
...@@ -63,5 +63,5 @@ ...@@ -63,5 +63,5 @@
{% endblock footer %} {% endblock footer %}
{% block javascript %} {% block javascript %}
<script src="{% static 'js/pages/program_offer_list_page.js' %}"></script> <script src="{% static 'js/pages/offer_list_page.js' %}"></script>
{% endblock %} {% endblock %}
import uuid import uuid
import httpretty import httpretty
from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from oscar.core.loading import get_model from oscar.core.loading import get_model
...@@ -9,45 +8,12 @@ from ecommerce.extensions.test import factories ...@@ -9,45 +8,12 @@ from ecommerce.extensions.test import factories
from ecommerce.programs.benefits import PercentageDiscountBenefitWithoutRange from ecommerce.programs.benefits import PercentageDiscountBenefitWithoutRange
from ecommerce.programs.custom import class_path from ecommerce.programs.custom import class_path
from ecommerce.programs.tests.mixins import ProgramTestMixin from ecommerce.programs.tests.mixins import ProgramTestMixin
from ecommerce.tests.testcases import CacheMixin, TestCase from ecommerce.tests.testcases import TestCase, ViewTestMixin
Benefit = get_model('offer', 'Benefit') Benefit = get_model('offer', 'Benefit')
ConditionalOffer = get_model('offer', 'ConditionalOffer') ConditionalOffer = get_model('offer', 'ConditionalOffer')
class ViewTestMixin(CacheMixin):
path = None
def setUp(self):
super(ViewTestMixin, self).setUp()
user = self.create_user(is_staff=True)
self.client.login(username=user.username, password=self.password)
def assert_get_response_status(self, status_code):
""" Asserts the HTTP status of a GET responses matches the expected status. """
response = self.client.get(self.path)
self.assertEqual(response.status_code, status_code)
return response
def test_login_required(self):
""" Users are required to login before accessing the view. """
self.client.logout()
response = self.assert_get_response_status(302)
self.assertIn(settings.LOGIN_URL, response.url)
def test_staff_only(self):
""" The view should only be accessible to staff. """
self.client.logout()
user = self.create_user(is_staff=False)
self.client.login(username=user.username, password=self.password)
self.assert_get_response_status(404)
user.is_staff = True
user.save()
self.assert_get_response_status(200)
class ProgramOfferListViewTests(ProgramTestMixin, ViewTestMixin, TestCase): class ProgramOfferListViewTests(ProgramTestMixin, ViewTestMixin, TestCase):
path = reverse('programs:offers:list') path = reverse('programs:offers:list')
......
...@@ -6,7 +6,7 @@ require([ ...@@ -6,7 +6,7 @@ require([
'use strict'; 'use strict';
$(function() { $(function() {
$('#programOfferForm').find('.add-pikaday').each(function() { $('#offerForm').find('.add-pikaday').each(function() {
new Pikaday({ new Pikaday({
field: this, field: this,
format: 'YYYY-MM-DD HH:mm:ss', format: 'YYYY-MM-DD HH:mm:ss',
......
...@@ -6,7 +6,7 @@ require([ ...@@ -6,7 +6,7 @@ require([
'use strict'; 'use strict';
$(function() { $(function() {
$('#programOfferTable').DataTable({ $('#offerTable').DataTable({
paging: true paging: true
}); });
}); });
......
...@@ -7,5 +7,8 @@ ...@@ -7,5 +7,8 @@
<a {% if admin == "program_offers" %}class="selected"{% endif %} href="{% url 'programs:offers:list' %}"> <a {% if admin == "program_offers" %}class="selected"{% endif %} href="{% url 'programs:offers:list' %}">
{% trans "Program Offers" %} {% trans "Program Offers" %}
</a> </a>
<a {% if admin == "enterprise_offers" %}class="selected"{% endif %} href="{% url 'enterprise:offers:list' %}">
{% trans "Enterprise Offers" %}
</a>
</div> </div>
</nav> </nav>
from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.test import LiveServerTestCase as DjangoLiveServerTestCase from django.test import LiveServerTestCase as DjangoLiveServerTestCase
from django.test import TestCase as DjangoTestCase from django.test import TestCase as DjangoTestCase
...@@ -16,6 +17,39 @@ class CacheMixin(object): ...@@ -16,6 +17,39 @@ class CacheMixin(object):
super(CacheMixin, self).tearDown() super(CacheMixin, self).tearDown()
class ViewTestMixin(CacheMixin):
path = None
def setUp(self):
super(ViewTestMixin, self).setUp()
user = self.create_user(is_staff=True)
self.client.login(username=user.username, password=self.password)
def assert_get_response_status(self, status_code):
""" Asserts the HTTP status of a GET responses matches the expected status. """
response = self.client.get(self.path)
self.assertEqual(response.status_code, status_code)
return response
def test_login_required(self):
""" Users are required to login before accessing the view. """
self.client.logout()
response = self.assert_get_response_status(302)
self.assertIn(settings.LOGIN_URL, response.url)
def test_staff_only(self):
""" The view should only be accessible to staff. """
self.client.logout()
user = self.create_user(is_staff=False)
self.client.login(username=user.username, password=self.password)
self.assert_get_response_status(404)
user.is_staff = True
user.save()
self.assert_get_response_status(200)
class TestCase(TestServerUrlMixin, UserMixin, SiteMixin, CacheMixin, DjangoTestCase): class TestCase(TestServerUrlMixin, UserMixin, SiteMixin, CacheMixin, DjangoTestCase):
""" """
Base test case for ecommerce tests. Base test case for ecommerce tests.
......
...@@ -62,6 +62,7 @@ urlpatterns = AUTH_URLS + WELL_KNOWN_URLS + [ ...@@ -62,6 +62,7 @@ urlpatterns = AUTH_URLS + WELL_KNOWN_URLS + [
url(r'^i18n/', include('django.conf.urls.i18n')), url(r'^i18n/', include('django.conf.urls.i18n')),
url(r'^jsi18n/$', JavaScriptCatalog.as_view(packages=['courses']), name='javascript-catalog'), url(r'^jsi18n/$', JavaScriptCatalog.as_view(packages=['courses']), name='javascript-catalog'),
url(r'^programs/', include('ecommerce.programs.urls', namespace='programs')), url(r'^programs/', include('ecommerce.programs.urls', namespace='programs')),
url(r'^enterprise/', include('ecommerce.enterprise.urls', namespace='enterprise')),
] ]
# Install Oscar extension URLs # Install Oscar extension URLs
......
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