Exemplo n.º 1
0
    def create_fixtures(self):
        self.user_email = self.user_email_template.format(random_string(8))
        self.user = User(self.user_email, 'test_user')
        self.db.session.add(self.user)

        self.group_name = self.group_template.format(random_string(8))
        self.group = ProductGroup(type='test',
                                  name=self.group_name,
                                  capacity_max=10)
        self.product1 = Product(name=self.product1_name,
                                parent=self.group,
                                capacity_max=3)
        self.tier1_1 = PriceTier(name=self.tier1_1_name, parent=self.product1)
        self.price1_1 = Price(price_tier=self.tier1_1,
                              currency='GBP',
                              price_int=10)
        self.db.session.add(self.tier1_1)

        self.tier1_2 = PriceTier(name=self.tier1_2_name, parent=self.product1)
        self.price1_2 = Price(price_tier=self.tier1_2,
                              currency='GBP',
                              price_int=20)
        self.db.session.add(self.tier1_2)

        self.product2 = Product(name=self.product2_name, parent=self.group)
        self.tier3 = PriceTier(name=self.tier3_name, parent=self.product2)
        self.price3 = Price(price_tier=self.tier3,
                            currency='GBP',
                            price_int=30)
        self.db.session.add(self.tier3)

        self.db.session.commit()
Exemplo n.º 2
0
def test_capacity_propagation(db, parent_group, user):
    product1 = Product(name="product", parent=parent_group, capacity_max=3)
    tier1_1 = PriceTier(name="tier1", parent=product1)
    Price(price_tier=tier1_1, currency="GBP", price_int=10)
    db.session.add(tier1_1)

    tier1_2 = PriceTier(name="tier2", parent=product1)
    Price(price_tier=tier1_2, currency="GBP", price_int=20)
    db.session.add(tier1_2)

    product2 = Product(name="product2", parent=parent_group)
    tier3 = PriceTier(name="tier3", parent=product2)
    Price(price_tier=tier3, currency="GBP", price_int=30)
    db.session.commit()

    # Check all our items have the correct initial capacity
    assert parent_group.get_total_remaining_capacity() == 10

    assert product1.get_total_remaining_capacity() == 3
    assert tier1_1.get_total_remaining_capacity() == 3
    assert tier1_2.get_total_remaining_capacity() == 3

    assert product2.get_total_remaining_capacity() == 10
    assert tier3.get_total_remaining_capacity() == 10

    # Issue three instances to exhaust product1
    create_purchases(tier1_1, 3, user)
    db.session.commit()

    # Now we shouldn't be able to issue any more tickets from this product
    with pytest.raises(CapacityException):
        create_purchases(tier1_1, 1, user)

    with pytest.raises(CapacityException):
        create_purchases(tier1_2, 1, user)

    db.session.commit()
    # All the capacity went from product1
    assert tier1_1.get_total_remaining_capacity() == 0
    assert tier1_2.get_total_remaining_capacity() == 0
    assert product1.get_total_remaining_capacity() == 0

    # produtill has capacity but is limited by the parent
    assert parent_group.get_total_remaining_capacity() == 7
    assert product2.get_total_remaining_capacity() == 7
    assert tier3.get_total_remaining_capacity() == 7

    price1 = Price(price_tier=tier1_1, currency="GBP", price_int=5)
    price2 = Price(price_tier=tier1_2, currency="GBP", price_int=500)

    db.session.add(price1)
    db.session.add(price2)
    db.session.commit()

    assert price1 == product1.get_cheapest_price("GBP")
Exemplo n.º 3
0
def test_create_purchases(db, parent_group, user):
    product = Product(name="product", capacity_max=3, parent=parent_group)
    tier = PriceTier(name="tier", parent=product)
    price = Price(price_tier=tier, currency="GBP", price_int=666)
    db.session.add(price)
    db.session.commit()

    assert tier.capacity_used == 0

    purchases = create_purchases(tier, 1, user)
    purchase = purchases[0]

    assert tier.capacity_used == 1
    assert product.capacity_used == 1

    # NB: Decimal('6.66') != Decimal(6.66) == Decimal(float(6.66)) ~= 6.6600000000000001
    assert purchase.price.value == Decimal("6.66")

    # Test issuing multiple instances works
    new_purchases = create_purchases(tier, 2, user)
    assert len(new_purchases) == 2
    assert product.capacity_used == 3

    # Test issuing beyond capacity errors
    with pytest.raises(CapacityException):
        create_purchases(tier, 1, user)
