Package: django-offermaker
django-offermakerlibrary is a solution for Django applications, which allows
to createmulti variant offersand use them on your site.Multi variant offeris a structure which defines dependencies between values for
given set of fields.Eg. for the fieldscredit contributionandnumber of instalmentswe
have the following dependencies:for0%credit contribution, it is required to have maximum12instalments,for10%+contribution, it is allowed to have12-24instalments,and for20%+contribution, it is allowed to have12-36instalments.Functions ofdjango-offermakerCreate and editmulti variant offerby usingOffermaker Admin Editor.Displaymulti variant offerin tabular way by usingOffermaker Template
Tag.Display form which dynamically adjusts to user filled values based onmulti variant offerby usingOfferMakerFormView Generic View.Make decision which variant ofmulti variant offeris suitable for
given parameters by usingdecide helper.Changelog0.9.8Selenium test for offermaker admin editor and offermaker form and bug fixes
in supported Django and Python versions.0.9.7Added Class Based View for offermaker formAdded decide() method0.9.5Small bug fixes0.9.4Django 1.7 supportPython 3 supportSupportEnvironments: Python 2.6, Python 2.7, Python 3.2, Python 3.3, Python 3.4, PyPy,Django versions: Django 1.5, Django 1.6, Django 1.7 (Python 2.6 is not supported),Demo applicationYou can check it onlinehttp://offermaker.kjw.ptOr checkout and install locally:git clone git@github.com:kkujawinski/django-offermaker-demo.gitQuick startInstall django-offermakerpip install django-offermakerSite configuration in settings.pyINSTALLED_APPS = (
    ...
    'offermaker',
)Create Django form with needed fields:from django import forms

class MyForm(forms.Form):
    product = forms.ChoiceField(
        label='Product',
        choices=(
            ('', '---'), ('PROD1', 'Product X'), ('PROD2', 'Product Y'), ('PROD3', 'Product Z'),
        ),
        required=False)
    crediting_period = forms.ChoiceField(
        label='Crediting period',
        choices=(('', '---'), ('12', '12'), ('24', '24'), ('36', '36'), ('48', '48')))
    interest_rate = forms.FloatField(label='Interest rate', min_value=1, max_value=5)
    contribution = forms.FloatField(label='Contribution', min_value=0)

    # # Uncomment for Django 1.5
    # def __init__(self, *args, **kwargs):
    #     super(MyForm, self).__init__(*args, **kwargs)
    #     self.fields['interest_rate'].widget.attrs['data-om-type'] = 'number'
    #     self.fields['interest_rate'].widget.attrs['data-om-min'] = 1
    #     self.fields['interest_rate'].widget.attrs['data-om-max'] = 5
    #     self.fields['contribution'].widget.attrs['data-om-type'] = 'number'
    #     self.fields['contribution'].widget.attrs['data-om-min'] = 0Define your offer (in case you do not store it in database):offer = {
    'params': {},
    'variants': [[
        {
            'params': {
                'crediting_period': ['24'],
                'product': ['PROD1']
            }
        }, {
            'params': {
                'crediting_period': ['12', '36', '48'],
                'product': ['PROD2']
            }
        }, {
            'params': {
                'product': ['PROD3']
            }
        }
    ], [
        {
            'params': {
                'contribution': [[10, 20]],
                'interest_rate': [[2, 2]],
                'product': ['PROD1']
            }
        }, {
            'params': {
                'contribution': [[30, 40]],
                'interest_rate': [[4, 4]],
                'product': ['PROD1']
            }
        }, {
            'params': {
                'contribution': [[30, 70]],
                'interest_rate': [[5, 5]],
                'product': ['PROD2', 'PROD3']
            }
        }
    ]]
}Offer form:Use dispatcher code in Django viewimport offermaker

class MyOfferFormView(offermaker.OfferMakerFormView):
    form_class = MyForm
    offermaker_offer = offer
    template_name = 'my_offer_form_view.html'

my_offer_form_view = MyOfferFormView.as_view()Initialize offerform in template<head>
{% load offermaker %}
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
{% offermaker_javascript %}
</head>

<body>

<form action="?" method="post" id="offer_form">
    <div class="alert-placeholder" style="height: 30px;"></div>
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Submit</button>
</form>


<script type="text/javascript">
    (function() {
        $('#offer_form').offer_form();
    })();
</script>Offer preview with Offermaker Template TagPass offer form object from view to template:class MyOfferPreviewView(TemplateView):
    template_name = 'offer_preview.html'

    def get_context_data(self):
        output = super(MyOfferPreviewView, self).get_context_data()
        output['offer'] = offermaker.OfferMakerCore(MyForm, offer)
        return outputUse proper template tag in template to print table:{% load offermaker %}

{% offermaker_preview offer %}Offermaker Admin Editor:Use OfferJSONField field in your model. Remember to pass your django form created in 3.:import offermaker

class MyOfferMakerField(offermaker.OfferJSONField):
    form_object = MyForm()

class MyOffer(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=30)
    offer = MyOfferMakerField()Create your own Admin Site for model:import models

class OfferAdmin(admin.ModelAdmin):
    list_display = ('name',)
    search_fields = ('name', 'user')
    fields = ('name', 'offer')

    # # Uncomment for Django 1.5
    # class Media:
    #     js = ('//code.jquery.com/jquery-1.11.0.min.js',)


admin.site.register(models.Offer, OfferAdmin)Decide helper:core_object = offermaker.OfferMakerCore(MyForm, offer)

result = core_object.decide({'crediting_period': 24})
print(result['product'].items)
# frozenset({'PROD1', 'PROD3'})
print(result['interest_rate'].ranges)
# frozenset({(4, 4), (5, 5), (2, 2)})
print(result['contribution'].ranges)
# frozenset({(10, 20), (30, 70)})

result = core_object.decide({'crediting_period': 24, 'interest_rate': 2})
print(result['product'].fixed)
# PROD1Basic customizationUsing offers stored in database:you need to pass proper offer object to Offermaker in form/preview view:offer = MyOffer.objects.filter(id=request.GET['id']).first()
core_object = offermaker.OfferMakerCore(MyForm, offer.offer)and configure proper params to be used in ajax requests:$('#offer_form').offer_form({
    ajax_extra_params: function(params) {
        return { id: {{ request.GET.id }} };
    },
});Substituting builtin formatters for infotip and error alerts:$('#offer_form').offer_form({
    error_alert_factory: function (msg) {
        var $error = $('<p class="error"><span>' + msg + '</span></p>');
        $('.alert-placeholder', $form).append($error);
        return $error;
    },
    tooltip_factory: function ($field, msg) {
        var $tooltip = $('<p class="infotip">' + msg + '</p>');
        $field.parent().append($tooltip);
        return $tooltip;
    }
});Use builtin formatters for Twitter Bootstap3:(function() {
    $('#offer_form').offer_form({
        bootstrap3: true,
    });
})();Customizing messages:(function() {
    $('#offer_form').offer_form({
        msgs: {
            'NO_VARIANTS': 'No matching variants',
            'INFO_ITEMS': 'Available values are: %s.',
            'INFO_FIXED': 'Only available value is %s.',
            'RANGE_left': 'to %2$s',
            'RANGE_right': 'from %1$s',
            'RANGE_both': 'from %1$s to %2$s',
            'AND': ' and '
        },
        iteration_str: function (items) {
            return items.slice(0, -2).concat(items.slice(-2).join(msgs.AND)).join(', ');
        }
    });
})();Creating preview table for certain fields:{% offermaker_preview offer fields='product, crediting_period' %}Add html attributes to generated preview table:{% offermaker_preview offer class='table table-bordered' %}