pricing: add pricing variables (#64561)

This commit is contained in:
Lauréline Guérin 2022-05-05 16:53:48 +02:00
parent eb67f72b35
commit 81722913a4
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
3 changed files with 110 additions and 16 deletions

View File

@ -0,0 +1,17 @@
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('pricing', '0002_pricing'),
]
operations = [
migrations.AddField(
model_name='pricing',
name='extra_variables',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
),
]

View File

@ -18,7 +18,7 @@ import decimal
from django.contrib.postgres.fields import JSONField
from django.db import models
from django.template import Context, Template, TemplateSyntaxError
from django.template import Context, RequestContext, Template, TemplateSyntaxError
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
@ -173,6 +173,7 @@ class Pricing(models.Model):
through='PricingCriteriaCategory',
)
criterias = models.ManyToManyField(Criteria)
extra_variables = JSONField(blank=True, default=dict)
class Meta:
ordering = ['label']
@ -189,6 +190,18 @@ class Pricing(models.Model):
def base_slug(self):
return slugify(self.label)
def get_extra_variables(self, request, original_context):
result = {}
context = RequestContext(request)
context.push(original_context)
for key, tplt in (self.extra_variables or {}).items():
try:
template = Template(tplt)
except TemplateSyntaxError:
continue
result[key] = template.render(context)
return result
class PricingCriteriaCategory(models.Model):
pricing = models.ForeignKey(Pricing, on_delete=models.CASCADE)
@ -219,9 +232,9 @@ class AgendaPricing(models.Model):
pricing_data = JSONField(null=True)
@staticmethod
def get_pricing_data(event, user_external_id, adult_external_id):
def get_pricing_data(request, event, user_external_id, adult_external_id):
agenda_pricing = AgendaPricing.get_agenda_pricing(event)
context = agenda_pricing.get_pricing_context(event, user_external_id, adult_external_id)
context = agenda_pricing.get_pricing_context(request, user_external_id, adult_external_id)
pricing, criterias = agenda_pricing.compute_pricing(context)
modifier = agenda_pricing.get_booking_modifier(event, user_external_id)
return agenda_pricing.aggregate_pricing_data(pricing, criterias, context, modifier)
@ -262,12 +275,13 @@ class AgendaPricing(models.Model):
except (Subscription.DoesNotExist, Subscription.MultipleObjectsReturned):
raise PricingSubscriptionError
def get_pricing_context(self, event, user_external_id, adult_external_id):
# FIXME: compute agenda pricing variables, add event data
return {
'qf': 2,
'domicile': 'commune',
}
def get_pricing_context(self, request, user_external_id, adult_external_id):
context = {'user_external_id': user_external_id, 'adult_external_id': adult_external_id}
if ':' in user_external_id:
context['user_external_raw_id'] = user_external_id.split(':')[1]
if ':' in adult_external_id:
context['adult_external_raw_id'] = adult_external_id.split(':')[1]
return self.pricing.get_extra_variables(request, context)
def compute_pricing(self, context):
criterias = {}

View File

@ -1,7 +1,12 @@
import datetime
import json
from unittest import mock
import pytest
from django.template import Context
from django.test.client import RequestFactory
from django.utils.timezone import make_aware, now
from publik_django_templatetags.wcs.context_processors import Cards
from chrono.agendas.models import Agenda, Booking, CheckType, CheckTypeGroup, Event, Subscription
from chrono.pricing.models import (
@ -24,6 +29,28 @@ from chrono.pricing.models import (
pytestmark = pytest.mark.django_db
@pytest.fixture
def context():
return Context(
{
'cards': Cards(),
'request': RequestFactory().get('/'),
}
)
class MockedRequestResponse(mock.Mock):
status_code = 200
def json(self):
return json.loads(self.content)
def mocked_requests_send(request, **kwargs):
data = [{'id': 1, 'fields': {'foo': 'bar'}}, {'id': 2, 'fields': {'foo': 'baz'}}] # fake result
return MockedRequestResponse(content=json.dumps({'data': data}))
def test_criteria_category_slug():
category = CriteriaCategory.objects.create(label='Foo bar')
assert category.slug == 'foo-bar'
@ -204,7 +231,8 @@ def test_get_agenda_pricing_event_date(event_date, found):
AgendaPricing.get_agenda_pricing(event)
def test_get_pricing_context():
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
def test_get_pricing_context(mock_send, context, nocache):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10
@ -216,10 +244,39 @@ def test_get_pricing_context():
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2021, month=10, day=1),
)
assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == {
assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == {}
pricing.extra_variables = {
'foo': 'bar',
'qf': '{{ 40|add:2 }}',
'domicile': 'commune',
'qf': 2,
'ids': '{{ cards|objects:"foo"|getlist:"id"|join:"," }}',
'bad': '{% if foo %}',
}
pricing.save()
assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == {
'foo': 'bar',
'qf': '42',
'domicile': 'commune',
'ids': '1,2',
}
# user_external_id and adult_external_id can be used in variables
pricing.extra_variables = {
'qf': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:user_external_id|filter_by:"bar"|filter_value:adult_external_id|list }}',
}
pricing.save()
mock_send.reset_mock()
agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35')
assert 'filter-foo=child%3A42&' in mock_send.call_args_list[0][0][0].url
assert 'filter-bar=parent%3A35&' in mock_send.call_args_list[0][0][0].url
pricing.extra_variables = {
'qf': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:user_external_raw_id|filter_by:"bar"|filter_value:adult_external_raw_id|list }}',
}
pricing.save()
mock_send.reset_mock()
agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35')
assert 'filter-foo=42&' in mock_send.call_args_list[0][0][0].url
assert 'filter-bar=35&' in mock_send.call_args_list[0][0][0].url
@pytest.mark.parametrize(
@ -745,7 +802,7 @@ def test_get_booking_modifier_booking_presence():
}
def test_get_pricing_data():
def test_get_pricing_data(context):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
agenda=agenda,
@ -761,7 +818,13 @@ def test_get_pricing_data():
)
category = CriteriaCategory.objects.create(label='Foo', slug='foo')
criteria = Criteria.objects.create(label='Bar', slug='bar', condition='True', category=category)
pricing = Pricing.objects.create(label='Foo bar')
pricing = Pricing.objects.create(
label='Foo bar',
extra_variables={
'domicile': 'commune',
'qf': '2',
},
)
pricing.criterias.add(criteria)
pricing.categories.add(category, through_defaults={'order': 1})
AgendaPricing.objects.create(
@ -773,12 +836,12 @@ def test_get_pricing_data():
'foo:bar': 42,
},
)
assert AgendaPricing.get_pricing_data(event, 'child:42', 'parent:35') == {
assert AgendaPricing.get_pricing_data(context['request'], event, 'child:42', 'parent:35') == {
'pricing': 0,
'calculation_details': {
'pricing': 42,
'criterias': {'foo': 'bar'},
'context': {'domicile': 'commune', 'qf': 2},
'context': {'domicile': 'commune', 'qf': '2'},
},
'booking_details': {
'status': 'not-booked',