Exemplo n.º 4
0
    def create_fixtures(self):
        self.user1_email = self.user1_email_template.format(random_string(8))
        self.user2_email = self.user2_email_template.format(random_string(8))
        self.user1 = User(self.user1_email, 'test_user1')
        self.user2 = User(self.user2_email, 'test_user2')
        self.db.session.add(self.user1)
        self.db.session.add(self.user2)

        self.group_name = self.group_template.format(random_string(8))
        self.group = ProductGroup(type='admissions', name=self.group_name)
        self.product = Product(name=self.product_name, parent=self.group)
        self.tier = PriceTier(name=self.tier_name, parent=self.product)
        self.price = Price(price_tier=self.tier, currency='GBP', price_int=666)

        self.db.session.add(self.price)
        self.db.session.commit()

        # PriceTier needs to have been committed before this
        basket = Basket(self.user1, 'GBP')
        basket[self.tier] = 1
        basket.create_purchases()
        basket.ensure_purchase_capacity()
        payment = basket.create_payment(BankPayment)
        assert len(payment.purchases) == 1
        self.db.session.commit()
Exemplo n.º 5
0
def test_check_in(db, parent_group, user):
    product = Product(name="product", capacity_max=3, parent=parent_group)
    tier = PriceTier(name="tier", parent=product)
    price = Price(price_tier=tier, currency="GBP", price_int=666)
    db.session.add(price)
    db.session.commit()

    purchases = create_purchases(tier, 1, user)
    purchase = purchases[0]

    with pytest.raises(PurchaseStateException):
        # Issuing tickets should fail if the purchase hasn't been paid for
        purchase.ticket_issued = True

    with pytest.raises(CheckinStateException):
        # Likewise, checking in should fail.
        purchase.check_in()

    purchase.state = "paid"
    db.session.commit()

    purchase.ticket_issued = True
    assert purchase.checked_in is False
    purchase.check_in()
    assert purchase.checked_in is True
def test_product_group_get_counts_by_state(db, parent_group, user):
    product = Product(name='product', capacity_max=3, parent=parent_group)
    tier = PriceTier(name='tier', parent=product)
    price = Price(price_tier=tier, currency='GBP', price_int=666)
    db.session.add(price)
    db.session.commit()

    # Test it works at the PriceTier level
    purchases = create_purchases(tier, 1, user)
    purchase1 = purchases[0]

    expected = {
        'reserved': 1,
    }

    assert tier.purchase_count_by_state == expected
    assert product.purchase_count_by_state == expected
    assert parent_group.purchase_count_by_state == expected

    # Test that other states show up
    purchase1.set_state('payment-pending')
    db.session.commit()

    expected = {
        'payment-pending': 1,
    }

    assert tier.purchase_count_by_state == expected
    assert product.purchase_count_by_state == expected
    assert parent_group.purchase_count_by_state == expected

    # Add another purchase in another tier
    tier2 = PriceTier(name='2', parent=product)
    price = Price(price_tier=tier2, currency='GBP', price_int=666)
    db.session.commit()
    create_purchases(tier2, 1, user)

    assert tier.purchase_count_by_state == expected

    expected = {
        'payment-pending': 1,
        'reserved': 1,
    }

    assert product.purchase_count_by_state == expected
    assert parent_group.purchase_count_by_state == expected
