예제 #1
0
class Contact(NameMixin, PolymorphicModel):
    is_anonymous = False
    is_all_seeing = False
    default_tax_group_getter = None

    created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on'))
    identifier = InternalIdentifierField(unique=True, null=True, blank=True)
    is_active = models.BooleanField(default=True, db_index=True, verbose_name=_('active'))
    # TODO: parent contact?
    default_shipping_address = models.ForeignKey(
        "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('shipping address'),
        on_delete=models.PROTECT
    )
    default_billing_address = models.ForeignKey(
        "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('billing address'),
        on_delete=models.PROTECT
    )
    default_shipping_method = models.ForeignKey(
        "ShippingMethod", verbose_name=_('default shipping method'), blank=True, null=True, on_delete=models.SET_NULL
    )
    default_payment_method = models.ForeignKey(
        "PaymentMethod", verbose_name=_('default payment method'), blank=True, null=True, on_delete=models.SET_NULL
    )

    language = LanguageField(verbose_name=_('language'), blank=True)
    marketing_permission = models.BooleanField(default=True, verbose_name=_('marketing permission'))
    phone = models.CharField(max_length=64, blank=True, verbose_name=_('phone'))
    www = models.URLField(max_length=128, blank=True, verbose_name=_('web address'))
    timezone = TimeZoneField(blank=True, null=True, verbose_name=_('time zone'))
    prefix = models.CharField(verbose_name=_('name prefix'), max_length=64, blank=True)
    name = models.CharField(max_length=256, verbose_name=_('name'))
    suffix = models.CharField(verbose_name=_('name suffix'), max_length=64, blank=True)
    name_ext = models.CharField(max_length=256, blank=True, verbose_name=_('name extension'))
    email = models.EmailField(max_length=256, blank=True, verbose_name=_('email'))

    tax_group = models.ForeignKey(
        "CustomerTaxGroup", blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('tax group')
    )

    def __str__(self):
        return self.full_name

    class Meta:
        verbose_name = _('contact')
        verbose_name_plural = _('contacts')

    def __init__(self, *args, **kwargs):
        if self.default_tax_group_getter:
            kwargs.setdefault("tax_group", self.default_tax_group_getter())
        super(Contact, self).__init__(*args, **kwargs)
