models.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from datetime import timedelta
  4. from django.core.urlresolvers import reverse
  5. from django.conf import settings
  6. from django.db import models
  7. from django.utils import timezone
  8. from .fields import CommaSeparatedCharField
  9. from .utils import add_one_year, ANGLES, merge_intervals
  10. class ContribQuerySet(models.query.QuerySet):
  11. def expired(self):
  12. return self.filter(expiration_date__lte=timezone.now())
  13. def expired_in_days(self, ndays):
  14. return self.filter(
  15. expiration_date__lte=timezone.now() + timedelta(days=ndays))
  16. def expires_in_days(self, ndays):
  17. """ Returns only the data expiring in exactly that number of days
  18. Think about it as an anniversary. This function ignores
  19. minutes/seconds/hours and check only days.
  20. """
  21. return self.filter(
  22. expiration_date__date=timezone.now() + timedelta(days=ndays))
  23. class Contrib(models.Model):
  24. CONTRIB_CONNECT = 'connect'
  25. CONTRIB_SHARE = 'share'
  26. CONTRIB_HELP_TECH = 'help_tech'
  27. id = models.AutoField(primary_key=True, blank=False, null=False)
  28. name = models.CharField(
  29. 'Nom / Pseudo',
  30. max_length=30)
  31. contrib_type = models.CharField(
  32. 'Type de contribution',
  33. max_length=10, choices=(
  34. (CONTRIB_CONNECT, 'Me raccorder à internet'),
  35. (CONTRIB_SHARE, 'Partager une partie de ma connexion'),
  36. (CONTRIB_HELP_TECH, 'Apporter mon aide technique')
  37. ), default=None)
  38. latitude = models.FloatField()
  39. longitude = models.FloatField()
  40. phone = models.CharField(
  41. 'Téléphone',
  42. max_length=30, blank=True, default='')
  43. email = models.EmailField(blank=True)
  44. access_type = models.CharField(
  45. 'Type de connexion',
  46. max_length=10, blank=True, choices=(
  47. ('vdsl', 'ADSL'),
  48. ('vdsl', 'VDSL'),
  49. ('fiber', 'Fibre optique'),
  50. ('cable', 'Coaxial (FTTLA)'),
  51. ))
  52. floor = models.PositiveIntegerField(
  53. 'étage',
  54. blank=True, null=True)
  55. floor_total = models.PositiveIntegerField(
  56. "mombre d'étages",
  57. blank=True, null=True)
  58. orientations = CommaSeparatedCharField(
  59. blank=True, null=True, max_length=100)
  60. roof = models.BooleanField(
  61. 'accès au toît',
  62. default=False)
  63. comment = models.TextField(
  64. 'commentaire',
  65. blank=True, null=True)
  66. privacy_name = models.BooleanField(
  67. 'nom/pseudo public',
  68. default=False)
  69. privacy_email = models.BooleanField(
  70. 'email public',
  71. default=False)
  72. privacy_coordinates = models.BooleanField(
  73. 'coordonnées GPS publiques',
  74. default=True)
  75. privacy_place_details = models.BooleanField(
  76. 'étage/orientations publiques',
  77. default=True)
  78. privacy_comment = models.BooleanField(
  79. 'commentaire public',
  80. default=False)
  81. date = models.DateTimeField(
  82. "date d'enregistrement",
  83. auto_now_add=True)
  84. expiration_date = models.DateTimeField(
  85. "date d'expiration",
  86. null=True, blank=True)
  87. STATUS_TOSTUDY = 'TOSTUDY'
  88. STATUS_TOCONNECT = 'TOCONNECT'
  89. STATUS_CONNECTED = 'CONNECTED'
  90. STATUS_WONTCONNECT = 'WONTCONNECT'
  91. CONNECTABILITY = (
  92. (STATUS_TOSTUDY, 'à étudier'),
  93. (STATUS_TOCONNECT, 'à connecter'),
  94. (STATUS_CONNECTED, 'connecté'),
  95. (STATUS_WONTCONNECT, 'pas connectable'),
  96. )
  97. status = models.CharField(
  98. blank=True,
  99. null=True,
  100. max_length=250,
  101. choices=CONNECTABILITY,
  102. default=STATUS_TOSTUDY)
  103. class Meta:
  104. managed = True
  105. db_table = 'contribs'
  106. verbose_name = 'contribution'
  107. PRIVACY_MAP = {
  108. 'name': 'privacy_name',
  109. 'comment': 'privacy_comment',
  110. 'floor': 'privacy_place_details',
  111. 'floor_total': 'privacy_place_details',
  112. 'orientations': 'privacy_place_details',
  113. 'roof': 'privacy_place_details',
  114. 'angles': 'privacy_place_details',
  115. }
  116. PUBLIC_FIELDS = set(PRIVACY_MAP.keys())
  117. objects = ContribQuerySet.as_manager()
  118. def __str__(self):
  119. return '#{} {}'.format(self.pk, self.name)
  120. @property
  121. def angles(self):
  122. """Return a list of (start, stop) angles from cardinal orientations
  123. """
  124. # Cleanup
  125. if self.orientations is None:
  126. return []
  127. orientations = [o for o in self.orientations if o in ANGLES.keys()]
  128. # Hack to make leaflet-semicircle happy (drawing a full circle only
  129. # works with (0, 360))
  130. if len(orientations) == 8:
  131. return [[0, 360]]
  132. angles = [ANGLES[orientation] for orientation in orientations]
  133. angles.sort(key=lambda i: i[0]) # sort by x
  134. return merge_intervals(angles)
  135. def get_postponed_expiration_date(self, from_date):
  136. """ Computes the new expiration date
  137. :param from_date: reference datetime frow where we add our extra delay.
  138. """
  139. return add_one_year(from_date)
  140. def clean(self):
  141. # usefull only for data imported from bottle version
  142. if not self.date:
  143. self.date = timezone.now()
  144. if not self.expiration_date:
  145. self.expiration_date = self.get_postponed_expiration_date(
  146. self.date)
  147. def save(self, *args, **kwargs):
  148. if not self.pk: # New instance
  149. self.date = timezone.now()
  150. self.expiration_date = self.get_postponed_expiration_date(
  151. self.date)
  152. super().save(*args, **kwargs)
  153. def is_public(self):
  154. return self.privacy_coordinates
  155. def is_expired(self):
  156. return self.expiration_date <= timezone.now()
  157. def _may_be_public(self, field):
  158. return field in self.PUBLIC_FIELDS
  159. def _is_public(self, field):
  160. return getattr(self, self.PRIVACY_MAP[field])
  161. def get_public_field(self, field):
  162. """ Gets safely an attribute in its public form (if any)
  163. :param field: The field name
  164. :return: the field value, or None, if the field is private
  165. """
  166. if self._may_be_public(field) and self._is_public(field):
  167. v = getattr(self, field)
  168. if hasattr(v, '__call__'):
  169. return v()
  170. else:
  171. return v
  172. else:
  173. return None
  174. def get_absolute_url(self, request=None, base_url=None):
  175. """ Get absolute url
  176. You can mention either `request` or `base_url` to get a full URL
  177. (starting with "http://" or "https://")
  178. :type request: request
  179. :param request: if mentioned, will be used to provide a full URL
  180. :param base_url: if mentioned, will be used to provide a full URL
  181. """
  182. url = '{}#{}'.format(
  183. reverse('display_map'), self.pk)
  184. if request:
  185. return request.build_absolute_uri(url)
  186. elif base_url:
  187. return '{}{}'.format(base_url, url)
  188. else:
  189. return url
  190. def make_management_url(self, token):
  191. return '{}{}?token={}'.format(
  192. settings.SITE_URL.strip('/'),
  193. reverse('manage_contrib', kwargs={'pk': self.pk}),
  194. token)