Exemplo n.º 7
0
def product_group_copy(group_id):
    group = ProductGroup.query.get_or_404(group_id)
    form = CopyProductGroupForm()

    if group.children:
        # No recursive copying
        abort(404)

    if request.method != "POST":
        form.name.data = group.name + " (copy)"

    if group.capacity_max is None:
        del form.capacity_max_required

    else:
        del form.capacity_max

    if form.validate_on_submit():
        capacity_max = None
        if group.capacity_max is not None:
            capacity_max = form.capacity_max_required.data
            group.capacity_max -= capacity_max

        new_group = ProductGroup(
            type=group.type,
            name=form.name.data,
            capacity_max=capacity_max,
            expires=form.expires.data,
        )
        for product in group.products:
            new_product = Product(
                name=product.name,
                display_name=product.display_name,
                description=product.description,
            )
            new_group.products.append(new_product)
            for pt in product.price_tiers:
                if not pt.active and not form.include_inactive.data:
                    continue

                new_pt = PriceTier(name=pt.name,
                                   personal_limit=pt.personal_limit,
                                   active=pt.active)
                new_product.price_tiers.append(new_pt)
                for price in pt.prices:
                    new_price = Price(price.currency, price.value)
                    new_pt.prices.append(new_price)

        new_group.parent = group.parent
        db.session.add(new_group)
        db.session.commit()
        flash("ProductGroup copied")
        return redirect(
            url_for(".product_group_details", group_id=new_group.id))

    return render_template("admin/products/product-group-copy.html",
                           group=group,
                           form=form)
Exemplo n.º 8
0
def test_transfer(db, user, parent_group):
    user1 = user
    user2 = User("test_user_{}@test.invalid".format(random_string(8)),
                 "test_user2")
    db.session.add(user2)

    product = Product(name="product", parent=parent_group)
    tier = PriceTier(name="tier", parent=product)
    price = Price(price_tier=tier, currency="GBP", price_int=666)
    db.session.add(price)
    db.session.commit()

    create_purchases(tier, 1, user1)

    item = user1.purchases[0]

    item.price_tier.allow_check_in = True
    item.price_tier.is_transferable = False

    with pytest.raises(PurchaseTransferException) as e:
        item.transfer(user1, user2)
        assert "Only paid items may be transferred." in e.args[0]

    item.state = "paid"
    db.session.commit()

    with pytest.raises(PurchaseTransferException) as e:
        item.transfer(user1, user2)
        assert "not transferable" in e.args[0]

    with pytest.raises(PurchaseTransferException) as e:
        item.transfer(user2, user1)
        assert "does not own this item" in e.args[0]

    db.session.commit()
    item.price_tier.parent.set_attribute("is_transferable", True)
    db.session.commit()

    with pytest.raises(PurchaseTransferException) as e:
        item.transfer(user1, user1)
        assert "users must be different" in e.args[0]

    item.transfer(user1, user2)
    db.session.commit()

    assert item.owner_id == user2.id
    assert item.purchaser_id == user1.id

    assert item == user2.owned_purchases[0]
    assert item not in user1.owned_purchases

    xfer = item.transfers[0]

    assert xfer.to_user.id == user2.id
    assert xfer.from_user.id == user1.id
def test_check_in(db, parent_group, user):
    product = Product(name='product', capacity_max=3, parent=parent_group)
    tier = PriceTier(name='tier', parent=product)
    price = Price(price_tier=tier, currency='GBP', price_int=666)
    db.session.add(price)
    db.session.commit()

    purchases = create_purchases(tier, 1, user)
    purchase = purchases[0]

    purchase.state = 'receipt-emailed'
    assert purchase.checked_in is False
    purchase.check_in()
    assert purchase.checked_in is True
Exemplo n.º 10
0
    def create_fixtures(self):
        self.user_email = self.user_email_template.format(random_string(8))
        self.user = User(self.user_email, 'test_user')
        self.db.session.add(self.user)

        self.group_name = self.group_template.format(random_string(8))
        self.group = ProductGroup(type='admissions', name=self.group_name)
        self.product = Product(name=self.product_name,
                               capacity_max=3,
                               parent=self.group)
        self.tier = PriceTier(name=self.tier_name, parent=self.product)
        self.price = Price(price_tier=self.tier, currency='GBP', price_int=666)
        # These have `cascade=all` so just add the bottom of the hierarchy
        self.db.session.add(self.price)

        self.db.session.commit()
Exemplo n.º 11
0
def new_price_tier(product_id):
    form = PriceTierForm()
    product = Product.query.get_or_404(product_id)

    if form.validate_on_submit():
        pt = PriceTier(form.name.data)
        pt.prices = [Price('GBP', form.price_gbp.data),
                     Price('EUR', form.price_eur.data)]

        # Only activate this price tier if it's the first one added.
        pt.active = (len(product.price_tiers) == 0)
        product.price_tiers.append(pt)
        db.session.commit()
        return redirect(url_for('.price_tier_details', tier_id=pt.id))

    return render_template('admin/products/price-tier-new.html', product=product, form=form)
