Browse Source

Basic ISP database application

Baptiste Jonglez 10 years ago
parent
commit
9f8d1c0d4a

+ 0 - 0
coin/isp_database/__init__.py


+ 61 - 0
coin/isp_database/admin.py

@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib import admin
+
+from coin.isp_database.models import ISPInfo, RegisteredOffice, OtherWebsite, ChatRoom, CoveredArea
+
+
+class SingleInstanceAdminMixin(object):
+    """Hides the "Add" button when there is already an instance"""
+    def has_add_permission(self, request):
+        num_objects = self.model.objects.count()
+        if num_objects >= 1:
+            return False
+        return super(SingleInstanceAdminMixin, self).has_add_permission(request)
+
+
+class RegisteredOfficeInline(admin.StackedInline):
+    model = RegisteredOffice
+    extra = 0
+    fields = (('street_address', 'extended_address', 'post_office_box'),
+              ('postal_code', 'locality'),
+              ('region', 'country_name'))
+
+
+class OtherWebsiteInline(admin.StackedInline):
+    model = OtherWebsite
+    extra = 0
+
+
+class ChatRoomInline(admin.StackedInline):
+    model = ChatRoom
+    extra = 0
+
+
+class CoveredAreaInline(admin.StackedInline):
+    model = CoveredArea
+    extra = 0
+
+
+class ISPInfoAdmin(SingleInstanceAdminMixin, admin.ModelAdmin):
+    model = ISPInfo
+    fieldsets = (
+        ('General', {'fields': (
+            ('name', 'shortname'),
+            'description',
+            'logoURL',
+            ('creationDate', 'ffdnMemberSince'),
+            'progressStatus',
+            ('latitude', 'longitude'))}),
+        ('Contact', {'fields': (
+            ('email', 'mainMailingList'),
+            'website')}),
+    )
+
+    inlines = (RegisteredOfficeInline, OtherWebsiteInline, ChatRoomInline,
+               CoveredAreaInline)
+    save_on_top = True
+
+
+admin.site.register(ISPInfo, ISPInfoAdmin)

+ 98 - 0
coin/isp_database/migrations/0001_initial.py

@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django.core.validators
+import coin.isp_database.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ChatRoom',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('url', models.CharField(max_length=256, verbose_name='URL')),
+            ],
+            options={
+            },
+            bases=(models.Model,),
+        ),
+        migrations.CreateModel(
+            name='CoveredArea',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('name', models.CharField(max_length=512)),
+                ('technologies', models.CharField(max_length=16, choices=[('ftth', 'FTTH'), ('dsl', '*DSL'), ('wifi', 'WiFi')])),
+            ],
+            options={
+            },
+            bases=(models.Model,),
+        ),
+        migrations.CreateModel(
+            name='ISPInfo',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('name', models.CharField(help_text="The ISP's name", max_length=512)),
+                ('shortname', models.CharField(help_text='Shorter name', max_length=15, blank=True)),
+                ('description', models.TextField(help_text='Short text describing the project', blank=True)),
+                ('logoURL', models.URLField(help_text="HTTP(S) URL of the ISP's logo", verbose_name='logo URL', blank=True)),
+                ('website', models.URLField(help_text='URL to the official website', blank=True)),
+                ('email', models.EmailField(help_text='Contact email address', max_length=254)),
+                ('mainMailingList', models.EmailField(help_text='Main public mailing-list', max_length=254, verbose_name='main mailing list', blank=True)),
+                ('creationDate', models.DateField(help_text='Date of creation for legal structure', null=True, verbose_name='creation date', blank=True)),
+                ('ffdnMemberSince', models.DateField(help_text='Date at wich the ISP joined the Federation', null=True, verbose_name='FFDN member since', blank=True)),
+                ('progressStatus', models.PositiveSmallIntegerField(blank=True, help_text='Progression status of the ISP', null=True, verbose_name='progress status', validators=[django.core.validators.MaxValueValidator(7)])),
+                ('latitude', models.FloatField(help_text='Coordinates of the registered office (latitude)', null=True, blank=True)),
+                ('longitude', models.FloatField(help_text='Coordinates of the registered office (longitude)', null=True, blank=True)),
+            ],
+            options={
+            },
+            bases=(coin.isp_database.models.SingleInstanceMixin, models.Model),
+        ),
+        migrations.CreateModel(
+            name='OtherWebsite',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('name', models.CharField(max_length=512)),
+                ('url', models.URLField(verbose_name='URL')),
+                ('isp', models.ForeignKey(to='isp_database.ISPInfo')),
+            ],
+            options={
+            },
+            bases=(models.Model,),
+        ),
+        migrations.CreateModel(
+            name='RegisteredOffice',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('post_office_box', models.CharField(max_length=512, blank=True)),
+                ('extended_address', models.CharField(max_length=512, blank=True)),
+                ('street_address', models.CharField(max_length=512, blank=True)),
+                ('locality', models.CharField(max_length=512)),
+                ('region', models.CharField(max_length=512)),
+                ('postal_code', models.CharField(max_length=512, blank=True)),
+                ('country_name', models.CharField(max_length=512)),
+                ('isp', models.OneToOneField(to='isp_database.ISPInfo')),
+            ],
+            options={
+            },
+            bases=(models.Model,),
+        ),
+        migrations.AddField(
+            model_name='coveredarea',
+            name='isp',
+            field=models.ForeignKey(to='isp_database.ISPInfo'),
+            preserve_default=True,
+        ),
+        migrations.AddField(
+            model_name='chatroom',
+            name='isp',
+            field=models.ForeignKey(to='isp_database.ISPInfo'),
+            preserve_default=True,
+        ),
+    ]

