models.py 7.0 KB

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