예제 #2
0
파일: orders.py 프로젝트: krisera/shoop
class Order(models.Model):
    # Identification
    shop = UnsavedForeignKey("Shop", on_delete=models.PROTECT)
    created_on = models.DateTimeField(auto_now_add=True, editable=False)
    identifier = InternalIdentifierField(unique=True,
                                         db_index=True,
                                         verbose_name=_('order identifier'))
    # TODO: label is actually a choice field, need to check migrations/choice deconstruction
    label = models.CharField(max_length=32,
                             db_index=True,
                             verbose_name=_('label'))
    # The key shouldn't be possible to deduce (i.e. it should be random), but it is
    # not a secret. (It could, however, be used as key material for an actual secret.)
    key = models.CharField(max_length=32,
                           unique=True,
                           blank=False,
                           verbose_name=_('key'))
    reference_number = models.CharField(max_length=64,
                                        db_index=True,
                                        unique=True,
                                        blank=True,
                                        null=True,
                                        verbose_name=_('reference number'))

    # Contact information
    customer = UnsavedForeignKey("Contact",
                                 related_name='customer_orders',
                                 blank=True,
                                 null=True,
                                 on_delete=models.PROTECT,
                                 verbose_name=_('customer'))
    orderer = UnsavedForeignKey("PersonContact",
                                related_name='orderer_orders',
                                blank=True,
                                null=True,
                                on_delete=models.PROTECT,
                                verbose_name=_('orderer'))
    billing_address = UnsavedForeignKey("Address",
                                        related_name="billing_orders",
                                        blank=True,
                                        null=True,
                                        on_delete=models.PROTECT,
                                        verbose_name=_('billing address'))
    shipping_address = UnsavedForeignKey("Address",
                                         related_name='shipping_orders',
                                         blank=True,
                                         null=True,
                                         on_delete=models.PROTECT,
                                         verbose_name=_('shipping address'))
    tax_number = models.CharField(max_length=20,
                                  blank=True,
                                  verbose_name=_('Tax number'))
    phone = models.CharField(max_length=32,
                             blank=True,
                             verbose_name=_('phone'))
    email = models.EmailField(max_length=128,
                              blank=True,
                              verbose_name=_('email address'))

    # Status
    creator = UnsavedForeignKey(settings.AUTH_USER_MODEL,
                                related_name='orders_created',
                                blank=True,
                                null=True,
                                on_delete=models.PROTECT,
                                verbose_name=_('creating user'))
    deleted = models.BooleanField(db_index=True, default=False)
    status = UnsavedForeignKey("OrderStatus",
                               verbose_name=_('status'),
                               on_delete=models.PROTECT)
    payment_status = EnumIntegerField(PaymentStatus,
                                      db_index=True,
                                      default=PaymentStatus.NOT_PAID,
                                      verbose_name=_('payment status'))
    shipping_status = EnumIntegerField(ShippingStatus,
                                       db_index=True,
                                       default=ShippingStatus.NOT_SHIPPED,
                                       verbose_name=_('shipping status'))

    # Methods
    payment_method = UnsavedForeignKey("PaymentMethod",
                                       related_name="payment_orders",
                                       blank=True,
                                       null=True,
                                       default=None,
                                       on_delete=models.PROTECT,
                                       verbose_name=_('payment method'))
    payment_method_name = models.CharField(
        max_length=64,
        blank=True,
        default="",
        verbose_name=_('payment method name'))
    payment_data = JSONField(blank=True, null=True)

    shipping_method = UnsavedForeignKey("ShippingMethod",
                                        related_name='shipping_orders',
                                        blank=True,
                                        null=True,
                                        default=None,
                                        on_delete=models.PROTECT,
                                        verbose_name=_('shipping method'))
    shipping_method_name = models.CharField(
        max_length=64,
        blank=True,
        default="",
        verbose_name=_('shipping method name'))
    shipping_data = JSONField(blank=True, null=True)

    extra_data = JSONField(blank=True, null=True)

    # Money stuff
    taxful_total_price = TaxfulPriceProperty('taxful_total_price_value',
                                             'currency')
    taxless_total_price = TaxlessPriceProperty('taxless_total_price_value',
                                               'currency')

    taxful_total_price_value = MoneyValueField(editable=False,
                                               verbose_name=_('grand total'),
                                               default=0)
    taxless_total_price_value = MoneyValueField(
        editable=False, verbose_name=_('taxless total'), default=0)
    currency = CurrencyField()
    prices_include_tax = models.BooleanField(
    )  # TODO: (TAX) Document Order.prices_include_tax

    display_currency = CurrencyField(blank=True)
    display_currency_rate = models.DecimalField(max_digits=36,
                                                decimal_places=9,
                                                default=1)

    # Other
    ip_address = models.GenericIPAddressField(null=True,
                                              blank=True,
                                              verbose_name=_('IP address'))
    # order_date is not `auto_now_add` for backdating purposes
    order_date = models.DateTimeField(editable=False,
                                      verbose_name=_('order date'))
    payment_date = models.DateTimeField(null=True,
                                        editable=False,
                                        verbose_name=_('payment date'))

    # TODO: (TAX) Add me? customer_tax_group = models.ForeignKey(CustomerTaxGroup, blank=True, null=True)
    language = LanguageField(blank=True, verbose_name=_('language'))
    customer_comment = models.TextField(blank=True,
                                        verbose_name=_('customer comment'))
    admin_comment = models.TextField(blank=True,
                                     verbose_name=_('admin comment/notes'))
    require_verification = models.BooleanField(
        default=False, verbose_name=_('requires verification'))
    all_verified = models.BooleanField(default=False,
                                       verbose_name=_('all lines verified'))
    marketing_permission = models.BooleanField(
        default=True, verbose_name=_('marketing permission'))

    common_select_related = ("billing_address", )
    objects = OrderQuerySet.as_manager()

    class Meta:
        ordering = ("-id", )
        verbose_name = _('order')
        verbose_name_plural = _('orders')

    def __str__(self):  # pragma: no cover
        if self.billing_address_id:
            name = self.billing_address.name
        else:
            name = "-"
        if settings.SHOOP_ENABLE_MULTIPLE_SHOPS:
            return "Order %s (%s, %s)" % (self.identifier, self.shop.name,
                                          name)
        else:
            return "Order %s (%s)" % (self.identifier, name)

    def cache_prices(self):
        taxful_total = TaxfulPrice(0, self.currency)
        taxless_total = TaxlessPrice(0, self.currency)
        for line in self.lines.all():
            taxful_total += line.taxful_total_price
            taxless_total += line.taxless_total_price
        self.taxful_total_price = _round_price(taxful_total)
        self.taxless_total_price = _round_price(taxless_total)

    def _cache_contact_values(self):
        sources = [
            self.shipping_address,
            self.billing_address,
            self.customer,
            self.orderer,
        ]

        fields = ("tax_number", "email", "phone")

        for field in fields:
            if getattr(self, field, None):
                continue
            for source in sources:
                val = getattr(source, field, None)
                if val:
                    setattr(self, field, val)
                    break

    def _cache_values(self):
        self._cache_contact_values()

        if not self.label:
            self.label = settings.SHOOP_DEFAULT_ORDER_LABEL

        if not self.currency:
            self.currency = self.shop.currency

        if not self.prices_include_tax:
            self.prices_include_tax = self.shop.prices_include_tax

        if not self.display_currency:
            self.display_currency = self.currency
            self.display_currency_rate = 1

        if self.shipping_method_id and not self.shipping_method_name:
            self.shipping_method_name = self.shipping_method.safe_translation_getter(
                "name",
                default=self.shipping_method.identifier,
                any_language=True)

        if self.payment_method_id and not self.payment_method_name:
            self.payment_method_name = self.payment_method.safe_translation_getter(
                "name",
                default=self.payment_method.identifier,
                any_language=True)

        if not self.key:
            self.key = get_random_string(32)

    def _save_identifiers(self):
        self.identifier = "%s" % (get_order_identifier(self))
        self.reference_number = get_reference_number(self)
        super(Order, self).save(update_fields=(
            "identifier",
            "reference_number",
        ))

    def full_clean(self, exclude=None, validate_unique=True):
        self._cache_values()
        return super(Order, self).full_clean(exclude, validate_unique)

    def create_immutable_address_copies(self):
        for field in ("billing_address", "shipping_address"):
            address = getattr(self, field, None)
            if address and not address.is_immutable:
                if address.pk:
                    address = address.copy()
                    address.set_immutable()
                else:
                    address.set_immutable()
                setattr(self, field, address)

    def save(self, *args, **kwargs):
        if not self.creator_id:
            if not settings.SHOOP_ALLOW_ANONYMOUS_ORDERS:
                raise ValidationError(
                    "Anonymous (userless) orders are not allowed "
                    "when SHOOP_ALLOW_ANONYMOUS_ORDERS is not enabled.")
        self._cache_values()
        first_save = (not self.pk)
        self.create_immutable_address_copies()
        super(Order, self).save(*args, **kwargs)
        if first_save:  # Have to do a double save the first time around to be able to save identifiers
            self._save_identifiers()

    def delete(self, using=None):
        if not self.deleted:
            self.deleted = True
            self.add_log_entry("Deleted.", kind=LogEntryKind.DELETION)
            # Bypassing local `save()` on purpose.
            super(Order, self).save(update_fields=("deleted", ), using=using)

    def set_canceled(self):
        if self.status.role != OrderStatusRole.CANCELED:
            self.status = OrderStatus.objects.get_default_canceled()
            self.save()

    def _set_paid(self):
        if self.payment_status != PaymentStatus.FULLY_PAID:  # pragma: no branch
            self.add_log_entry(_('Order marked as paid.'))
            self.payment_status = PaymentStatus.FULLY_PAID
            self.payment_date = now()
            self.save()

    def is_paid(self):
        return (self.payment_status == PaymentStatus.FULLY_PAID)

    def get_total_paid_amount(self):
        amounts = self.payments.values_list('amount_value', flat=True)
        return Money(sum(amounts, Decimal(0)), self.currency)

    def create_payment(self, amount, payment_identifier=None, description=''):
        """
        Create a payment with given amount for this order.

        If the order already has payments and sum of their amounts is
        equal or greater than self.taxful_total_price, an exception is raised.

        If the end sum of all payments is equal or greater than
        self.taxful_total_price, then the order is marked as paid.

        :param amount:
          Amount of the payment to be created
        :type amount: Money
        :param payment_identifier:
          Identifier of the created payment. If not set, default value
          of "gateway_id:order_id:number" will be used (where number is
          number of payments in the order).
        :type payment_identifier: str|None
        :param description:
          Description of the payment. Will be set to `method` property
          of the created payment.
        :type description: str

        :returns: The created Payment object
        :rtype: shoop.core.models.Payment
        """
        assert isinstance(amount, Money)
        assert amount.currency == self.currency

        payments = self.payments.order_by('created_on')

        total_paid_amount = self.get_total_paid_amount()
        if total_paid_amount >= self.taxful_total_price.amount:
            raise NoPaymentToCreateException(
                "Order %s has already been fully paid (%s >= %s)." %
                (self.pk, total_paid_amount, self.taxful_total_price))

        if not payment_identifier:
            number = payments.count() + 1
            payment_identifier = '%d:%d' % (self.id, number)

        payment = self.payments.create(
            payment_identifier=payment_identifier,
            amount_value=amount.value,
            description=description,
        )

        if self.get_total_paid_amount() >= self.taxful_total_price.amount:
            self._set_paid()  # also calls save

        return payment

    def create_shipment(self, supplier, product_quantities):
        """
        Create a shipment for this order from `product_quantities`.
        `product_quantities` is expected to be a dict mapping Product instances to quantities.

        Only quantities over 0 are taken into account, and if the mapping is empty or has no quantity value
        over 0, `NoProductsToShipException` will be raised.

        :param supplier: The Supplier for this product. No validation is made
                         as to whether the given supplier supplies the products.
        :param product_quantities: a dict mapping Product instances to quantities to ship
        :type product_quantities: dict[shoop.shop.models.products.Product, decimal.Decimal]
        :raises: NoProductsToShipException
        :return: Saved, complete Shipment object
        :rtype: shoop.core.models.shipments.Shipment
        """
        if not product_quantities or not any(
                quantity > 0 for quantity in product_quantities.values()):
            raise NoProductsToShipException(
                "No products to ship (`quantities` is empty or has no quantity over 0)."
            )

        from .shipments import Shipment, ShipmentProduct

        shipment = Shipment(order=self, supplier=supplier)
        shipment.save()

        for product, quantity in product_quantities.items():
            if quantity > 0:
                sp = ShipmentProduct(shipment=shipment,
                                     product=product,
                                     quantity=quantity)
                sp.cache_values()
                sp.save()

        shipment.cache_values()
        shipment.save()

        self.add_log_entry(_(u"Shipment #%d created.") % shipment.id)
        self.check_and_set_fully_shipped()
        return shipment

    def create_shipment_of_all_products(self, supplier=None):
        """
        Create a shipment of all the products in this Order, no matter whether or not any have been previously
        marked as shipped or not.

        See the documentation for `create_shipment`.

        :param supplier: The Supplier to use. If `None`, the first supplier in
                         the order is used. (If several are in the order, this fails.)
        :return: Saved, complete Shipment object
        :rtype: shoop.shop.models.shipments.Shipment
        """
        suppliers_to_product_quantities = defaultdict(
            lambda: defaultdict(lambda: 0))
        lines = (self.lines.filter(type=OrderLineType.PRODUCT).values_list(
            "supplier_id", "product_id", "quantity"))
        for supplier_id, product_id, quantity in lines:
            if product_id:
                suppliers_to_product_quantities[supplier_id][
                    product_id] += quantity

        if not suppliers_to_product_quantities:
            raise NoProductsToShipException(
                "Could not find any products to ship.")

        if supplier is None:
            if len(suppliers_to_product_quantities) > 1:  # pragma: no cover
                raise ValueError(
                    "Can only use create_shipment_of_all_products when there is only one supplier"
                )
            supplier_id, quantities = suppliers_to_product_quantities.popitem()
            supplier = Supplier.objects.get(pk=supplier_id)
        else:
            quantities = suppliers_to_product_quantities[supplier.id]

        products = dict(
            (product.pk, product)
            for product in Product.objects.filter(pk__in=quantities.keys()))
        quantities = dict((products[product_id], quantity)
                          for (product_id, quantity) in quantities.items())
        return self.create_shipment(supplier, quantities)

    def check_all_verified(self):
        if not self.all_verified:
            new_all_verified = (not self.lines.filter(verified=False).exists())
            if new_all_verified:
                self.all_verified = True
                if self.require_verification:
                    self.add_log_entry(
                        _('All rows requiring verification have been verified.'
                          ))
                    self.require_verification = False
                self.save()
        return self.all_verified

    def get_purchased_attachments(self):
        from .product_media import ProductMedia

        if self.payment_status != PaymentStatus.FULLY_PAID:
            return ProductMedia.objects.none()
        prods = self.lines.exclude(product=None).values_list("product_id",
                                                             flat=True)
        return ProductMedia.objects.filter(product__in=prods,
                                           enabled=True,
                                           purchased=True)

    def get_tax_summary(self):
        """
        :rtype: taxing.TaxSummary
        """
        all_line_taxes = []
        untaxed = TaxlessPrice(0, self.currency)
        for line in self.lines.all():
            line_taxes = list(line.taxes.all())
            all_line_taxes.extend(line_taxes)
            if not line_taxes:
                untaxed += line.taxless_total_price
        return taxing.TaxSummary.from_line_taxes(all_line_taxes, untaxed)

    def get_product_ids_and_quantities(self):
        quantities = defaultdict(lambda: 0)
        for product_id, quantity in self.lines.filter(
                type=OrderLineType.PRODUCT).values_list(
                    "product_id", "quantity"):
            quantities[product_id] += quantity
        return dict(quantities)

    def is_complete(self):
        return (self.status.role == OrderStatusRole.COMPLETE)

    def can_set_complete(self):
        fully_shipped = (self.shipping_status == ShippingStatus.FULLY_SHIPPED)
        canceled = (self.status.role == OrderStatusRole.CANCELED)
        return (not self.is_complete()) and fully_shipped and (not canceled)

    def check_and_set_fully_shipped(self):
        if self.shipping_status != ShippingStatus.FULLY_SHIPPED:
            if not self.get_unshipped_products():
                self.shipping_status = ShippingStatus.FULLY_SHIPPED
                self.add_log_entry(
                    _(u"All products have been shipped. Fully Shipped status set."
                      ))
                self.save(update_fields=("shipping_status", ))
                return True

    def get_known_additional_data(self):
        """
        Get a list of "known additional data" in this order's payment_data, shipping_data and extra_data.
        The list is returned in the order the fields are specified in the settings entries for said known keys.
        `dict(that_list)` can of course be used to "flatten" the list into a dict.
        :return: list of 2-tuples.
        """
        output = []
        for data_dict, name_mapping in (
            (self.payment_data, settings.SHOOP_ORDER_KNOWN_PAYMENT_DATA_KEYS),
            (self.shipping_data,
             settings.SHOOP_ORDER_KNOWN_SHIPPING_DATA_KEYS),
            (self.extra_data, settings.SHOOP_ORDER_KNOWN_EXTRA_DATA_KEYS),
        ):
            if hasattr(data_dict, "get"):
                for key, display_name in name_mapping:
                    if key in data_dict:
                        output.append(
                            (force_text(display_name), data_dict[key]))
        return output

    def get_product_summary(self):
        """Return a dict of product IDs -> {ordered, unshipped, shipped}"""

        products = defaultdict(lambda: defaultdict(lambda: Decimal(0)))
        lines = (self.lines.filter(type=OrderLineType.PRODUCT).values_list(
            "product_id", "quantity"))
        for product_id, quantity in lines:
            products[product_id]['ordered'] += quantity
            products[product_id]['unshipped'] += quantity

        from .shipments import ShipmentProduct

        shipment_prods = (ShipmentProduct.objects.filter(
            shipment__order=self).values_list("product_id", "quantity"))
        for product_id, quantity in shipment_prods:
            products[product_id]['shipped'] += quantity
            products[product_id]['unshipped'] -= quantity

        return products

    def get_unshipped_products(self):
        return dict(
            (product, summary_datum)
            for product, summary_datum in self.get_product_summary().items()
            if summary_datum['unshipped'])

    def get_status_display(self):
        return force_text(self.status)
