You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

abstract_models.py 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import zlib
  2. from django.db import models
  3. from django.utils.translation import ugettext_lazy as _
  4. class AbstractAddress(models.Model):
  5. """
  6. Superclass address object
  7. This is subclassed and extended to provide models for
  8. user, shipping and billing addresses.
  9. The only required fields are last_name, line1 and postcode.
  10. """
  11. # @todo: Need a way of making these choice lists configurable
  12. # per project
  13. MR, MISS, MRS, MS, DR = ('Mr', 'Miss', 'Mrs', 'Ms', 'Dr')
  14. TITLE_CHOICES = (
  15. (MR, _("Mr")),
  16. (MISS, _("Miss")),
  17. (MRS, _("Mrs")),
  18. (MS, _("Ms")),
  19. (DR, _("Dr")),
  20. )
  21. title = models.CharField(_("Title"), max_length=64, choices=TITLE_CHOICES, blank=True)
  22. first_name = models.CharField(_("First name"), max_length=255, blank=True)
  23. last_name = models.CharField(_("Last name"), max_length=255)
  24. # We use quite a few lines of an address as they are often quite long and
  25. # it's easier to just hide the unnecessary ones than add extra ones.
  26. line1 = models.CharField(_("First line of address"), max_length=255)
  27. line2 = models.CharField(_("Second line of address"), max_length=255, blank=True)
  28. line3 = models.CharField(_("Third line of address"), max_length=255, blank=True)
  29. line4 = models.CharField(_("City"), max_length=255, blank=True)
  30. state = models.CharField(_("State/County"), max_length=255, blank=True)
  31. postcode = models.CharField(_("Post/Zip-code"), max_length=64)
  32. country = models.ForeignKey('address.Country')
  33. # A field only used for searching addresses - this contains all the relevant fields
  34. search_text = models.CharField(_("Search text"), max_length=1000)
  35. class Meta:
  36. abstract = True
  37. def save(self, *args, **kwargs):
  38. self._clean_fields()
  39. self._update_search_text()
  40. super(AbstractAddress, self).save(*args, **kwargs)
  41. def _clean_fields(self):
  42. """
  43. Clean up fields
  44. """
  45. self.first_name = self.first_name.strip()
  46. for field in ['first_name', 'last_name', 'line1', 'line2', 'line3', 'line4', 'postcode']:
  47. self.__dict__[field] = self.__dict__[field].strip()
  48. # Ensure postcodes are always uppercase
  49. if self.postcode:
  50. self.postcode = self.postcode.upper()
  51. def _update_search_text(self):
  52. search_fields = filter(lambda x: x, [self.first_name, self.last_name,
  53. self.line1, self.line2, self.line3, self.line4, self.state,
  54. self.postcode, self.country.name])
  55. self.search_text = ' '.join(search_fields)
  56. @property
  57. def summary(self):
  58. """
  59. Returns a single string summary of the address,
  60. separating fields using commas.
  61. """
  62. return u", ".join(self.active_address_fields())
  63. def populate_alternative_model(self, address_model):
  64. """
  65. For populating an address model using the matching fields
  66. from this one.
  67. This is used to convert a user address to a shipping address
  68. as part of the checkout process.
  69. """
  70. destination_field_names = list(address_model._meta.get_all_field_names())
  71. for field_name in self._meta.get_all_field_names():
  72. if field_name in destination_field_names and field_name != 'id':
  73. setattr(address_model, field_name, getattr(self, field_name))
  74. def active_address_fields(self):
  75. u"""
  76. Returns the non-empty components of the address, but merging the
  77. title, first_name and last_name into a single line.
  78. """
  79. self._clean_fields()
  80. return filter(lambda x: x, [self.salutation(), self.line1, self.line2, self.line3,
  81. self.line4, self.postcode, self.country.name])
  82. def salutation(self):
  83. u"""Returns the salutation"""
  84. return u" ".join([part for part in [self.title, self.first_name, self.last_name] if part])
  85. def name(self):
  86. """
  87. Returns the full name
  88. """
  89. return u" ".join([part for part in [self.first_name, self.last_name] if part])
  90. def __unicode__(self):
  91. return self.summary
  92. class AbstractCountry(models.Model):
  93. """
  94. International Organization for Standardization (ISO) 3166-1 Country list.
  95. """
  96. iso_3166_1_a2 = models.CharField(_('ISO 3166-1 alpha-2'), max_length=2, primary_key=True)
  97. iso_3166_1_a3 = models.CharField(_('ISO 3166-1 alpha-3'), max_length=3, null=True, db_index=True)
  98. iso_3166_1_numeric = models.PositiveSmallIntegerField(_('ISO 3166-1 numeric'), null=True, db_index=True)
  99. name = models.CharField(_('Official name (CAPS)'), max_length=128)
  100. printable_name = models.CharField(_('Country name'), max_length=128)
  101. is_highlighted = models.BooleanField(default=False, db_index=True)
  102. is_shipping_country = models.BooleanField(default=False, db_index=True)
  103. class Meta:
  104. abstract = True
  105. verbose_name = _('Country')
  106. verbose_name_plural = _('Countries')
  107. ordering = ('-is_highlighted', 'name',)
  108. def __unicode__(self):
  109. return self.printable_name
  110. class AbstractShippingAddress(AbstractAddress):
  111. u"""
  112. Shipping address.
  113. A shipping address should not be edited once the order has been placed -
  114. it should be read-only after that.
  115. """
  116. phone_number = models.CharField(max_length=32, blank=True, null=True)
  117. notes = models.TextField(blank=True, null=True, help_text="""Shipping notes""")
  118. class Meta:
  119. abstract = True
  120. verbose_name_plural = "shipping addresses"
  121. class AbstractUserAddress(AbstractShippingAddress):
  122. """
  123. A user address which forms an "AddressBook" for a user.
  124. We use a separate model to shipping and billing (even though there will be
  125. some data duplication) because we don't want shipping/billing addresses changed
  126. or deleted once an order has been placed. By having a separate model, we allow
  127. users the ability to add/edit/delete from their address book without affecting
  128. orders already placed.
  129. """
  130. user = models.ForeignKey('auth.User', related_name='addresses')
  131. # Customers can set defaults
  132. is_default_for_shipping = models.BooleanField(default=False)
  133. is_default_for_billing = models.BooleanField(default=False)
  134. # We keep track of the number of times an address has been used
  135. # as a shipping address so we can show the most popular ones
  136. # first at the checkout.
  137. num_orders = models.PositiveIntegerField(default=0)
  138. # A hash is kept to try and avoid duplicate addresses being added
  139. # to the address book.
  140. hash = models.CharField(max_length=255, db_index=True)
  141. date_created = models.DateTimeField(auto_now_add=True)
  142. def generate_hash(self):
  143. u"""Returns a hash of the address summary."""
  144. # We use an upper-case version of the summary
  145. return zlib.crc32(self.summary.strip().upper().encode('UTF8'))
  146. def save(self, *args, **kwargs):
  147. u"""Save a hash of the address fields"""
  148. # Save a hash of the address fields so we can check whether two
  149. # addresses are the same to avoid saving duplicates
  150. self.hash = self.generate_hash()
  151. super(AbstractUserAddress, self).save(*args, **kwargs)
  152. class Meta:
  153. abstract = True
  154. verbose_name_plural = "User addresses"
  155. ordering = ['-num_orders']
  156. class AbstractBillingAddress(AbstractAddress):
  157. class Meta:
  158. abstract = True
  159. verbose_name_plural = "Billing addresses"