Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

abstract_models.py 9.8KB

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