예제 #3
0
class Contact(PolymorphicShoopModel):
    is_anonymous = False
    is_all_seeing = False
    default_tax_group_getter = None
    default_contact_group_identifier = None
    default_contact_group_name = None

    created_on = models.DateTimeField(auto_now_add=True,
                                      editable=False,
                                      verbose_name=_('created on'))
    identifier = InternalIdentifierField(unique=True, null=True, blank=True)
    is_active = models.BooleanField(default=True,
                                    db_index=True,
                                    verbose_name=_('active'))
    # TODO: parent contact?
    default_shipping_address = models.ForeignKey(
        "MutableAddress",
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_('shipping address'),
        on_delete=models.PROTECT)
    default_billing_address = models.ForeignKey(
        "MutableAddress",
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_('billing address'),
        on_delete=models.PROTECT)
    default_shipping_method = models.ForeignKey(
        "ShippingMethod",
        verbose_name=_('default shipping method'),
        blank=True,
        null=True,
        on_delete=models.SET_NULL)
    default_payment_method = models.ForeignKey(
        "PaymentMethod",
        verbose_name=_('default payment method'),
        blank=True,
        null=True,
        on_delete=models.SET_NULL)

    language = LanguageField(verbose_name=_('language'), blank=True)
    marketing_permission = models.BooleanField(
        default=True, verbose_name=_('marketing permission'))
    phone = models.CharField(max_length=64,
                             blank=True,
                             verbose_name=_('phone'))
    www = models.URLField(max_length=128,
                          blank=True,
                          verbose_name=_('web address'))
    timezone = TimeZoneField(blank=True,
                             null=True,
                             verbose_name=_('time zone'))
    prefix = models.CharField(verbose_name=_('name prefix'),
                              max_length=64,
                              blank=True)
    name = models.CharField(max_length=256, verbose_name=_('name'))
    suffix = models.CharField(verbose_name=_('name suffix'),
                              max_length=64,
                              blank=True)
    name_ext = models.CharField(max_length=256,
                                blank=True,
                                verbose_name=_('name extension'))
    email = models.EmailField(max_length=256,
                              blank=True,
                              verbose_name=_('email'))

    tax_group = models.ForeignKey("CustomerTaxGroup",
                                  blank=True,
                                  null=True,
                                  on_delete=models.PROTECT,
                                  verbose_name=_('tax group'))
    merchant_notes = models.TextField(blank=True,
                                      verbose_name=_('merchant notes'))

    def __str__(self):
        return self.full_name

    class Meta:
        verbose_name = _('contact')
        verbose_name_plural = _('contacts')

    def __init__(self, *args, **kwargs):
        if self.default_tax_group_getter:
            kwargs.setdefault("tax_group", self.default_tax_group_getter())
        super(Contact, self).__init__(*args, **kwargs)

    @property
    def full_name(self):
        return (" ".join([self.prefix, self.name, self.suffix])).strip()

    def get_price_display_options(self):
        """
        Get price display options of the contact.

        If the default group (`get_default_group`) defines price display
        options and the contact is member of it, return it.

        If contact is not (anymore) member of the default group or the
        default group does not define options, return one of the groups
        which defines options.  If there is more than one such groups,
        it is undefined which options will be used.

        If contact is not a member of any group that defines price
        display options, return default constructed
        `PriceDisplayOptions`.

        Subclasses may still override this default behavior.

        :rtype: PriceDisplayOptions
        """
        groups_with_options = self.groups.with_price_display_options()
        if groups_with_options:
            default_group = self.get_default_group()
            if groups_with_options.filter(pk=default_group.pk).exists():
                group_with_options = default_group
            else:
                # Contact was removed from the default group.
                group_with_options = groups_with_options.first()
            return group_with_options.get_price_display_options()
        return PriceDisplayOptions()

    def save(self, *args, **kwargs):
        add_to_default_group = bool(self.pk is None
                                    and self.default_contact_group_identifier)
        super(Contact, self).save(*args, **kwargs)
        if add_to_default_group:
            self.groups.add(self.get_default_group())

    @classmethod
    def get_default_group(cls):
        """
        Get or create default contact group for the class.

        Identifier of the group is specified by the class property
        `default_contact_group_identifier`.

        If new group is created, its name is set to value of
        `default_contact_group_name` class property.

        :rtype: core.models.ContactGroup
        """
        obj, created = ContactGroup.objects.get_or_create(
            identifier=cls.default_contact_group_identifier,
            defaults={"name": cls.default_contact_group_name})
        return obj
