Cart: ensure free price input is decimal (PRETIXEU-80N)

Co-authored-by: Phin Wolkwitz <wolkwitz@rami.io>
This commit is contained in:
Richard Schreiber
2023-03-21 08:51:49 +01:00
committed by GitHub
parent 5ad0f92776
commit e9b22b7d33
5 changed files with 59 additions and 7 deletions

View File

@@ -31,6 +31,7 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import re
import uuid
from collections import Counter, defaultdict, namedtuple
from datetime import datetime, time, timedelta
@@ -38,6 +39,7 @@ from decimal import Decimal
from typing import List, Optional
from celery.exceptions import MaxRetriesExceededError
from django import forms
from django.core.exceptions import ValidationError
from django.db import DatabaseError, transaction
from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Value
@@ -135,6 +137,7 @@ error_messages = {
'some_subevent_ended': gettext_lazy(
'The booking period for one of the events in your cart has ended. The affected '
'positions have been removed from your cart.'),
'price_not_a_number': gettext_lazy('The entered price is not a number.'),
'price_too_high': gettext_lazy('The entered price is to high.'),
'voucher_invalid': gettext_lazy('This voucher code is not known in our database.'),
'voucher_min_usages': gettext_lazy(
@@ -725,9 +728,18 @@ class CartManager:
price_after_voucher = listed_price
custom_price = None
if item.free_price and i.get('price'):
custom_price = Decimal(str(i.get('price')).replace(",", "."))
custom_price = re.sub('[^0-9.,]', '', str(i.get('price')))
if not custom_price:
raise CartError(error_messages['price_not_a_number'])
try:
custom_price = forms.DecimalField(localize=True).to_python(custom_price)
except:
try:
custom_price = Decimal(custom_price)
except:
raise CartError(error_messages['price_not_a_number'])
if custom_price > 99_999_999_999:
raise ValueError('price_too_high')
raise CartError(error_messages['price_too_high'])
op = self.AddOperation(
count=i['count'],
@@ -840,9 +852,18 @@ class CartManager:
listed_price = get_listed_price(item, variation, cp.subevent)
custom_price = None
if item.free_price and a.get('price'):
custom_price = Decimal(str(a.get('price')).replace(",", "."))
custom_price = re.sub('[^0-9.,]', '', a.get('price'))
if not custom_price:
raise CartError(error_messages['price_not_a_number'])
try:
custom_price = forms.DecimalField(localize=True).to_python(custom_price)
except:
try:
custom_price = Decimal(custom_price)
except:
raise CartError(error_messages['price_not_a_number'])
if custom_price > 99_999_999_999:
raise ValueError('price_too_high')
raise CartError(error_messages['price_too_high'])
# Fix positions with wrong price (TODO: happens out-of-cartmanager-transaction and therefore a little hacky)
for ca in current_addons[cp][a['item'], a['variation']]:

View File

@@ -19,9 +19,11 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import re
from decimal import Decimal
from typing import List, Optional, Tuple
from django import forms
from django.db.models import Q
from django.utils.timezone import now
@@ -69,7 +71,16 @@ def get_price(item: Item, variation: ItemVariation = None,
subtract_from_gross=bundled_sum)
elif item.free_price and custom_price is not None and custom_price != "":
if not isinstance(custom_price, Decimal):
custom_price = Decimal(str(custom_price).replace(",", "."))
custom_price = re.sub('[^0-9.,]', '', str(custom_price))
if not custom_price:
raise ValueError('price_not_a_number')
try:
custom_price = forms.DecimalField(localize=True).to_python(custom_price)
except:
try:
custom_price = Decimal(custom_price)
except:
raise ValueError('price_not_a_number')
if custom_price > 99_999_999_999:
raise ValueError('price_too_high')

View File

@@ -23,6 +23,7 @@ import json
from decimal import Decimal
import pytest
from django.utils import translation
from django.utils.timezone import now
from django_countries.fields import Country
@@ -196,7 +197,8 @@ def test_free_price_accepted(item):
@pytest.mark.django_db
def test_free_price_string(item):
item.free_price = True
assert get_price(item, custom_price='42,00').gross == Decimal('42.00')
with translation.override('de'):
assert get_price(item, custom_price='42,00').gross == Decimal('42.00')
@pytest.mark.django_db
@@ -209,7 +211,7 @@ def test_free_price_float(item):
def test_free_price_limit(item):
item.free_price = True
with pytest.raises(ValueError):
get_price(item, custom_price=Decimal('200000000'))
get_price(item, custom_price=Decimal('200000000000'))
@pytest.mark.django_db

View File

@@ -635,6 +635,21 @@ class CartTest(CartTestMixin, TestCase):
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 24)
def test_free_price_numeric(self):
self.ticket.free_price = True
self.ticket.save()
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'price_%d' % self.ticket.id: 'abcde'
}, follow=True)
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
target_status_code=200)
doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertIn('not a number', doc.select('#error-message')[0].text)
with scopes_disabled():
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 0)
def test_free_price_only_if_allowed(self):
self.ticket.free_price = False
self.ticket.save()

View File

@@ -3138,6 +3138,9 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
assert cp1.addons.last().item == self.workshop1
def test_set_addon_free_price(self):
self.event.settings.locales = ['de']
self.event.settings.locale = 'de'
with scopes_disabled():
self.workshop1.free_price = True
self.workshop1.save()