models.py 7.8 KB

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