Source

django-lfs / lfs / plugins.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# python imports
import math

# django imports
from django import forms
from django.db import models

# lfs imports
import lfs
from lfs.payment.settings import PM_ORDER_IMMEDIATELY
from lfs.payment.settings import PM_ORDER_ACCEPTED
from lfs.payment.settings import PM_MSG_TOP
from lfs.payment.settings import PM_MSG_FORM
from lfs.order.settings import PAID


class OrderNumberGenerator(models.Model):
    """
    Base class from which all order number generators should inherit.

    **Attributes:**

    id
        The unique id of the order number generator.
    """
    id = models.CharField(primary_key=True, max_length=20)

    class Meta:
        abstract = True

    def init(self, request, order):
        """
        Initializes the order number generator. This method is called
        automatically from LFS.
        """
        self.request = request
        self.order = order
        self.user = request.user
        self.customer = lfs.customer.utils.get_customer(request)
        self.cart = lfs.cart.utils.get_cart(request)

    def get_next(self, formatted=True):
        """
        Returns the next order number as string. Derived classes must implement
        this method.

        **Parameters:**

        formatted
            If True the number will be returned within the stored format, which
            is based on Python default string formatting operators, e.g.
            ``%04d``.
        """
        raise NotImplementedError

    def exclude_form_fields(self):
        """
        Returns a list of fields, which are excluded from the model form, see
        also ``get_form``.
        """
        return ("id", )

    def get_form(self, **kwargs):
        """
        Returns the form which is used within the shop preferences management
        interface.

        All parameters are passed to the form.
        """
        class OrderNumberGeneratorForm(forms.ModelForm):
            class Meta:
                model = self
                exclude = self.exclude_form_fields()

        return OrderNumberGeneratorForm(**kwargs)


class PaymentMethodProcessor(object):
    """
    Base class from which all 3rd-party payment method processors should inherit.

    **Attributes:**

    request
        The current request.

    cart
        The current cart. This is only set, when create order time is ACCEPTED.

    order
        The current order. This is only set, when create order time is
        IMMEDIATELY.
    """
    def __init__(self, request, cart=None, order=None):
        self.request = request
        self.cart = cart
        self.order = order

    def process(self):
        """
        Implements the processing of the payment method. Returns a dictionary
        with several status codes, see below.

        **Return Values:**

        This values are returned within a dictionary.

        accepted (mandatory)
            Indicates whether the payment is accepted or not. if this is
            ``False`` the customer keeps on the checkout page and gets
            ``message`` (if given) below. If this is ``True`` the customer will
            be redirected to next_url (if given).

        message (optional)
            This message is displayed on the checkout page, when the order is
            not accepted.

        message_location (optional)
            The location, where the message is displayed.

        next_url (optional)
            The url to which the user is redirect after the payment has been
            processed. if this is not given the customer is redirected to the
            default thank-you page.

        order_state (optional)
            The state in which the order should be set. It's just PAID. If it's
            not given the state keeps in SUBMITTED.
        """
        raise NotImplementedError

    def get_create_order_time(self):
        """
        Returns the time when the order should be created. It is one of:

        PM_ORDER_IMMEDIATELY
            The order is created immediately before the payment is processed.

        PM_ORDER_ACCEPTED
            The order is created when the payment has been processed and
            accepted.
        """
        raise NotImplementedError

    def get_pay_link(self):
        """
        Returns a link to the payment service to pay the current order, which
        is displayed on the thank-you page and the order confirmation mail. In
        this way the customer can pay the order again if something has gone
        wrong.
        """
        return None


