models.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.db import models
  4. from django.core.validators import MaxValueValidator
  5. from django.core.exceptions import ValidationError
  6. from localflavor.generic.models import IBANField, BICField
  7. from localflavor.fr.models import FRSIRETField
  8. from multiselectfield import MultiSelectField
  9. from coin.members.models import count_active_members
  10. from coin.offers.models import count_active_subscriptions
  11. from coin import utils
  12. from coin.validation import chatroom_url_validator
  13. # API version, see http://db.ffdn.org/format
  14. API_VERSION = 0.1
  15. TECHNOLOGIES = (('ftth', 'FTTH'),
  16. ('dsl', '*DSL'),
  17. ('wifi', 'WiFi'),
  18. ('vpn', 'VPN'),
  19. ('cube', 'Brique Internet'))
  20. class SingleInstanceMixin(object):
  21. """Makes sure that no more than one instance of a given model is created."""
  22. def clean(self):
  23. model = self.__class__
  24. if (model.objects.count() > 0 and self.id != model.objects.get().id):
  25. raise ValidationError("Can only create 1 instance of %s" % model.__name__)
  26. super(SingleInstanceMixin, self).clean()
  27. class ISPInfo(SingleInstanceMixin, models.Model):
  28. """http://db.ffdn.org/format
  29. The naming convention is different from Python/django so that it
  30. matches exactly the format (which uses CamelCase...)
  31. """
  32. # These two properties can be overriden with static counters, see below.
  33. @property
  34. def memberCount(self):
  35. """Number of members"""
  36. return count_active_members()
  37. @property
  38. def subscriberCount(self):
  39. """Number of subscribers to an internet access"""
  40. return count_active_subscriptions()
  41. name = models.CharField(max_length=512,
  42. verbose_name="Nom",
  43. help_text="Nom du FAI")
  44. # Length required by the spec
  45. shortname = models.CharField(max_length=15, blank=True,
  46. verbose_name="Abréviation",
  47. help_text="Nom plus court")
  48. description = models.TextField(blank=True,
  49. verbose_name="Description",
  50. help_text="Description courte du projet")
  51. logoURL = models.URLField(blank=True,
  52. verbose_name="URL du logo",
  53. help_text="Adresse HTTP(S) du logo du FAI")
  54. website = models.URLField(blank=True,
  55. verbose_name="URL du site Internet",
  56. help_text='Adresse URL du site Internet')
  57. email = models.EmailField(verbose_name="Courriel",
  58. help_text="Adresse courriel de contact")
  59. mainMailingList = models.EmailField(blank=True,
  60. verbose_name="Liste de discussion principale",
  61. help_text="Principale liste de discussion publique")
  62. phone_number = models.CharField(max_length=25, blank=True,
  63. verbose_name="Numéro de téléphone",
  64. help_text='Numéro de téléphone de contact principal')
  65. creationDate = models.DateField(blank=True, null=True,
  66. verbose_name="Date de création",
  67. help_text="Date de création de la structure légale")
  68. ffdnMemberSince = models.DateField(blank=True, null=True,
  69. verbose_name="Membre de FFDN depuis",
  70. help_text="Date à laquelle le FAI a rejoint la Fédération FDN")
  71. # TODO: choice field
  72. progressStatus = models.PositiveSmallIntegerField(
  73. validators=[MaxValueValidator(7)],
  74. blank=True, null=True, verbose_name="État d'avancement",
  75. help_text="État d'avancement du FAI")
  76. # TODO: better model for coordinates
  77. latitude = models.FloatField(blank=True, null=True,
  78. verbose_name="Latitude",
  79. help_text="Coordonnées latitudinales du siège")
  80. longitude = models.FloatField(blank=True, null=True,
  81. verbose_name="Longitude",
  82. help_text="Coordonnées longitudinales du siège")
  83. # Uncomment this (and handle the necessary migrations) if you want to
  84. # manage one of the counters by hand. Otherwise, they are computed
  85. # automatically, which is probably what you want.
  86. #memberCount = models.PositiveIntegerField(help_text="Nombre de membres",
  87. # default=0)
  88. #subscriberCount = models.PositiveIntegerField(
  89. # help_text="Nombre d'abonnés à un accès Internet",
  90. # default=0)
  91. # field outside of db-ffdn format:
  92. administrative_email = models.EmailField(
  93. blank=True, verbose_name="contact administratif",
  94. help_text='Adresse email pour les contacts administratifs (ex: bureau)')
  95. support_email = models.EmailField(
  96. blank=True, verbose_name="contact de support",
  97. help_text="Adresse email pour les demandes de support technique")
  98. lists_url = models.URLField(
  99. verbose_name="serveur de listes", blank=True,
  100. help_text="URL du serveur de listes de discussions/diffusion")
  101. class Meta:
  102. verbose_name = "Information du FAI"
  103. verbose_name_plural = "Informations du FAI"
  104. @property
  105. def version(self):
  106. """Version de l'API"""
  107. return API_VERSION
  108. @property
  109. def main_chat_verbose(self):
  110. first_chatroom = self.chatroom_set.first()
  111. if first_chatroom:
  112. m = utils.re_chat_url.match(first_chatroom.url)
  113. if m:
  114. return '{channel} sur {server}'.format(**(m.groupdict()))
  115. return None
  116. def get_absolute_url(self):
  117. return '/isp.json'
  118. def to_dict(self):
  119. data = dict()
  120. # These are required
  121. for f in ('version', 'name', 'email', 'memberCount', 'subscriberCount'):
  122. data[f] = getattr(self, f)
  123. # These are optional
  124. for f in ('shortname', 'description', 'logoURL', 'website',
  125. 'mainMailingList', 'progressStatus'):
  126. if getattr(self, f):
  127. data[f] = getattr(self, f)
  128. # Dates
  129. for d in ('creationDate', 'ffdnMemberSince'):
  130. if getattr(self, d):
  131. data[d] = getattr(self, d).isoformat()
  132. # Hackish for now
  133. if self.latitude or self.longitude:
  134. data['coordinates'] = { "latitude": self.latitude,
  135. "longitude": self.longitude }
  136. # Related objects
  137. data['coveredAreas'] = [c.to_dict() for c in self.coveredarea_set.all()]
  138. otherwebsites = self.otherwebsite_set.all()
  139. if otherwebsites:
  140. data['otherWebsites'] = { site.name: site.url for site in otherwebsites }
  141. chatrooms = self.chatroom_set.all()
  142. if chatrooms:
  143. data['chatrooms'] = [chatroom.url for chatroom in chatrooms]
  144. if hasattr(self, 'registeredoffice'):
  145. data['registeredOffice'] = self.registeredoffice.to_dict()
  146. return data
  147. def __unicode__(self):
  148. return self.name
  149. class OtherWebsite(models.Model):
  150. name = models.CharField(max_length=512, verbose_name="Nom")
  151. url = models.URLField(verbose_name="URL")
  152. isp = models.ForeignKey(ISPInfo)
  153. class Meta:
  154. verbose_name = "Autre site Internet"
  155. verbose_name_plural = "Autres sites Internet"
  156. class RegisteredOffice(models.Model):
  157. """ http://json-schema.org/address """
  158. post_office_box = models.CharField(max_length=512, blank=True, verbose_name="Boîte postale")
  159. extended_address = models.CharField(max_length=512, blank=True, verbose_name="Adresse complémentaire")
  160. street_address = models.CharField(max_length=512, blank=True, verbose_name="Adresse")
  161. locality = models.CharField(max_length=512, verbose_name="Ville")
  162. region = models.CharField(max_length=512, verbose_name="Région")
  163. postal_code = models.CharField(max_length=512, blank=True, verbose_name="Code postal")
  164. country_name = models.CharField(max_length=512, verbose_name="Pays")
  165. isp = models.OneToOneField(ISPInfo)
  166. # not in db.ffdn.org spec
  167. siret = FRSIRETField('SIRET')
  168. class Meta:
  169. verbose_name = "Siège social"
  170. verbose_name_plural = "Sièges sociaux"
  171. def to_dict(self):
  172. d = dict()
  173. for field in ('post_office_box', 'extended_address', 'street_address',
  174. 'locality', 'region', 'postal_code', 'country_name'):
  175. if getattr(self, field):
  176. key = field.replace('_', '-')
  177. d[key] = getattr(self, field)
  178. return d
  179. class ChatRoom(models.Model):
  180. url = models.CharField(
  181. verbose_name="URL", max_length=256, validators=[chatroom_url_validator])
  182. isp = models.ForeignKey(ISPInfo)
  183. class Meta:
  184. verbose_name = "Salon de discussions"
  185. verbose_name_plural = "Salons de discussions"
  186. class CoveredArea(models.Model):
  187. name = models.CharField(max_length=512, verbose_name="Nom")
  188. technologies = MultiSelectField(choices=TECHNOLOGIES, max_length=42, verbose_name="Technologie")
  189. # TODO: find a geojson library
  190. #area =
  191. isp = models.ForeignKey(ISPInfo)
  192. def to_dict(self):
  193. return {"name": self.name,
  194. "technologies": self.technologies}
  195. class Meta:
  196. verbose_name = "Zone couverte"
  197. verbose_name_plural = "Zones couvertes"
  198. class BankInfo(models.Model):
  199. """Information about bank account and the bank itself
  200. This is out of the scope of db.ffdn.org spec.
  201. """
  202. isp = models.OneToOneField(ISPInfo)
  203. iban = IBANField('IBAN')
  204. bic = BICField('BIC', blank=True, null=True)
  205. bank_name = models.CharField('établissement bancaire',
  206. max_length=100, blank=True, null=True)
  207. check_order = models.CharField('ordre',
  208. max_length=100, blank=False, null=False,
  209. help_text='Ordre devant figurer sur un \
  210. chèque bancaire à destination de\
  211. l\'association')
  212. class Meta:
  213. verbose_name = 'coordonnées bancaires'
  214. verbose_name_plural = verbose_name