models.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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 django.utils.translation import ugettext_lazy as _
  7. from localflavor.generic.models import IBANField, BICField
  8. from localflavor.fr.models import FRSIRETField
  9. from coin.members.models import count_active_members
  10. from coin.offers.models import count_active_subscriptions
  11. from coin import utils
  12. # API version, see http://db.ffdn.org/format
  13. API_VERSION = 0.1
  14. TECHNOLOGIES = (('ftth', _('FTTH')),
  15. ('dsl', _('xDSL')),
  16. ('wifi', _('Wi-Fi')))
  17. class SingleInstanceMixin(object):
  18. """Makes sure that no more than one instance of a given model is created."""
  19. def clean(self):
  20. from django.utils.translation import ugettext as _
  21. model = self.__class__
  22. if (model.objects.count() > 0 and self.id != model.objects.get().id):
  23. error = _("Can only create 1 instance of {model}.")
  24. raise ValidationError(error.format(model=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. name = models.CharField(max_length=512, verbose_name=_("name"),
  32. help_text=_("The ISP's name"))
  33. # Length required by the spec
  34. shortname = models.CharField(max_length=15, blank=True,
  35. verbose_name=_("short name"),
  36. help_text=_("Shorter name"))
  37. description = models.TextField(blank=True, verbose_name=_("description"),
  38. help_text=_("Short text describing the project"))
  39. logoURL = models.URLField(blank=True, verbose_name=_("logo URL"),
  40. help_text=_("HTTP(S) URL of the ISP's logo"))
  41. website = models.URLField(blank=True, verbose_name=_("website"),
  42. help_text=_('URL to the official website'))
  43. email = models.EmailField(max_length=254, verbose_name=_("email"),
  44. help_text=_("Contact email address"))
  45. mainMailingList = models.EmailField(max_length=254, blank=True,
  46. verbose_name=_("main mailing list"),
  47. help_text=_("Main public mailing-list"))
  48. creationDate = models.DateField(blank=True, null=True,
  49. verbose_name=_("creation date"),
  50. help_text=_("Date of creation for legal structure"))
  51. ffdnMemberSince = models.DateField(blank=True, null=True,
  52. verbose_name=_("FFDN member since"),
  53. help_text=_("Date at wich the ISP joined the Federation"))
  54. # TODO: choice field
  55. progressStatus = models.PositiveSmallIntegerField(
  56. validators=[MaxValueValidator(7)],
  57. blank=True, null=True, verbose_name=_('progress status'),
  58. help_text=_("Progression status of the ISP"))
  59. # TODO: better model for coordinates
  60. latitude = models.FloatField(blank=True, null=True,
  61. verbose_name=_("latitude"),
  62. help_text=_("Coordinates of the registered "
  63. "office (latitude)"))
  64. longitude = models.FloatField(blank=True, null=True,
  65. verbose_name=_("longitude"),
  66. help_text=_("Coordinates of the registered "
  67. "office (longitude)"))
  68. # Uncomment this if you want to manage these counters by hand.
  69. #member_count = models.PositiveIntegerField(help_text="Number of members")
  70. #subscriber_count = models.PositiveIntegerField(
  71. # help_text="Number of subscribers to an internet access")
  72. # field outside of db-ffdn format:
  73. administrative_email = models.EmailField(
  74. max_length=254, blank=True, verbose_name=_("administrative contact email"),
  75. help_text=_("Email address to use for administrative inquiries "
  76. "(e.g. membership, personal information)"))
  77. support_email = models.EmailField(
  78. max_length=254, blank=True, verbose_name=_("support contact email"),
  79. help_text=_("Email address to use for technical support"))
  80. lists_url = models.URLField(
  81. verbose_name=_("mailing list server"), blank=True,
  82. help_text=_("URL of the mailing list server"))
  83. @property
  84. def memberCount(self):
  85. """Number of members"""
  86. return count_active_members()
  87. @property
  88. def subscriberCount(self):
  89. """Number of subscribers to an internet access"""
  90. return count_active_subscriptions()
  91. @property
  92. def version(self):
  93. """Version of the API"""
  94. return API_VERSION
  95. @property
  96. def main_chat_verbose(self):
  97. from django.utils.translation import ugettext as _
  98. m = utils.re_chat_url.match(self.chatroom_set.first().url)
  99. return _('{channel} on {server}').format(**(m.groupdict()))
  100. def get_absolute_url(self):
  101. return '/isp.json'
  102. def to_dict(self):
  103. data = dict()
  104. # These are required
  105. for f in ('version', 'name', 'email', 'memberCount', 'subscriberCount'):
  106. data[f] = getattr(self, f)
  107. # These are optional
  108. for f in ('shortname', 'description', 'logoURL', 'website',
  109. 'mainMailingList', 'progressStatus'):
  110. if getattr(self, f):
  111. data[f] = getattr(self, f)
  112. # Dates
  113. for d in ('creationDate', 'ffdnMemberSince'):
  114. if getattr(self, d):
  115. data[d] = getattr(self, d).isoformat()
  116. # Hackish for now
  117. if self.latitude or self.longitude:
  118. data['coordinates'] = { "latitude": self.latitude,
  119. "longitude": self.longitude }
  120. # Related objects
  121. data['coveredAreas'] = [c.to_dict() for c in self.coveredarea_set.all()]
  122. otherwebsites = self.otherwebsite_set.all()
  123. if otherwebsites:
  124. data['otherWebsites'] = { site.name: site.url for site in otherwebsites }
  125. chatrooms = self.chatroom_set.all()
  126. if chatrooms:
  127. data['chatrooms'] = [chatroom.url for chatroom in chatrooms]
  128. if hasattr(self, 'registeredoffice'):
  129. data['registeredOffice'] = self.registeredoffice.to_dict()
  130. return data
  131. def __unicode__(self):
  132. return self.name
  133. class Meta:
  134. verbose_name = _('ISP details')
  135. verbose_name_plural = _('ISP details')
  136. class OtherWebsite(models.Model):
  137. name = models.CharField(max_length=512, verbose_name=_("name"))
  138. url = models.URLField(verbose_name=_("URL"))
  139. isp = models.ForeignKey(ISPInfo, verbose_name=_("ISP"))
  140. class Meta:
  141. verbose_name = _('other website')
  142. verbose_name_plural = _('other websites')
  143. class RegisteredOffice(models.Model):
  144. """ http://json-schema.org/address """
  145. post_office_box = models.CharField(max_length=512, blank=True,
  146. verbose_name=_("post office box"))
  147. extended_address = models.CharField(max_length=512, blank=True,
  148. verbose_name=_("extended address"))
  149. street_address = models.CharField(max_length=512, blank=True,
  150. verbose_name=_("street address"))
  151. locality = models.CharField(max_length=512, verbose_name=_("locality"))
  152. region = models.CharField(max_length=512, verbose_name=_("region"))
  153. postal_code = models.CharField(max_length=512, blank=True,
  154. verbose_name=_("postal code"))
  155. country_name = models.CharField(max_length=512,
  156. verbose_name=_("country name"))
  157. isp = models.OneToOneField(ISPInfo, verbose_name=_("ISP"))
  158. # not in db.ffdn.org spec
  159. siret = FRSIRETField('SIRET')
  160. def to_dict(self):
  161. d = dict()
  162. for field in ('post_office_box', 'extended_address', 'street_address',
  163. 'locality', 'region', 'postal_code', 'country_name'):
  164. if getattr(self, field):
  165. key = field.replace('_', '-')
  166. d[key] = getattr(self, field)
  167. return d
  168. class Meta:
  169. verbose_name = _('registered office')
  170. verbose_name_plural = _('registered offices')
  171. class ChatRoom(models.Model):
  172. url = models.CharField(verbose_name=_("URL"), max_length=256)
  173. isp = models.ForeignKey(ISPInfo, verbose_name=_("ISP"))
  174. class Meta:
  175. verbose_name = _('chat room')
  176. verbose_name_plural = _('chat rooms')
  177. class CoveredArea(models.Model):
  178. name = models.CharField(max_length=512, verbose_name=_("name"))
  179. # TODO: we must allow multiple values
  180. technologies = models.CharField(choices=TECHNOLOGIES, max_length=16,
  181. verbose_name=_("technologies"))
  182. # TODO: find a geojson library
  183. #area =
  184. isp = models.ForeignKey(ISPInfo, verbose_name=_("ISP"))
  185. def to_dict(self):
  186. return {"name": self.name,
  187. "technologies": [self.technologies]}
  188. class Meta:
  189. verbose_name = _('covered area')
  190. verbose_name_plural = _('covered areas')
  191. class BankInfo(models.Model):
  192. """Information about bank account and the bank itself
  193. This is out of the scope of db.ffdn.org spec.
  194. """
  195. isp = models.OneToOneField(ISPInfo, verbose_name=_("ISP"))
  196. iban = IBANField(verbose_name=_('IBAN'))
  197. bic = BICField(verbose_name=_('BIC'), blank=True, null=True)
  198. bank_name = models.CharField(verbose_name=_("bank name"), max_length=100,
  199. blank=True, null=True)
  200. check_order = models.CharField(verbose_name=_("check recipient"),
  201. max_length=100, blank=False, null=False,
  202. help_text=_("Recipient that must appear on "
  203. "a bank check destinated to "
  204. "the ISP"))
  205. class Meta:
  206. verbose_name = _('bank information')
  207. verbose_name_plural = _('banks information')