Exemplo n.º 12
0
def test_set_state(db, parent_group, user):
    product = Product(name="product", capacity_max=3, parent=parent_group)
    tier = PriceTier(name="tier", parent=product)
    price = Price(price_tier=tier, currency="GBP", price_int=666)
    db.session.add(price)
    db.session.commit()

    purchases = create_purchases(tier, 1, user)
    purchase = purchases[0]

    with pytest.raises(PurchaseStateException):
        purchase.set_state("disallowed-state")

    purchase.set_state("payment-pending")

    assert purchase.state == "payment-pending", purchase.state
Exemplo n.º 13
0
    def test_product_group_get_counts_by_state(self):
        with self.app.app_context():
            self.create_fixtures()

            # Test it works at the PriceTier level
            purchases = self.create_purchases(self.tier, 1)
            purchase1 = purchases[0]

            expected = {
                'reserved': 1,
            }

            assert self.tier.purchase_count_by_state == expected
            assert self.product.purchase_count_by_state == expected
            assert self.group.purchase_count_by_state == expected

            # Test that other states show up
            purchase1.set_state('payment-pending')
            self.db.session.commit()

            expected = {
                'payment-pending': 1,
            }

            assert self.tier.purchase_count_by_state == expected
            assert self.product.purchase_count_by_state == expected
            assert self.group.purchase_count_by_state == expected

            # Add another purchase in another tier
            self.tier2 = PriceTier(name='2', parent=self.product)
            self.price = Price(price_tier=self.tier2,
                               currency='GBP',
                               price_int=666)
            self.db.session.commit()
            self.create_purchases(self.tier2, 1)

            assert self.tier.purchase_count_by_state == expected

            expected = {
                'payment-pending': 1,
                'reserved': 1,
            }

            assert self.product.purchase_count_by_state == expected
            assert self.group.purchase_count_by_state == expected
def test_set_state(db, parent_group, user):
    product = Product(name='product', capacity_max=3, parent=parent_group)
    tier = PriceTier(name='tier', parent=product)
    price = Price(price_tier=tier, currency='GBP', price_int=666)
    db.session.add(price)
    db.session.commit()

    purchases = create_purchases(tier, 1, user)
    purchase = purchases[0]

    with pytest.raises(PurchaseStateException):
        purchase.set_state('disallowed-state')

    with pytest.raises(PurchaseStateException):
        purchase.set_state('receipt-emailed')

    purchase.set_state('payment-pending')

    assert purchase.state == 'payment-pending', purchase.state
Exemplo n.º 15
0
def new_price_tier(product_id):
    form = NewPriceTierForm()
    product = Product.query.get_or_404(product_id)

    if form.validate_on_submit():
        pt = PriceTier(form.name.data, personal_limit=form.personal_limit.data)
        pt.prices = [
            Price("GBP", form.price_gbp.data),
            Price("EUR", form.price_eur.data),
        ]

        # Only activate this price tier if it's the first one added.
        pt.active = len(product.price_tiers) == 0
        product.price_tiers.append(pt)
        db.session.commit()
        return redirect(url_for(".price_tier_details", tier_id=pt.id))

    return render_template("admin/products/price-tier-new.html",
                           product=product,
                           form=form)
