| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 | import zlib
from django.db import models
from django.utils.translation import ugettext_lazy as _
class AbstractAddress(models.Model):
    """
    Superclass address object
    This is subclassed and extended to provide models for
    user, shipping and billing addresses.
    The only required fields are last_name, line1 and postcode.
    """
    # @todo: Need a way of making these choice lists configurable
    # per project
    MR, MISS, MRS, MS, DR = ('Mr', 'Miss', 'Mrs', 'Ms', 'Dr')
    TITLE_CHOICES = (
        (MR, _("Mr")),
        (MISS, _("Miss")),
        (MRS, _("Mrs")),
        (MS, _("Ms")),
        (DR, _("Dr")),
    )
    title = models.CharField(_("Title"), max_length=64, choices=TITLE_CHOICES,
                             blank=True, null=True)
    first_name = models.CharField(_("First name"), max_length=255, blank=True,
                                  null=True)
    last_name = models.CharField(_("Last name"), max_length=255, blank=True)
    # We use quite a few lines of an address as they are often quite long and
    # it's easier to just hide the unnecessary ones than add extra ones.
    line1 = models.CharField(_("First line of address"), max_length=255)
    line2 = models.CharField(_("Second line of address"), max_length=255,
                             blank=True, null=True)
    line3 = models.CharField(_("Third line of address"), max_length=255,
                             blank=True, null=True)
    line4 = models.CharField(_("City"), max_length=255, blank=True, null=True)
    state = models.CharField(_("State/County"), max_length=255, blank=True,
                             null=True)
    postcode = models.CharField(_("Post/Zip-code"), max_length=64)
    country = models.ForeignKey('address.Country', verbose_name=_("Country"))
    # A field only used for searching addresses - this contains all the
    # relevant fields
    search_text = models.CharField(_("Search text"), max_length=1000)
    class Meta:
        abstract = True
        verbose_name = _('Address')
        verbose_name_plural = _('Addresses')
    def save(self, *args, **kwargs):
        self._clean_fields()
        self._update_search_text()
        super(AbstractAddress, self).save(*args, **kwargs)
    @property
    def city(self):
        return self.line4
    def _clean_fields(self):
        """
        Clean up fields
        """
        for field in ['first_name', 'last_name', 'line1', 'line2', 'line3',
                      'line4', 'state', 'postcode']:
            if self.__dict__[field]:
                self.__dict__[field] = self.__dict__[field].strip()
        # Ensure postcodes are always uppercase
        if self.postcode:
            self.postcode = self.postcode.upper()
    def _update_search_text(self):
        search_fields = filter(
            lambda x: x, [self.first_name, self.last_name,
                          self.line1, self.line2, self.line3, self.line4,
                          self.state, self.postcode, self.country.name])
        self.search_text = ' '.join(search_fields)
    @property
    def summary(self):
        """
        Returns a single string summary of the address,
        separating fields using commas.
        """
        return u", ".join(self.active_address_fields())
    def populate_alternative_model(self, address_model):
        """
        For populating an address model using the matching fields
        from this one.
        This is used to convert a user address to a shipping address
        as part of the checkout process.
        """
        destination_field_names = [
            field.name for field in address_model._meta.fields]
        for field_name in [field.name for field in self._meta.fields]:
            if field_name in destination_field_names and field_name != 'id':
                setattr(address_model, field_name, getattr(self, field_name))
    def active_address_fields(self):
        """
        Returns the non-empty components of the address, but merging the
        title, first_name and last_name into a single line.
        """
        self._clean_fields()
        fields = filter(
            lambda x: x, [self.salutation(), self.line1, self.line2,
                          self.line3, self.line4, self.state, self.postcode])
        if self.country:
            fields.append(self.country.name)
        return fields
    def salutation(self):
        """
        Return the salutation
        """
        return u" ".join([part for part in [
            self.title, self.first_name, self.last_name] if part])
    def name(self):
        """
        Return the full name
        """
        return u" ".join([part for part in [
            self.first_name, self.last_name] if part])
    def __unicode__(self):
        return self.summary
