您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

abstract_models.py 9.6KB

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