models.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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 coin.members.models import count_active_members
  9. from coin.offers.models import count_active_subscriptions
  10. from coin import utils
  11. from coin.validation import chatroom_url_validator
  12. # API version, see http://db.ffdn.org/format
  13. API_VERSION = 0.1
  14. TECHNOLOGIES = (('ftth', 'FTTH'),
  15. ('dsl', '*DSL'),
  16. ('wifi', 'WiFi'),
  17. ('vpn', 'VPN'),
  18. ('cube', 'Brique Internet'))
  19. class SingleInstanceMixin(object):
  20. """Makes sure that no more than one instance of a given model is created."""
  21. def clean(self):
  22. model = self.__class__
  23. if (model.objects.count() > 0 and self.id != model.objects.get().id):
  24. raise ValidationError("Can only create 1 instance of %s" % model.__name__)
  25. super(SingleInstanceMixin, self).clean()
  26. class ISPInfo(SingleInstanceMixin, models.Model):
  27. """http://db.ffdn.org/format
  28. The naming convention is different from Python/django so that it
  29. matches exactly the format (which uses CamelCase...)
  30. """
  31. # These two properties can be overriden with static counters, see below.
  32. @property
  33. def memberCount(self):
  34. """Number of members"""
  35. return count_active_members()
  36. @property
  37. def subscriberCount(self):
  38. """Number of subscribers to an internet access"""
  39. return count_active_subscriptions()
  40. name = models.CharField(max_length=512,
  41. help_text="The ISP's name")
  42. # Length required by the spec
  43. shortname = models.CharField(max_length=15, blank=True,
  44. help_text="Shorter name")
  45. description = models.TextField(blank=True,
  46. help_text="Short text describing the project")
  47. logoURL = models.URLField(blank=True,
  48. verbose_name="logo URL",
  49. help_text="HTTP(S) URL of the ISP's logo")
  50. website = models.URLField(blank=True,
  51. help_text='URL to the official website')
  52. email = models.EmailField(help_text="Contact email address")
  53. mainMailingList = models.EmailField(blank=True,
  54. verbose_name="main mailing list",
  55. help_text="Main public mailing-list")
  56. phone_number = models.CharField(max_length=25, blank=True,
  57. verbose_name="phone number",
  58. help_text='Main contact phone number')
  59. creationDate = models.DateField(blank=True, null=True,
  60. verbose_name="creation date",
  61. help_text="Date of creation for legal structure")
  62. ffdnMemberSince = models.DateField(blank=True, null=True,
  63. verbose_name="FFDN member since",
  64. help_text="Date at wich the ISP joined the Federation")
  65. # TODO: choice field
  66. progressStatus = models.PositiveSmallIntegerField(
  67. validators=[MaxValueValidator(7)],
  68. blank=True, null=True, verbose_name='progress status',
  69. help_text="Progression status of the ISP")
  70. # TODO: better model for coordinates
  71. latitude = models.FloatField(blank=True, null=True,
  72. help_text="Coordinates of the registered office (latitude)")
  73. longitude = models.FloatField(blank=True, null=True,
  74. help_text="Coordinates of the registered office (longitude)")
  75. # Uncomment this (and handle the necessary migrations) if you want to
  76. # manage one of the counters by hand. Otherwise, they are computed
  77. # automatically, which is probably what you want.
  78. #memberCount = models.PositiveIntegerField(help_text="Number of members",
  79. # default=0)
  80. #subscriberCount = models.PositiveIntegerField(
  81. # help_text="Number of subscribers to an internet access",
  82. # default=0)
  83. # field outside of db-ffdn format:
  84. administrative_email = models.EmailField(
  85. blank=True, verbose_name="contact administratif",
  86. help_text='Adresse email pour les contacts administratifs (ex: bureau)')
  87. support_email = models.EmailField(
  88. blank=True, verbose_name="contact de support",
  89. help_text="Adresse email pour les demandes de support technique")
  90. lists_url = models.URLField(
  91. verbose_name="serveur de listes", blank=True,
  92. help_text="URL du serveur de listes de discussions/diffusion")
  93. @property
  94. def version(self):
  95. """Version of the API"""
  96. return API_VERSION
  97. @property
  98. def main_chat_verbose(self):
  99. first_chatroom = self.chatroom_set.first()
  100. if first_chatroom:
  101. m = utils.re_chat_url.match(first_chatroom.url)
  102. if m:
  103. return '{channel} sur {server}'.format(**(m.groupdict()))
  104. return None
  105. def get_absolute_url(self):
  106. return '/isp.json'
  107. def to_dict(self):
  108. data = dict()
  109. # These are required
  110. for f in ('version', 'name', 'email', 'memberCount', 'subscriberCount'):
  111. data[f] = getattr(self, f)
  112. # These are optional
  113. for f in ('shortname', 'description', 'logoURL', 'website',
  114. 'mainMailingList', 'progressStatus'):
  115. if getattr(self, f):
  116. data[f] = getattr(self, f)
  117. # Dates
  118. for d in ('creationDate', 'ffdnMemberSince'):
  119. if getattr(self, d):
  120. data[d] = getattr(self, d).isoformat()
  121. # Hackish for now
  122. if self.latitude or self.longitude:
  123. data['coordinates'] = { "latitude": self.latitude,
  124. "longitude": self.longitude }
  125. # Related objects
  126. data['coveredAreas'] = [c.to_dict() for c in self.coveredarea_set.all()]
  127. otherwebsites = self.otherwebsite_set.all()
  128. if otherwebsites:
  129. data['otherWebsites'] = { site.name: site.url for site in otherwebsites }
  130. chatrooms = self.chatroom_set.all()
  131. if chatrooms:
  132. data['chatrooms'] = [chatroom.url for chatroom in chatrooms]
  133. if hasattr(self, 'registeredoffice'):
  134. data['registeredOffice'] = self.registeredoffice.to_dict()
  135. return data
  136. def __unicode__(self):
  137. return self.name
  138. class OtherWebsite(models.Model):
  139. name = models.CharField(max_length=512)
  140. url = models.URLField(verbose_name="URL")
  141. isp = models.ForeignKey(ISPInfo)
  142. class RegisteredOffice(models.Model):
  143. """ http://json-schema.org/address """
  144. post_office_box = models.CharField(max_length=512, blank=True)
  145. extended_address = models.CharField(max_length=512, blank=True)
  146. street_address = models.CharField(max_length=512, blank=True)
  147. locality = models.CharField(max_length=512)
  148. region = models.CharField(max_length=512)
  149. postal_code = models.CharField(max_length=512, blank=True)
  150. country_name = models.CharField(max_length=512)
  151. isp = models.OneToOneField(ISPInfo)
  152. # not in db.ffdn.org spec
  153. siret = FRSIRETField('SIRET')
  154. def to_dict(self):
  155. d = dict()
  156. for field in ('post_office_box', 'extended_address', 'street_address',
  157. 'locality', 'region', 'postal_code', 'country_name'):
  158. if getattr(self, field):
  159. key = field.replace('_', '-')
  160. d[key] = getattr(self, field)
  161. return d
  162. class ChatRoom(models.Model):
  163. url = models.CharField(
  164. verbose_name="URL", max_length=256, validators=[chatroom_url_validator])
  165. isp = models.ForeignKey(ISPInfo)
  166. class CoveredArea(models.Model):
  167. name = models.CharField(max_length=512)
  168. # TODO: we must allow multiple values
  169. technologies = models.CharField(choices=TECHNOLOGIES, max_length=16)
  170. # TODO: find a geojson library
  171. #area =
  172. isp = models.ForeignKey(ISPInfo)
  173. def to_dict(self):
  174. return {"name": self.name,
  175. "technologies": [self.technologies]}
  176. class BankInfo(models.Model):
  177. """Information about bank account and the bank itself
  178. This is out of the scope of db.ffdn.org spec.
  179. """
  180. isp = models.OneToOneField(ISPInfo)
  181. iban = IBANField('IBAN')
  182. bic = BICField('BIC', blank=True, null=True)
  183. bank_name = models.CharField('établissement bancaire',
  184. max_length=100, blank=True, null=True)
  185. check_order = models.CharField('ordre',
  186. max_length=100, blank=False, null=False,
  187. help_text='Ordre devant figurer sur un \
  188. chèque bancaire à destination de\
  189. l\'association')
  190. class Meta:
  191. verbose_name = 'coordonnées bancaires'
  192. verbose_name_plural = verbose_name