{"sha":"0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","node_id":"MDY6Q29tbWl0NDgzMDU3NTQ6MGQ5YmQ3YjJiM2VjM2Q4MTE5ZDhjYzk3MmRhMTJjYjdlZmI1ZTJhMw==","commit":{"author":{"name":"Chris J. Karr","email":"chris@audacious-software.com","date":"2019-06-21T04:59:00Z"},"committer":{"name":"Chris J. Karr","email":"chris@audacious-software.com","date":"2019-06-21T04:59:00Z"},"message":"API improvements","tree":{"sha":"a2314cbfae88d9e19d1bfdc07ea83609236d1db5","url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/git/trees/a2314cbfae88d9e19d1bfdc07ea83609236d1db5"},"url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/git/commits/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/commits/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","html_url":"https://github.com/audacious-software/PassiveDataKit-Django/commit/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","comments_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/commits/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/comments","author":{"login":"audaciouscode","id":1141048,"node_id":"MDQ6VXNlcjExNDEwNDg=","avatar_url":"https://avatars.githubusercontent.com/u/1141048?v=4","gravatar_id":"","url":"https://api.github.com/users/audaciouscode","html_url":"https://github.com/audaciouscode","followers_url":"https://api.github.com/users/audaciouscode/followers","following_url":"https://api.github.com/users/audaciouscode/following{/other_user}","gists_url":"https://api.github.com/users/audaciouscode/gists{/gist_id}","starred_url":"https://api.github.com/users/audaciouscode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/audaciouscode/subscriptions","organizations_url":"https://api.github.com/users/audaciouscode/orgs","repos_url":"https://api.github.com/users/audaciouscode/repos","events_url":"https://api.github.com/users/audaciouscode/events{/privacy}","received_events_url":"https://api.github.com/users/audaciouscode/received_events","type":"User","site_admin":false},"committer":{"login":"audaciouscode","id":1141048,"node_id":"MDQ6VXNlcjExNDEwNDg=","avatar_url":"https://avatars.githubusercontent.com/u/1141048?v=4","gravatar_id":"","url":"https://api.github.com/users/audaciouscode","html_url":"https://github.com/audaciouscode","followers_url":"https://api.github.com/users/audaciouscode/followers","following_url":"https://api.github.com/users/audaciouscode/following{/other_user}","gists_url":"https://api.github.com/users/audaciouscode/gists{/gist_id}","starred_url":"https://api.github.com/users/audaciouscode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/audaciouscode/subscriptions","organizations_url":"https://api.github.com/users/audaciouscode/orgs","repos_url":"https://api.github.com/users/audaciouscode/repos","events_url":"https://api.github.com/users/audaciouscode/events{/privacy}","received_events_url":"https://api.github.com/users/audaciouscode/received_events","type":"User","site_admin":false},"parents":[{"sha":"5f352d19e504f3e1109630c3b6640835245e792e","url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/commits/5f352d19e504f3e1109630c3b6640835245e792e","html_url":"https://github.com/audacious-software/PassiveDataKit-Django/commit/5f352d19e504f3e1109630c3b6640835245e792e"}],"stats":{"total":223,"additions":176,"deletions":47},"files":[{"sha":"26a3dfc9a32942b17c812070a674fefb1c0c5ec9","filename":"admin.py","status":"modified","additions":16,"deletions":1,"changes":17,"blob_url":"https://github.com/audacious-software/PassiveDataKit-Django/blob/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/admin.py","raw_url":"https://github.com/audacious-software/PassiveDataKit-Django/raw/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/admin.py","contents_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/contents/admin.py?ref=0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","patch":"@@ -12,7 +12,8 @@\n DataPointVisualization, ReportJob, DataSourceAlert, \\\n DataServerMetadatum, ReportJobBatchRequest, DataServerApiToken, \\\n DataFile, AppConfiguration, DataGeneratorDefinition, \\\n- DataSourceReference, ReportDestination\n+ DataSourceReference, ReportDestination, DataServerAccessRequest, \\\n+ DataServerAccessRequestPending\n \n def reset_visualizations(modeladmin, request, queryset): # pylint: disable=unused-argument\n for visualization in queryset:\n@@ -205,3 +206,17 @@ class DataSourceReferenceAdmin(admin.OSMGeoAdmin):\n class ReportDestinationAdmin(admin.OSMGeoAdmin):\n list_display = ('user', 'destination', 'description')\n search_fields = ('destination', 'user',)\n+\n+@admin.register(DataServerAccessRequestPending)\n+class DataServerAccessRequestPendingAdmin(admin.OSMGeoAdmin):\n+ list_display = ('user_identifier', 'request_type', 'request_time', 'successful', 'processed',)\n+\n+ search_fields = ('user_identifier', 'request_metadata',)\n+ list_filter = ('request_time', 'request_type', 'successful', 'processed',)\n+\n+@admin.register(DataServerAccessRequest)\n+class DataServerAccessRequestAdmin(admin.OSMGeoAdmin):\n+ list_display = ('user_identifier', 'request_type', 'request_time', 'successful',)\n+\n+ search_fields = ('user_identifier', 'request_metadata',)\n+ list_filter = ('request_time', 'request_type', 'successful',)"},{"sha":"2f43f2f102d3204fc2a7a75211b443bd4e2d0d73","filename":"api_views.py","status":"modified","additions":20,"deletions":2,"changes":22,"blob_url":"https://github.com/audacious-software/PassiveDataKit-Django/blob/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/api_views.py","raw_url":"https://github.com/audacious-software/PassiveDataKit-Django/raw/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/api_views.py","contents_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/contents/api_views.py?ref=0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","patch":"@@ -7,13 +7,14 @@\n \n from django.conf import settings\n from django.core.exceptions import PermissionDenied\n+from django.db.models import Q\n from django.http import HttpResponse, HttpResponseNotAllowed\n from django.utils import timezone\n from django.views.decorators.csrf import csrf_exempt\n \n from django.contrib.auth import authenticate\n \n-from passive_data_kit.models import DataServerApiToken, DataPoint\n+from passive_data_kit.models import DataServerApiToken, DataPoint, DataServerAccessRequestPending\n \n \n def valid_pdk_token_required(function):\n@@ -22,7 +23,9 @@ def wrap(request, *args, **kwargs):\n \n now = timezone.now()\n \n- if DataServerApiToken.objects.filter(token=token, expires__gte=now).count() > 0:\n+ expires = Q(expires__gte=now) | Q(expires=None)\n+\n+ if DataServerApiToken.objects.filter(token=token).filter(expires).count() > 0:\n return function(request, *args, **kwargs)\n else:\n raise PermissionDenied('Invalid Token')\n@@ -134,6 +137,21 @@ def pdk_data_point_query(request): # pylint: disable=too-many-locals, too-many-b\n \n payload['matches'] = matches\n \n+ token = DataServerApiToken.objects.filter(token=request.POST['token']).first()\n+\n+ access_request = DataServerAccessRequestPending()\n+\n+ if token is not None:\n+ access_request.user_identifier = str(token.user.pk) + ': ' + str(token.user.username)\n+ else:\n+ access_request.user_identifier = 'api_token: ' + request.POST['token']\n+\n+ access_request.request_type = 'api-data-points-request'\n+ access_request.request_time = timezone.now()\n+ access_request.request_metadata = json.dumps(request.POST, indent=2)\n+ access_request.successful = True\n+ access_request.save()\n+\n return HttpResponse(json.dumps(payload), content_type='application/json')\n \n return HttpResponseNotAllowed(['POST'])"},{"sha":"0edb9d30131ba9705f2d1f697825740cbbf8d8ea","filename":"management/commands/pdk_process_data_access_requests.py","status":"added","additions":16,"deletions":0,"changes":16,"blob_url":"https://github.com/audacious-software/PassiveDataKit-Django/blob/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/management%2Fcommands%2Fpdk_process_data_access_requests.py","raw_url":"https://github.com/audacious-software/PassiveDataKit-Django/raw/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/management%2Fcommands%2Fpdk_process_data_access_requests.py","contents_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/contents/management%2Fcommands%2Fpdk_process_data_access_requests.py?ref=0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","patch":"@@ -0,0 +1,16 @@\n+# pylint: disable=no-member,line-too-long\n+\n+from django.core.management.base import BaseCommand\n+\n+from ...decorators import handle_lock\n+from ...models import DataServerAccessRequestPending\n+\n+class Command(BaseCommand):\n+ help = 'Convert pending DataServerAccessRequestPending items to archived records.'\n+\n+ @handle_lock\n+ def handle(self, *args, **options): # pylint: disable=too-many-locals, too-many-branches, too-many-statements\n+ for request in DataServerAccessRequestPending.objects.filter(processed=False):\n+ request.process()\n+\n+ DataServerAccessRequestPending.objects.filter(processed=True).delete()"},{"sha":"07c93ca7c1a7735a043106b9af424335640b7ec5","filename":"migrations/0051_datapoint_source_reference.py","status":"modified","additions":0,"deletions":1,"changes":1,"blob_url":"https://github.com/audacious-software/PassiveDataKit-Django/blob/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/migrations%2F0051_datapoint_source_reference.py","raw_url":"https://github.com/audacious-software/PassiveDataKit-Django/raw/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/migrations%2F0051_datapoint_source_reference.py","contents_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/contents/migrations%2F0051_datapoint_source_reference.py?ref=0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","patch":"@@ -1,5 +1,4 @@\n # pylint: skip-file\n-# pylint: skip-file\n # -*- coding: utf-8 -*-\n # Generated by Django 1.11.20 on 2019-04-05 23:02\n from __future__ import unicode_literals"},{"sha":"45bf68fb2973e1ffec90e7bd1e13eecbe9cce532","filename":"migrations/0059_auto_20190614_1335.py","status":"modified","additions":1,"deletions":20,"changes":21,"blob_url":"https://github.com/audacious-software/PassiveDataKit-Django/blob/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/migrations%2F0059_auto_20190614_1335.py","raw_url":"https://github.com/audacious-software/PassiveDataKit-Django/raw/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/migrations%2F0059_auto_20190614_1335.py","contents_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/contents/migrations%2F0059_auto_20190614_1335.py?ref=0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","patch":"@@ -13,24 +13,5 @@ class Migration(migrations.Migration):\n ]\n \n operations = [\n- migrations.AlterField(\n- model_name='appconfiguration',\n- name='configuration_json',\n- field=models.TextField(max_length=34359738368),\n- ),\n- migrations.AlterField(\n- model_name='datasourcealert',\n- name='alert_details',\n- field=models.TextField(max_length=34359738368),\n- ),\n- migrations.AlterField(\n- model_name='reportdestination',\n- name='parameters',\n- field=models.TextField(max_length=34359738368),\n- ),\n- migrations.AlterField(\n- model_name='reportjobbatchrequest',\n- name='parameters',\n- field=models.TextField(max_length=34359738368),\n- ),\n+ \t# Removed migrations that would convert JSON fields to text fields.\n ]"},{"sha":"aaed84430de9ddfbf46480e58dc4a4fd7ba1ccb5","filename":"migrations/0060_auto_20190620_2326.py","status":"added","additions":40,"deletions":0,"changes":40,"blob_url":"https://github.com/audacious-software/PassiveDataKit-Django/blob/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/migrations%2F0060_auto_20190620_2326.py","raw_url":"https://github.com/audacious-software/PassiveDataKit-Django/raw/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/migrations%2F0060_auto_20190620_2326.py","contents_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/contents/migrations%2F0060_auto_20190620_2326.py?ref=0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","patch":"@@ -0,0 +1,40 @@\n+# pylint: skip-file\n+# -*- coding: utf-8 -*-\n+# Generated by Django 1.11.21 on 2019-06-21 04:26\n+from __future__ import unicode_literals\n+\n+import django.contrib.postgres.fields.jsonb\n+from django.db import migrations, models\n+\n+\n+class Migration(migrations.Migration):\n+\n+ dependencies = [\n+ ('passive_data_kit', '0059_auto_20190614_1335'),\n+ ]\n+\n+ operations = [\n+ migrations.CreateModel(\n+ name='DataServerAccessRequest',\n+ fields=[\n+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n+ ('user_identifier', models.CharField(db_index=True, max_length=4096)),\n+ ('request_type', models.CharField(db_index=True, max_length=4096)),\n+ ('request_time', models.DateTimeField(db_index=True)),\n+ ('request_metadata', models.TextField(max_length=34359738368)),\n+ ('successful', models.BooleanField(db_index=True, default=True)),\n+ ],\n+ ),\n+ migrations.CreateModel(\n+ name='DataServerAccessRequestPending',\n+ fields=[\n+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n+ ('user_identifier', models.CharField(max_length=4096)),\n+ ('request_type', models.CharField(max_length=4096)),\n+ ('request_time', models.DateTimeField()),\n+ ('request_metadata', models.TextField(max_length=34359738368)),\n+ ('successful', models.BooleanField(default=True)),\n+ ('processed', models.BooleanField(default=False)),\n+ ],\n+ ),\n+ ]"},{"sha":"91f96d547eb6aff561d44c6820861ffad33f23d3","filename":"models.py","status":"modified","additions":46,"deletions":15,"changes":61,"blob_url":"https://github.com/audacious-software/PassiveDataKit-Django/blob/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/models.py","raw_url":"https://github.com/audacious-software/PassiveDataKit-Django/raw/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/models.py","contents_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/contents/models.py?ref=0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","patch":"@@ -1047,6 +1047,27 @@ def report_job_batch_request_pre_save_handler(sender, **kwargs): # pylint: disab\n else:\n job.parameters = json.dumps(parameters, indent=2)\n \n+class AppConfiguration(models.Model):\n+ name = models.CharField(max_length=1024)\n+ id_pattern = models.CharField(max_length=1024)\n+ context_pattern = models.CharField(max_length=1024, default='.*')\n+\n+ if install_supports_jsonfield():\n+ configuration_json = JSONField()\n+ else:\n+ configuration_json = models.TextField(max_length=(32 * 1024 * 1024 * 1024))\n+\n+ evaluate_order = models.IntegerField(default=1)\n+\n+ is_valid = models.BooleanField(default=False)\n+ is_enabled = models.BooleanField(default=True)\n+\n+ def configuration(self):\n+ if install_supports_jsonfield():\n+ return self.configuration_json\n+\n+ return json.loads(self.configuration_json)\n+\n class DataServerApiToken(models.Model):\n class Meta: # pylint: disable=old-style-class, no-init, too-few-public-methods\n verbose_name = \"data server API token\"\n@@ -1065,23 +1086,33 @@ def fetch_token(self):\n \n return self.token\n \n-class AppConfiguration(models.Model):\n- name = models.CharField(max_length=1024)\n- id_pattern = models.CharField(max_length=1024)\n- context_pattern = models.CharField(max_length=1024, default='.*')\n+class DataServerAccessRequest(models.Model):\n+ user_identifier = models.CharField(max_length=4096, db_index=True)\n+ request_type = models.CharField(max_length=4096, db_index=True)\n+ request_time = models.DateTimeField(db_index=True)\n+ request_metadata = models.TextField(max_length=(32 * 1024 * 1024 * 1024))\n+ successful = models.BooleanField(default=True, db_index=True)\n \n- if install_supports_jsonfield():\n- configuration_json = JSONField()\n- else:\n- configuration_json = models.TextField(max_length=(32 * 1024 * 1024 * 1024))\n+class DataServerAccessRequestPending(models.Model):\n+ user_identifier = models.CharField(max_length=4096)\n+ request_type = models.CharField(max_length=4096)\n+ request_time = models.DateTimeField()\n+ request_metadata = models.TextField(max_length=(32 * 1024 * 1024 * 1024))\n+ successful = models.BooleanField(default=True)\n \n- evaluate_order = models.IntegerField(default=1)\n+ processed = models.BooleanField(default=False)\n \n- is_valid = models.BooleanField(default=False)\n- is_enabled = models.BooleanField(default=True)\n+ def process(self):\n+ request = DataServerAccessRequest()\n \n- def configuration(self):\n- if install_supports_jsonfield():\n- return self.configuration_json\n+ request.user_identifier = self.user_identifier\n+ request.request_type = self.request_type\n+ request.request_type = self.request_type\n+ request.request_time = self.request_time\n+ request.request_metadata = self.request_metadata\n+ request.successful = self.successful\n \n- return json.loads(self.configuration_json)\n+ request.save()\n+\n+ self.processed = True\n+ self.save()"},{"sha":"7f15820e104590227c4f59a10fc77289e7b72789","filename":"templates/pdk_user_profile.html","status":"modified","additions":10,"deletions":7,"changes":17,"blob_url":"https://github.com/audacious-software/PassiveDataKit-Django/blob/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/templates%2Fpdk_user_profile.html","raw_url":"https://github.com/audacious-software/PassiveDataKit-Django/raw/0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3/templates%2Fpdk_user_profile.html","contents_url":"https://api.github.com/repos/audacious-software/PassiveDataKit-Django/contents/templates%2Fpdk_user_profile.html?ref=0d9bd7b2b3ec3d8119d8cc972da12cb7efb5e2a3","patch":"@@ -14,23 +14,26 @@