models.py 7.3 KB

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