예제 #4
0
class Contact(NameMixin, PolymorphicModel):
    is_anonymous = False
    is_all_seeing = False
    default_tax_group_getter = None
    default_contact_group_identifier = None
    default_contact_group_name = None

    created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on'))
    identifier = InternalIdentifierField(unique=True, null=True, blank=True)
    is_active = models.BooleanField(default=True, db_index=True, verbose_name=_('active'))
    # TODO: parent contact?
    default_shipping_address = models.ForeignKey(
        "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('shipping address'),
        on_delete=models.PROTECT
    )
    default_billing_address = models.ForeignKey(
        "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('billing address'),
        on_delete=models.PROTECT
    )
    default_shipping_method = models.ForeignKey(
        "ShippingMethod", verbose_name=_('default shipping method'), blank=True, null=True, on_delete=models.SET_NULL
    )
    default_payment_method = models.ForeignKey(
        "PaymentMethod", verbose_name=_('default payment method'), blank=True, null=True, on_delete=models.SET_NULL
    )

    language = LanguageField(verbose_name=_('language'), blank=True)
    marketing_permission = models.BooleanField(default=True, verbose_name=_('marketing permission'))
    phone = models.CharField(max_length=64, blank=True, verbose_name=_('phone'))
    www = models.URLField(max_length=128, blank=True, verbose_name=_('web address'))
    timezone = TimeZoneField(blank=True, null=True, verbose_name=_('time zone'))
    prefix = models.CharField(verbose_name=_('name prefix'), max_length=64, blank=True)
    name = models.CharField(max_length=256, verbose_name=_('name'))
    suffix = models.CharField(verbose_name=_('name suffix'), max_length=64, blank=True)
    name_ext = models.CharField(max_length=256, blank=True, verbose_name=_('name extension'))
    email = models.EmailField(max_length=256, blank=True, verbose_name=_('email'))

    tax_group = models.ForeignKey(
        "CustomerTaxGroup", blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('tax group')
    )

    def __str__(self):
        return self.full_name

    class Meta:
        verbose_name = _('contact')
        verbose_name_plural = _('contacts')

    def __init__(self, *args, **kwargs):
        if self.default_tax_group_getter:
            kwargs.setdefault("tax_group", self.default_tax_group_getter())
        super(Contact, self).__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        add_to_default_group = bool(self.pk is None and self.default_contact_group_identifier)
        super(Contact, self).save(*args, **kwargs)
        if add_to_default_group:
            self.groups.add(self.get_default_group())

    @classmethod
    def get_default_group(cls):
        """
        Get or create default contact group for the class.

        Identifier of the group is specified by the class property
        `default_contact_group_identifier`.

        If new group is created, its name is set to value of
        `default_contact_group_name` class property.

        :rtype: core.models.ContactGroup
        """
        obj, created = ContactGroup.objects.get_or_create(
            identifier=cls.default_contact_group_identifier,
            defaults={
                "name": cls.default_contact_group_name
            }
        )
        return obj