class PriceCalculator(object):
    """
    This is the base class that pricing calculators must inherit from.
    """
    def __init__(self, request, product, **kwargs):
        self.request = request
        self.product = product

    def get_price(self, with_properties=True):
        """
        Returns the stored price of the product without any tax calculations.
        It takes variants, properties and sale prices into account, though.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the prices of the default properties are added to the price.
        """
        object = self.product

        if object.is_product_with_variants() and object.get_default_variant():
            object = object.get_default_variant()

        if object.get_for_sale():
            if object.is_variant() and not object.active_for_sale_price:
                price = object.parent.get_for_sale_price(self.request)
            else:
                price = object.get_for_sale_price(self.request)
        else:
            if object.is_variant() and not object.active_price:
                price = object.parent.price
            else:
                price = object.price

        if with_properties and object.is_configurable_product():
            price += object.get_default_properties_price()

        return price

    def get_price_net(self, with_properties=True):
        """
        Returns the net price of the product.
        """
        raise NotImplementedError

    def get_price_gross(self, with_properties=True):
        """
        Returns the real gross price of the product. This is the base of
        all price and tax calculations.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the prices of the default properties are added to the price.
        """
        raise NotImplementedError

    def get_standard_price(self, with_properties=True):
        """
        Returns always the stored standard price for the product. Independent
        whether the product is for sale or not. If you want the real price of
        the product use ``get_price`` instead.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the prices of the default properties are added to the price.
        """
        object = self.product

        if object.is_product_with_variants() and object.get_default_variant():
            object = object.get_default_variant()

        if object.is_variant() and not object.active_price:
            object = object.parent

        price = object.price
        if with_properties and object.is_configurable_product():
            price += object.get_default_properties_price()

        return price

    def get_standard_price_net(self, with_properties=True):
        """
        Returns always the standard net price for the product. Independent
        whether the product is for sale or not. If you want the real net price
        of the product use ``get_price_net`` instead.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the prices of the default properties are added to the price.
        """
        raise NotImplementedError

    def get_standard_price_gross(self, with_properties=True):
        """
        Returns always the gross standard price for the product. Independent
        whether the product is for sale or not. If you want the real gross
        price of the product use ``get_price_gross`` instead.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the prices of the default properties are added to the price.
        """
        raise NotImplementedError

    def get_for_sale_price(self, with_properties=True):
        """
        Returns the sale price for the product.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the prices of the default properties are added to the price.
        """
        object = self.product

        if object.is_product_with_variants() and object.get_default_variant():
            object = object.get_default_variant()

        if object.is_variant() and not object.active_for_sale_price:
            object = object.parent

        price = object.for_sale_price
        if with_properties and object.is_configurable_product():
            price += object.get_default_properties_price()

        return price

    def get_for_sale_price_net(self, with_properties=True):
        """
        Returns the sale net price for the product.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the prices of the default properties are added to the price.
        """
        raise NotImplementedError

    def get_for_sale_price_gross(self, with_properties=True):
        """
        Returns the sale net price for the product.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the prices of the default properties are added to the price.
        """
        raise NotImplementedError

    def get_base_price(self, with_properties=True):
        """
        Returns the base price of the product.
        """
        try:
            return self.get_price(with_properties) / self.product.get_base_price_amount()
        except (TypeError, ZeroDivisionError):
            return 0.0

    def get_base_price_net(self, with_properties=True):
        """
        Returns the net base price of the product.
        """
        try:
            return self.get_price_net(with_properties) / self.product.get_base_price_amount()
        except (TypeError, ZeroDivisionError):
            return 0.0

    def get_base_price_gross(self, with_properties=True):
        """
        Returns the gross base price of the product.
        """
        try:
            return self.get_price_gross(with_properties) / self.product.get_base_price_amount()
        except (TypeError, ZeroDivisionError):
            return 0.0

    def get_base_packing_price(self):
        """
        Returns the base packing price of the product.
        """
        return self.get_price(self.request) * self._calc_packing_amount()

    def get_base_packing_price_net(self):
        """
        Returns the base packing net price of the product.
        """
        return self.get_price_net(self.request) * self._calc_packing_amount()

    def get_base_packing_price_gross(self):
        """
        Returns the base packing gross price of the product.
        """
        return self.get_price_gross(self.request) * self._calc_packing_amount()

    def get_customer_tax_rate(self):
        """
        Returns the tax rate for the current customer and product.
        """
        from lfs.customer_tax.utils import get_customer_tax_rate
        return get_customer_tax_rate(self.request, self.product)

    def get_customer_tax(self, with_properties=True):
        """
        Returns the calculated tax for the current customer and product.

        **Parameters:**

        with_properties
            If the instance is a configurable product and with_properties is
            True the taxes of the default properties are added to the price.
        """
        return self.get_price_gross(with_properties) - self.get_price_net(with_properties)

    def get_product_tax_rate(self):
        """
        Returns the stored tax rate of the product. If the product is a variant
        it returns the parent's tax rate.
        """
        if self.product.is_variant():
            obj = self.product.parent
        else:
            obj = self.product

        try:
            return obj.tax.rate
        except AttributeError:
            return 0.0

    def get_product_tax(self, with_properties=True):
        """
        Returns the calculated tax for the current product independent of the
        customer.
        """
        return self.get_price(with_properties) - self.get_price(with_properties)

    def price_includes_tax(self):
        """
        Returns True if stored price includes tax. False if not.
        """
        raise NotImplementedError

    def _calc_product_tax_rate(self):
        """
        Returns the default tax rate for the product.
        """
        tax_rate = self.get_product_tax_rate()
        return ((tax_rate + 100.0) / 100.0)

    def _calc_customer_tax_rate(self):
        """
        Returns the tax rate for the current customer.
        """
        return (self.get_customer_tax_rate() + 100.0) / 100.0

    def _calc_packing_amount(self):
        packing_amount, packing_unit = self.product.get_packing_info()
        packs = math.ceil(1 / packing_amount)
        return packs * packing_amount


class ShippingMethodPriceCalculator(object):
    """
    Base class from which all 3rd-party shipping method prices should inherit.

    **Attributes:**

    request
        The current request.

    shipping_method
        The shipping method for which the price is calculated.
    """
    def __init__(self, request, shipping_method):
        self.shipping_method = shipping_method
        self.request = request

    def get_price(self):
        """
        Returns the stored price without any calculations.
        """
        return self.shipping_method.price

    def get_price_net(self):
        """
        Returns the net price of the shipping method.
        """
        raise NotImplementedError

    def get_price_gross(self):
        """
        Returns the gross price of the shipping method.
        """
        raise NotImplementedError

    def get_tax(self):
        """
        Returns the total tax of the shipping method.
        """
        return self.get_price_gross() - self.get_price_net()