class AbstractCountry(models.Model):
    """
    International Organization for Standardization (ISO) 3166-1 Country list.
    """
    iso_3166_1_a2 = models.CharField(_('ISO 3166-1 alpha-2'), max_length=2,
                                     primary_key=True)
    iso_3166_1_a3 = models.CharField(_('ISO 3166-1 alpha-3'), max_length=3,
                                     null=True, db_index=True)
    iso_3166_1_numeric = models.PositiveSmallIntegerField(
        _('ISO 3166-1 numeric'), null=True, db_index=True)
    name = models.CharField(_('Official name (CAPS)'), max_length=128)
    printable_name = models.CharField(_('Country name'), max_length=128)
    is_highlighted = models.BooleanField(_("Is Highlighted"), default=False,
                                         db_index=True)
    is_shipping_country = models.BooleanField(_("Is Shipping Country"),
                                              default=False, db_index=True)
    class Meta:
        abstract = True
        verbose_name = _('Country')
        verbose_name_plural = _('Countries')
        ordering = ('-is_highlighted', 'name',)
    def __unicode__(self):
        return self.printable_name
class AbstractShippingAddress(AbstractAddress):
    """
    Shipping address.
    A shipping address should not be edited once the order has been placed -
    it should be read-only after that.
    """
    phone_number = models.CharField(_("Phone number"), max_length=32,
                                    blank=True, null=True)
    notes = models.TextField(
        blank=True, null=True,
        verbose_name=_('Courier instructions'),
        help_text=_("For example, leave the parcel in the wheelie bin " \
                    "if I'm not in."))
    class Meta:
        abstract = True
        verbose_name = _("Shipping address")
        verbose_name_plural = _("Shipping addresses")
    @property
    def order(self):
        """
        Return the order linked to this shipping address
        """
        orders = self.order_set.all()
        if not orders:
            return None
        return orders[0]
class AbstractUserAddress(AbstractShippingAddress):
    """
    A user address which forms an "AddressBook" for a user.
    We use a separate model to shipping and billing (even though there will be
    some data duplication) because we don't want shipping/billing addresses
    changed or deleted once an order has been placed.  By having a separate
    model, we allow users the ability to add/edit/delete from their address
    book without affecting orders already placed.
    """
    user = models.ForeignKey('auth.User', related_name='addresses',
                             verbose_name=_("User"))
    # Customers can set defaults
    is_default_for_shipping = models.BooleanField(
        _("Default shipping address?"), default=False)
    is_default_for_billing = models.BooleanField(
        _("Default billing address?"), default=False)
    # We keep track of the number of times an address has been used
    # as a shipping address so we can show the most popular ones
    # first at the checkout.
    num_orders = models.PositiveIntegerField(_("Number of Orders"), default=0)
    # A hash is kept to try and avoid duplicate addresses being added
    # to the address book.
    hash = models.CharField(_("Address Hash"), max_length=255, db_index=True)
    date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
    def generate_hash(self):
        """
        Returns a hash of the address summary
        """
        # We use an upper-case version of the summary
        return zlib.crc32(self.summary.strip().upper().encode('UTF8'))
    def save(self, *args, **kwargs):
        """
        Save a hash of the address fields
        """
        # Save a hash of the address fields so we can check whether two
        # addresses are the same to avoid saving duplicates
        self.hash = self.generate_hash()
        # Ensure that each user only has one default shipping address
        # and billing address
        self._ensure_defaults_integrity()
        super(AbstractUserAddress, self).save(*args, **kwargs)
    def _ensure_defaults_integrity(self):
        if self.is_default_for_shipping:
            self.__class__._default_manager.filter(
                user=self.user,
                is_default_for_shipping=True).update(
                    is_default_for_shipping=False)
        if self.is_default_for_billing:
            self.__class__._default_manager.filter(
                user=self.user,
                is_default_for_billing=True).update(
                    is_default_for_billing=False)
    class Meta:
        abstract = True
        verbose_name = _("User address")
        verbose_name_plural = _("User addresses")
        ordering = ['-num_orders']
class AbstractBillingAddress(AbstractAddress):
    class Meta:
        abstract = True
        verbose_name_plural = _("Billing address")
        verbose_name_plural = _("Billing addresses")
    @property
    def order(self):
        """
        Return the order linked to this shipping address
        """
        orders = self.order_set.all()
        if not orders:
            return None
        return orders[0]
 |