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 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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, null=True)
  22. first_name = models.CharField(_("First name"), max_length=255, blank=True, null=True)
  23. last_name = models.CharField(_("Last name"), max_length=255, blank=True)
  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, null=True)
  28. line3 = models.CharField(_("Third line of address"), max_length=255, blank=True, null=True)
  29. line4 = models.CharField(_("City"), max_length=255, blank=True, null=True)
  30. state = models.CharField(_("State/County"), max_length=255, blank=True, null=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. @property
  42. def city(self):
  43. return self.line4
  44. def _clean_fields(self):
  45. """
  46. Clean up fields
  47. """
  48. for field in ['first_name', 'last_name', 'line1', 'line2', 'line3', 'line4', 'state', 'postcode']:
  49. if self.__dict__[field]:
  50. self.__dict__[field] = self.__dict__[field].strip()
  51. # Ensure postcodes are always uppercase
  52. if self.postcode:
  53. self.postcode = self.postcode.upper()
  54. def _update_search_text(self):
  55. search_fields = filter(lambda x: x, [self.first_name, self.last_name,
  56. self.line1, self.line2, self.line3, self.line4, self.state,
  57. self.postcode, self.country.name])
  58. self.search_text = ' '.join(search_fields)
  59. @property
  60. def summary(self):
  61. """
  62. Returns a single string summary of the address,
  63. separating fields using commas.
  64. """
  65. return u", ".join(self.active_address_fields())
  66. def populate_alternative_model(self, address_model):
  67. """
  68. For populating an address model using the matching fields
  69. from this one.
  70. This is used to convert a user address to a shipping address
  71. as part of the checkout process.
  72. """
  73. destination_field_names = [field.name for field in address_model._meta.fields]
  74. for field_name in [field.name for field in self._meta.fields]:
  75. if field_name in destination_field_names and field_name != 'id':
  76. setattr(address_model, field_name, getattr(self, field_name))
  77. def active_address_fields(self):
  78. u"""
  79. Returns the non-empty components of the address, but merging the
  80. title, first_name and last_name into a single line.
  81. """
  82. self._clean_fields()
  83. fields = filter(lambda x: x, [self.salutation(), self.line1, self.line2, self.line3,
  84. self.line4, self.state, self.postcode])
  85. if self.country:
  86. fields.append(self.country.name)
  87. return fields
  88. def salutation(self):
  89. u"""Returns the salutation"""
  90. return u" ".join([part for part in [self.title, self.first_name, self.last_name] if part])
  91. def name(self):
  92. """
  93. Returns the full name
  94. """
  95. return u" ".join([part for part in [self.first_name, self.last_name] if part])
  96. def __unicode__(self):
  97. return self.summary
  98. class AbstractCountry(models.Model):
  99. """
  100. International Organization for Standardization (ISO) 3166-1 Country list.
  101. """
  102. iso_3166_1_a2 = models.CharField(_('ISO 3166-1 alpha-2'), max_length=2, primary_key=True)
  103. iso_3166_1_a3 = models.CharField(_('ISO 3166-1 alpha-3'), max_length=3, null=True, db_index=True)
  104. iso_3166_1_numeric = models.PositiveSmallIntegerField(_('ISO 3166-1 numeric'), null=True, db_index=True)
  105. name = models.CharField(_('Official name (CAPS)'), max_length=128)
  106. printable_name = models.CharField(_('Country name'), max_length=128)
  107. is_highlighted = models.BooleanField(default=False, db_index=True)
  108. is_shipping_country = models.BooleanField(default=False, db_index=True)
  109. class Meta:
  110. abstract = True
  111. verbose_name = _('Country')
  112. verbose_name_plural = _('Countries')
  113. ordering = ('-is_highlighted', 'name',)
  114. def __unicode__(self):
  115. return self.printable_name
  116. class AbstractShippingAddress(AbstractAddress):
  117. u"""
  118. Shipping address.
  119. A shipping address should not be edited once the order has been placed -
  120. it should be read-only after that.
  121. """
  122. phone_number = models.CharField(max_length=32, blank=True, null=True)
  123. notes = models.TextField(blank=True, null=True, help_text="""Shipping notes""")
  124. class Meta:
  125. abstract = True
  126. verbose_name_plural = "shipping addresses"
  127. @property
  128. def order(self):
  129. """
  130. Return the order linked to this shipping address
  131. """
  132. orders = self.order_set.all()
  133. if not orders:
  134. return None
  135. return orders[0]
  136. class AbstractUserAddress(AbstractShippingAddress):
  137. """
  138. A user address which forms an "AddressBook" for a user.
  139. We use a separate model to shipping and billing (even though there will be
  140. some data duplication) because we don't want shipping/billing addresses changed
  141. or deleted once an order has been placed. By having a separate model, we allow
  142. users the ability to add/edit/delete from their address book without affecting
  143. orders already placed.
  144. """
  145. user = models.ForeignKey('auth.User', related_name='addresses')
  146. # Customers can set defaults
  147. is_default_for_shipping = models.BooleanField(default=False)
  148. is_default_for_billing = models.BooleanField(default=False)
  149. # We keep track of the number of times an address has been used
  150. # as a shipping address so we can show the most popular ones
  151. # first at the checkout.
  152. num_orders = models.PositiveIntegerField(default=0)
  153. # A hash is kept to try and avoid duplicate addresses being added
  154. # to the address book.
  155. hash = models.CharField(max_length=255, db_index=True)
  156. date_created = models.DateTimeField(auto_now_add=True)
  157. def generate_hash(self):
  158. u"""Returns a hash of the address summary."""
  159. # We use an upper-case version of the summary
  160. return zlib.crc32(self.summary.strip().upper().encode('UTF8'))
  161. def save(self, *args, **kwargs):
  162. u"""Save a hash of the address fields"""
  163. # Save a hash of the address fields so we can check whether two
  164. # addresses are the same to avoid saving duplicates
  165. self.hash = self.generate_hash()
  166. super(AbstractUserAddress, self).save(*args, **kwargs)
  167. class Meta:
  168. abstract = True
  169. verbose_name_plural = "User addresses"
  170. ordering = ['-num_orders']
  171. class AbstractBillingAddress(AbstractAddress):
  172. class Meta:
  173. abstract = True
  174. verbose_name_plural = "Billing addresses"
  175. @property
  176. def order(self):
  177. """
  178. Return the order linked to this shipping address
  179. """
  180. orders = self.order_set.all()
  181. if not orders:
  182. return None
  183. return orders[0]