Exemplo n.º 16
0
def create_product_groups():
    top_level_groups = [
        # name, capacity, expires
        ('admissions', datetime(2018, 9,
                                3), app.config.get('MAXIMUM_ADMISSIONS')),
        ('parking', datetime(2018, 9, 3), None),
        ('campervan', datetime(2018, 9, 3), None),
        ('merchandise', datetime(2018, 8, 12), None),
    ]
    for name, expires, capacity in top_level_groups:
        if ProductGroup.get_by_name(name):
            continue
        pg = ProductGroup(name=name,
                          type=name,
                          capacity_max=capacity,
                          expires=expires)
        db.session.add(pg)

    db.session.flush()

    allocations = [
        # name, capacity
        ('vendors', 100),
        ('sponsors', 200),
        ('speakers', 100),
        ('general', 800),
    ]

    admissions = ProductGroup.get_by_name('admissions')
    for name, capacity in allocations:
        if ProductGroup.get_by_name(name):
            continue
        ProductGroup(name=name, capacity_max=capacity, parent=admissions)

    view = ProductView.get_by_name('main')
    if not view:
        view = ProductView(name='main', type='tickets')
        db.session.add(view)

    db.session.flush()

    general = ProductGroup.get_by_name('general')

    products = [
        # name, display name, transferable, badge, capacity, description, (std cap, gbp eur), (early cap, gbp, eur), (late cap, gbp, eur)
        ('full', 'Full Camp Ticket', True, True, None, 'Full ticket',
         ((1500, 115, 135), (250, 105, 125), (None, 125, 145))),
        ('full-s', 'Full Camp Ticket (Supporter)', True, True, None,
         'Support this non-profit event by paying a bit more. All money will go towards making EMF more awesome.',
         ((None, 150, 180), )),
        ('full-sg', 'Full Camp Ticket (Gold Supporter)', True, True, None,
         'Support this non-profit event by paying a bit more. All money will go towards making EMF more awesome.',
         ((None, 200, 240), )),
        ('u18', 'Under-18', True, False, 150,
         'For visitors born after August 30th, 2000. All under-18s must be accompanied by an adult.',
         ((None, 55, 63), )),
        ('u12', 'Under-12', True, False, 50,
         'For children born after August 30th, 2006. All children must be accompanied by an adult.',
         ((None, 0, 0), )),
    ]

    order = 0

    for name, display_name, has_xfer, has_badge, capacity, description, prices in products:
        if Product.get_by_name('general', name):
            continue
        product = Product(name=name,
                          display_name=display_name,
                          capacity_max=capacity,
                          description=description,
                          parent=general,
                          attributes={
                              'is_transferable': has_xfer,
                              'has_badge': has_badge
                          })

        for index, (price_cap, gbp, eur) in enumerate(prices):
            if len(prices) == 1 or index == 0:
                tier_name = name + '-std'
                active = True

            elif index == 1:
                tier_name = name + '-early-bird'
                active = False

            elif index == 2:
                tier_name = name + '-late'
                active = False

            if PriceTier.get_by_name('general', 'name', tier_name):
                continue

            pt = PriceTier(name=tier_name,
                           capacity_max=price_cap,
                           personal_limit=10,
                           parent=product,
                           active=active)
            Price(currency='GBP', price_int=gbp * 100, price_tier=pt)
            Price(currency='EUR', price_int=eur * 100, price_tier=pt)

        ProductViewProduct(view, product, order)
        order += 1

    db.session.flush()

    misc = [
        # name, display_name, cap, personal_limit, gbp, eur, description
        ('parking', 'Parking Ticket', 1700, 4, 15, 21,
         "We're trying to keep cars to a minimum. Please take public transport or car-share if you can."
         ),
        ('campervan', 'Caravan/\u200cCampervan Ticket', 60, 2, 30, 42,
         "If you bring a caravan, you won't need a separate parking ticket for the towing car."
         ),
    ]

    for name, display_name, cap, personal_limit, gbp, eur, description in misc:
        if Product.get_by_name(name, name):
            continue

        group = ProductGroup.get_by_name(name)
        product = Product(name=name,
                          display_name=display_name,
                          description=description,
                          parent=group)
        pt = PriceTier(name=name,
                       personal_limit=personal_limit,
                       parent=product)
        db.session.add(pt)
        db.session.add(
            Price(currency='GBP', price_int=gbp * 100, price_tier=pt))
        db.session.add(
            Price(currency='EUR', price_int=eur * 100, price_tier=pt))

        ProductViewProduct(view, product, order)
        order += 1

    db.session.commit()