+ 0 - 0
coin/isp_database/migrations/__init__.py


+ 173 - 0
coin/isp_database/models.py

@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models
+from django.core.validators import MaxValueValidator
+from django.core.exceptions import ValidationError
+
+from coin.members.models import count_active_members
+from coin.offers.models import count_active_subscriptions
+
+# API version, see http://db.ffdn.org/format
+API_VERSION = "0.1"
+
+TECHNOLOGIES = (('ftth', 'FTTH'),
+                ('dsl', '*DSL'),
+                ('wifi', 'WiFi'))
+
+
+class SingleInstanceMixin(object):
+    """Makes sure that no more than one instance of a given model is created."""
+
+    def clean(self):
+        model = self.__class__
+        if (model.objects.count() > 0 and self.id != model.objects.get().id):
+            raise ValidationError("Can only create 1 instance of %s" % model.__name__)
+        super(SingleInstanceMixin, self).clean()
+
+
+class ISPInfo(SingleInstanceMixin, models.Model):
+    """http://db.ffdn.org/format
+
+    The naming convention is different from Python/django so that it
+    matches exactly the format (which uses CamelCase...)
+    """
+    name = models.CharField(max_length=512,
+                            help_text="The ISP's name")
+    # Length required by the spec
+    shortname = models.CharField(max_length=15, blank=True,
+                                 help_text="Shorter name")
+    description = models.TextField(blank=True,
+                                   help_text="Short text describing the project")
+    logoURL = models.URLField(blank=True,
+                              verbose_name="logo URL",
+                              help_text="HTTP(S) URL of the ISP's logo")
+    website = models.URLField(blank=True,
+                              help_text='URL to the official website')
+    email = models.EmailField(max_length=254,
+                              help_text="Contact email address")
+    mainMailingList = models.EmailField(max_length=254, blank=True,
+                                        verbose_name="main mailing list",
+                                        help_text="Main public mailing-list")
+    creationDate = models.DateField(blank=True, null=True,
+                                    verbose_name="creation date",
+                                     help_text="Date of creation for legal structure")
+    ffdnMemberSince = models.DateField(blank=True, null=True,
+                                       verbose_name="FFDN member since",
+                                       help_text="Date at wich the ISP joined the Federation")
+    # TODO: choice field
+    progressStatus = models.PositiveSmallIntegerField(
+        validators=[MaxValueValidator(7)],
+        blank=True, null=True, verbose_name='progress status',
+        help_text="Progression status of the ISP")
+    # TODO: better model for coordinates
+    latitude = models.FloatField(blank=True, null=True,
+        help_text="Coordinates of the registered office (latitude)")
+    longitude = models.FloatField(blank=True, null=True,
+        help_text="Coordinates of the registered office (longitude)")
+
+    # Uncomment this if you want to manage these counters by hand.
+    #member_count = models.PositiveIntegerField(help_text="Number of members")
+    #subscriber_count = models.PositiveIntegerField(
+    #    help_text="Number of subscribers to an internet access")
+
+    @property
+    def memberCount(self):
+        """Number of members"""
+        return count_active_members()
+
+    @property
+    def subscriberCount(self):
+        """Number of subscribers to an internet access"""
+        return count_active_subscriptions()
+
+    @property
+    def version(self):
+        """Version of the API"""
+        return API_VERSION
+
+    def get_absolute_url(self):
+        return '/isp.json'
+
+    def to_dict(self):
+        data = dict()
+        # These are required
+        for f in ('version', 'name', 'email', 'memberCount', 'subscriberCount'):
+            data[f] = getattr(self, f)
+
+        # These are optional
+        for f in ('shortname', 'description', 'logoURL', 'website',
+                  'mainMailingList', 'progressStatus'):
+            if getattr(self, f):
+                data[f] = getattr(self, f)
+
+        # Dates
+        for d in ('creationDate', 'ffdnMemberSince'):
+            if getattr(self, d):
+                data[d] = getattr(self, d).isoformat()
+
+        # Hackish for now
+        if self.latitude or self.longitude:
+            data['coordinates'] = { "latitude": self.latitude,
+                                    "longitude": self.longitude }
+
+        # Related objects
+        data['coveredAreas'] = [c.to_dict() for c in self.coveredarea_set.all()]
+        otherwebsites = self.otherwebsite_set.all()
+        if otherwebsites:
+            data['otherWebsites'] = { site.name: site.url for site in otherwebsites }
+        chatrooms = self.chatroom_set.all()
+        if chatrooms:
+            data['chatrooms'] = [chatroom.url for chatroom in chatrooms]
+        if hasattr(self, 'registeredoffice'):
+            data['registeredOffice'] = self.registeredoffice.to_dict()
+
+        return data
+
+    def __unicode__(self):
+        return self.name
+
+
+class OtherWebsite(models.Model):
+    name = models.CharField(max_length=512)
+    url = models.URLField(verbose_name="URL")
+    isp = models.ForeignKey(ISPInfo)
+
+
+class RegisteredOffice(models.Model):
+    """ http://json-schema.org/address """
+    post_office_box = models.CharField(max_length=512, blank=True)
+    extended_address = models.CharField(max_length=512, blank=True)
+    street_address = models.CharField(max_length=512, blank=True)
+    locality = models.CharField(max_length=512)
+    region = models.CharField(max_length=512)
+    postal_code = models.CharField(max_length=512, blank=True)
+    country_name = models.CharField(max_length=512)
+    isp = models.OneToOneField(ISPInfo)
+
+    def to_dict(self):
+        d = dict()
+        for field in ('post_office_box', 'extended_address', 'street_address',
+                      'locality', 'region', 'postal_code', 'country_name'):
+            if getattr(self, field):
+                key = field.replace('_', '-')
+                d[key] = getattr(self, field)
+        return d
+
+
+class ChatRoom(models.Model):
+    url = models.CharField(verbose_name="URL", max_length=256)
+    isp = models.ForeignKey(ISPInfo)
+
+
+class CoveredArea(models.Model):
+    name = models.CharField(max_length=512)
+    # TODO: we must allow multiple values
+    technologies = models.CharField(choices=TECHNOLOGIES, max_length=16)
+    # TODO: find a geojson library
+    #area =
+    isp = models.ForeignKey(ISPInfo)
+
+    def to_dict(self):
+        return {"name": self.name,
+                "technologies": [self.technologies]}

+ 3 - 0
coin/isp_database/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 18 - 0
coin/isp_database/views.py

@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import json
+
+from django.shortcuts import render
+from django.http import HttpResponse, Http404
+
+from coin.isp_database.models import ISPInfo
+
+
+def isp_json(request):
+    try:
+        isp = ISPInfo.objects.get()
+    except ISPInfo.DoesNotExist:
+        raise Http404
+    data = isp.to_dict()
+    return HttpResponse(json.dumps(data), content_type="application/json")