models.py 8.3 KB

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