Exemplo n.º 17
0
def create_product_groups():
    top_level_groups = [
        # name, capacity, expires
        ("admissions", None, 2500),
        ("parking", None, None),
        ("campervan", None, None),
        ("merchandise", None, None),
    ]
    for name, expires, capacity in top_level_groups:
        if ProductGroup.get_by_name(name):
            continue
        pg = ProductGroup(name=name,
                          type=name,
                          capacity_max=capacity,
                          expires=expires)
        db.session.add(pg)

    db.session.flush()

    allocations = [
        # name, capacity
        ("vendors", 100),
        ("sponsors", 200),
        ("speakers", 100),
        ("general", 1800),
    ]

    admissions = ProductGroup.get_by_name("admissions")
    for name, capacity in allocations:
        if ProductGroup.get_by_name(name):
            continue
        ProductGroup(name=name, capacity_max=capacity, parent=admissions)

    view = ProductView.get_by_name("main")
    if not view:
        view = ProductView(name="main", type="tickets")
        db.session.add(view)

    db.session.flush()

    general = ProductGroup.get_by_name("general")

    products = [
        # name, display name, transferable, badge, capacity, description, (std cap, gbp eur), (early cap, gbp, eur), (late cap, gbp, eur)
        (
            "full",
            "Full Camp Ticket",
            True,
            True,
            None,
            "Full ticket",
            ((1500, 115, 135), (250, 105, 125), (None, 125, 145)),
        ),
        (
            "full-s",
            "Full Camp Ticket (Supporter)",
            True,
            True,
            None,
            "Support this non-profit event by paying a bit more. All money will go towards making EMF more awesome.",
            ((None, 150, 180), ),
        ),
        (
            "full-sg",
            "Full Camp Ticket (Gold Supporter)",
            True,
            True,
            None,
            "Support this non-profit event by paying a bit more. All money will go towards making EMF more awesome.",
            ((None, 200, 240), ),
        ),
        (
            "u18",
            "Under-18",
            True,
            False,
            150,
            "For visitors born after August 30th, 2000. All under-18s must be accompanied by an adult.",
            ((None, 55, 63), ),
        ),
        (
            "u12",
            "Under-12",
            True,
            False,
            50,
            "For children born after August 30th, 2006. All children must be accompanied by an adult.",
            ((None, 0, 0), ),
        ),
    ]

    order = 0

    for (
            name,
            display_name,
            has_xfer,
            has_badge,
            capacity,
            description,
            prices,
    ) in products:
        if Product.get_by_name("general", name):
            continue
        product = Product(
            name=name,
            display_name=display_name,
            capacity_max=capacity,
            description=description,
            parent=general,
            attributes={
                "is_transferable": has_xfer,
                "has_badge": has_badge
            },
        )

        for index, (price_cap, gbp, eur) in enumerate(prices):
            if len(prices) == 1 or index == 0:
                tier_name = name + "-std"
                active = True

            elif index == 1:
                tier_name = name + "-early-bird"
                active = False

            elif index == 2:
                tier_name = name + "-late"
                active = False

            if PriceTier.get_by_name("general", "name", tier_name):
                continue

            pt = PriceTier(
                name=tier_name,
                capacity_max=price_cap,
                personal_limit=10,
                parent=product,
                active=active,
            )
            Price(currency="GBP", price_int=gbp * 100, price_tier=pt)
            Price(currency="EUR", price_int=eur * 100, price_tier=pt)

        ProductViewProduct(view, product, order)
        order += 1

    db.session.flush()

    misc = [
        # name, display_name, cap, personal_limit, gbp, eur, description
        (
            "parking",
            "Parking Ticket",
            1700,
            4,
            15,
            21,
            "We're trying to keep cars to a minimum. Please take public transport or car-share if you can.",
        ),
        (
            "campervan",
            "Caravan/\u200cCampervan Ticket",
            60,
            2,
            30,
            42,
            "If you bring a caravan, you won't need a separate parking ticket for the towing car.",
        ),
    ]

    for name, display_name, cap, personal_limit, gbp, eur, description in misc:
        if Product.get_by_name(name, name):
            continue

        group = ProductGroup.get_by_name(name)
        product = Product(name=name,
                          display_name=display_name,
                          description=description,
                          parent=group)
        pt = PriceTier(name=name,
                       personal_limit=personal_limit,
                       parent=product)
        db.session.add(pt)
        db.session.add(
            Price(currency="GBP", price_int=gbp * 100, price_tier=pt))
        db.session.add(
            Price(currency="EUR", price_int=eur * 100, price_tier=pt))

        ProductViewProduct(view, product, order)
        order += 1

    db.session.commit()