diff --git a/activities/management/commands/bluefrog_adoptions.py b/activities/management/commands/bluefrog_adoptions.py index 143fe17efa..6d473dc345 100644 --- a/activities/management/commands/bluefrog_adoptions.py +++ b/activities/management/commands/bluefrog_adoptions.py @@ -78,4 +78,3 @@ def handle(self, *args, **options): wtr.writerow(['Not able to Save Adoption in AP Adoption TABLE', member_code, ap_adopt_practice, e]) - diff --git a/activities/models.py b/activities/models.py index e7fd123dd6..f946d2ffcc 100644 --- a/activities/models.py +++ b/activities/models.py @@ -33,8 +33,8 @@ class VRPpayment(models.Manager): - "Custom manager filters standard query set with given args." + def __init__(self, partner_id, block_id, start_period, end_period): super(VRPpayment, self).__init__() self.start_yyyy = start_period[-4:] @@ -95,6 +95,7 @@ class FrontLineWorkerPresent(models.Model): def __unicode__(self): return self.worker_type + class Screening(CocoModel): id = models.AutoField(primary_key=True) old_coco_id = models.BigIntegerField(editable=False, null=True) @@ -147,6 +148,7 @@ class PersonMeetingAttendance(CocoModel): def __unicode__(self): return u'%s' % (self.id) + class PersonAdoptPractice(CocoModel): id = models.AutoField(primary_key=True) old_coco_id = models.BigIntegerField(editable=False, null=True) @@ -173,9 +175,11 @@ def __unicode__(self): class Meta: unique_together = ("person", "video", "date_of_adoption") + post_save.connect(save_log, sender=PersonAdoptPractice) pre_delete.connect(delete_log, sender=PersonAdoptPractice) + class JSLPS_Screening(CocoModel): id = models.AutoField(primary_key=True) screenig_code = models.CharField(max_length=100) @@ -213,7 +217,6 @@ class Meta: verbose_name_plural = "JSLPS Adoption" - class AP_Screening(CocoModel): screening_code = models.CharField(max_length=100) screening = models.ForeignKey(Screening, null=True, blank=True) @@ -240,4 +243,3 @@ class Meta: verbose_name = "Adoption" verbose_name_plural = "Adoption" - diff --git a/activities/serializers.py b/activities/serializers.py new file mode 100644 index 0000000000..8e5ad79253 --- /dev/null +++ b/activities/serializers.py @@ -0,0 +1,19 @@ +# rest framework imports +from rest_framework import serializers +# app imports +from activities.models import Screening +from api.utils import DynamicFieldsModelSerializer + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class ScreeningSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for Screening model + """ + + class Meta: + model = Screening + fields = '__all__' diff --git a/activities/urls.py b/activities/urls.py new file mode 100644 index 0000000000..952af84a2c --- /dev/null +++ b/activities/urls.py @@ -0,0 +1,14 @@ +# rest framework imports +from django.conf.urls import url, include, patterns +# app imports +from activities import views + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +urlpatterns=[ + url(r'^api/screening', views.ScreeningAPIView.as_view(), name='upavan'), +] + diff --git a/activities/views.py b/activities/views.py new file mode 100644 index 0000000000..34fa3d1d9c --- /dev/null +++ b/activities/views.py @@ -0,0 +1,133 @@ +""" +This file consists of views for activities.models +""" +# default imports +from django.shortcuts import render +# rest framework imports +from rest_framework import viewsets, generics +from rest_framework import permissions +from rest_framework.response import Response +from rest_framework import status +# app imports +from videos.models import * +from activities.models import * +from activities.serializers import * +# authentication imports +from rest_framework.authentication import SessionAuthentication, BasicAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated +# logging, pagination and permission imports +import time +from api.utils import Utils, CustomPagination +from api.permissions import IsAllowed + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class ScreeningAPIView( generics.ListCreateAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to query Screening model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes =[IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = ScreeningSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Screening obects: + user_created_id - this is id associated with CoCoModel inherited by Screening model, + start_day - takes day part value of a search in date range, + start_month - takes month part value of a search in date range, + start_year - takes year part value of a search in date range, + end_day - takes day part value of a search in date range, + end_month - takes month part value of a search in date range, + end_year - takes year part value of a search in date range, + fields - to pass comma separated value to be returned a value for each Screening object, e.g. pass + fields value as id,user_created to get only these key-value pairs for each Screening object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + queryset = Screening.objects.get_queryset().order_by('id') + + start_day = request.POST.get('start_day') + start_month = request.POST.get('start_month') + start_year = request.POST.get('start_year') + end_day = request.POST.get('end_day') + end_month = request.POST.get('end_month') + end_year = request.POST.get('end_year') + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + uc_id = request.POST.get('user_created_id') # POST param 'user_created', default value is empty string + + if uc_id: + queryset = queryset.filter(user_created__exact=uc_id) # filters for numeric values with exact match + + # case1: all values are present + if start_day and start_month and start_year and end_day and end_month and end_year: + # params type value is string, trimmed spaces,convert to int and then make date by combining values + try: + start_date = datetime.date(int(start_year.strip()), int(start_month.strip()), int(start_day.strip())) + end_date = datetime.date(int(end_year.strip()), int(end_month.strip()), int(end_day.strip())) + queryset = queryset.filter(date__range=(start_date, end_date)) # filters values in date range + except: + utils.logMessage(self, self.post.__name__, "Date error occurred") + # case2: only start values are present + elif start_day and start_month and start_year and not end_day and not end_month and not end_year: + try: + start_date = datetime.date(int(start_year.strip()), int(start_month.strip()), int(start_day.strip())) + queryset = queryset.filter(date__gte=start_date) # filters values greater than or equal to start date + except: + utils.logMessage(self, self.post.__name__, "Start Date error occurred") + # case3: only end values are present + elif not start_day and not start_month and not start_year and end_day and end_month and end_year: + try: + end_date = datetime.date(int(end_year.strip()), int(end_month.strip()), int(end_day.strip())) + queryset = queryset.filter(date__lte=end_date) # filters values less than or equal to end date + except: + utils.logMessage(self, self.post.__name__, "End Date error occurred") + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = ScreeningSerializer(queryset, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = ScreeningSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/management/__init__.py b/api/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/management/commands/__init__.py b/api/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/management/commands/insert_views_for_permissions.py b/api/management/commands/insert_views_for_permissions.py new file mode 100644 index 0000000000..daa1ee89d6 --- /dev/null +++ b/api/management/commands/insert_views_for_permissions.py @@ -0,0 +1,82 @@ +# django imports +from django.contrib.auth.models import Group +from django.core.management.base import BaseCommand +# csv imports +import unicodecsv as csv +# app imports +from api.models import View +from activities.views import ScreeningAPIView +from geographies.views import VillageAPIView, BlockAPIView, DistrictAPIView, StateAPIView, CountryAPIView, GeoInfoView +from people.views import FarmersJsonAPIView, FarmersCsvAPIView, FarmerInfoView +from programs.views import PartnerAPIView, ProjectAPIView +from videos.views import VideoAPIView + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +# add a view in this list to insert it in database +VIEWS_LIST = { + ScreeningAPIView.__name__, + VillageAPIView.__name__, + BlockAPIView.__name__, + DistrictAPIView.__name__, + StateAPIView.__name__, + CountryAPIView.__name__, + GeoInfoView.__name__, + FarmersJsonAPIView.__name__, + FarmersCsvAPIView.__name__, + FarmerInfoView.__name__, + PartnerAPIView.__name__, + ProjectAPIView.__name__, + VideoAPIView.__name__, +} + +class CreateViewAndAddGroups(): + """ + Class to create views and add groups for View model + """ + + def createAView(self, view_class): + ''' + This function adds a view to the database + ''' + + view = View(view_name = view_class) + view.save() + return + + def addAGroupByName(self, view_class_name, group_name): + ''' + This function adds a group to a view by view name to the database + ''' + + view = View.objects.get(view_name=view_class_name) + gr = Group.objects.get(name=group_name) + view.permission_groups.add(gr) + view.save() + return + + +class Command(BaseCommand): + """ + This class is used to add management commands to insert views in the database + """ + + def handle(self, *args, **options): + prod_path = '/home/ubuntu/code/dg_git/api/management/commands/errors.csv' + local_path = '/Users/stuti/Desktop/dg/api/management/commands/errors.csv' + error_file = open(local_path, 'wb') + wrtr = csv.writer(error_file, delimiter=',', quotechar='"') + createAdd = CreateViewAndAddGroups() + + for (i, view_name) in enumerate(VIEWS_LIST): + try: + createAdd.createAView(view_name) + # createAdd.addAGroupByName(view_name, "cocoadmin") + except Exception as e: + wrtr.writerow([i, "**insertion-error**", view_name, e]) + print(e, "**insertion-error") + + error_file.close() diff --git a/api/management/log/logfile b/api/management/log/logfile new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py new file mode 100644 index 0000000000..c7cbb5e551 --- /dev/null +++ b/api/migrations/0001_initial.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0006_require_contenttypes_0002'), + ] + + operations = [ + migrations.CreateModel( + name='View', + fields=[ + ('id', models.AutoField(serialize=False, primary_key=True)), + ('view_name', models.CharField(max_length=200)), + ('permission_groups', models.ManyToManyField(to='auth.Group')), + ], + ), + ] diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000000..1e18ceb403 --- /dev/null +++ b/api/models.py @@ -0,0 +1,19 @@ +from django.db import models +from django.contrib.auth.models import Group + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class View(models.Model): + ''' + This is model for View present in the views.py. Groups represent the permitted groups allowed to access it. + ''' + id = models.AutoField(primary_key=True) + view_name = models.CharField(max_length=200) + permission_groups = models.ManyToManyField(Group) + + def __str__(self): + "Returns the view name and groups names mapped together" + return '%s, %s'%(self.view_name, self.permission_groups) diff --git a/api/permissions.py b/api/permissions.py new file mode 100644 index 0000000000..59afe9f88a --- /dev/null +++ b/api/permissions.py @@ -0,0 +1,31 @@ +# rest framework imports +from rest_framework import viewsets, generics, status, permissions +from rest_framework.response import Response +# app imports +from api.models import View +# logging imports +import logging + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +logger = logging +logger = logging.getLogger('coco_api') + +class IsAllowed(permissions.BasePermission): + """ + View-level permission to allow group-wise access to the view-based-apis. + """ + + def has_permission(self, request, view): + view = View.objects.get(view_name=view.__class__.__name__) + user_groups = request.user.groups.all() + if view.permission_groups.filter(name__in=list(user_groups)).exists(): + common_groups = view.permission_groups.filter(name__in=list(user_groups)) + logger.info("Permission granted for view: %s to user: %s of group: %s"%(view.view_name, request.user, common_groups)) + return True + else: + logger.info("Permission denied for view: %s to user: %s of groups: %s"%(view.view_name, request.user, user_groups)) + return False diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 0000000000..d865f874e9 --- /dev/null +++ b/api/serializers.py @@ -0,0 +1,31 @@ +from rest_framework import serializers +from django.contrib.auth.models import Group +from models import View + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class ViewSerializer(serializers.Serializer): + """ + Serializer class for View model + """ + + permission_group_id = serializers.CharField(source='permission_groups.id', read_only=True) + permission_group_name = serializers.CharField(source='permission_groups.name', read_only=True) + + class Meta: + model = View + fields = '__all__' + + +class GroupSerializer(serializers.Serializer): + """ + Serializer class for Group model + """ + + class Meta: + model = Group + fields = '__all__' + \ No newline at end of file diff --git a/api/utils.py b/api/utils.py new file mode 100644 index 0000000000..c3fd4a289b --- /dev/null +++ b/api/utils.py @@ -0,0 +1,99 @@ +# django imports +from django.contrib.auth.models import User +from django.utils import importlib +# python imports +import time +import logging +from collections import OrderedDict +# rest framework imports +from rest_framework import pagination, serializers +from rest_framework.response import Response + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class CustomPagination(pagination.PageNumberPagination): + """ + Pagination class to paginate queryset + """ + + page_size = 50 + page_size_query_param = 'page_size' + page_query_param = 'page' + max_page_size = 100 + + # overriding of method to get customised paginated response + def get_paginated_response(self, data): + return Response(OrderedDict([ + ('count', self.page.paginator.count), + ('next', self.get_next_link()), + ('previous', self.get_previous_link()), + ('results', data) + ])) + + +class DynamicFieldsModelSerializer(serializers.ModelSerializer): + """ + A ModelSerializer that takes an additional `fields` argument that + controls which fields should be displayed. + """ + + def __init__(self, *args, **kwargs): + # Don't pass the 'fields' arg up to the superclass + fields = kwargs.pop('fields', None) + + # Instantiate the superclass normally + super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs) + + if fields is not None: + # Drop any fields that are not specified in the `fields` argument. + allowed = set(fields) + existing = set(self.fields) + for field_name in existing - allowed: + self.fields.pop(field_name) + + +class Utils: + """ + Utility class for api + """ + + def limitQueryset(self, queryset, start_limit, end_limit): + """ + limits the total response count + """ + + if start_limit and end_limit: # case1: both are present + queryset = queryset[int(start_limit)-1:int(end_limit)] + elif start_limit: # case2: only start_limit is present + queryset = queryset[int(start_limit)-1:] + elif end_limit: # case3: only end_limit is present + queryset = queryset[:int(end_limit)] + return queryset + + def logRequest(self, request, class_instance, view_fun, processing_time, status_code ): + """ + Logs the request, username and other details + """ + + logger = logging.getLogger('coco_api') + user_obj = User.objects.get(username=request.user) + ip_addr = request.META['REMOTE_ADDR'] + method = request.method + user_id = user_obj.id + class_name = class_instance.__class__.__name__ + module_name = class_instance.__module__ + logger.info("Accessed: %s.%s.%s, user_id: %s, username: %s, ip_address: %s, method: %s, processing_time: %s seconds, status_code: %s" % ( module_name, class_name, view_fun, user_id, user_obj, ip_addr, method, processing_time, status_code)) + + def logMessage(self, class_instance, view_fun, message): + """ + Logs the message associated with class and its method + """ + + logger = logging.getLogger('coco_api') + class_name = class_instance.__class__.__name__ + module_name = class_instance.__module__ + logger.info("Log for %s.%s.%s: %s " % ( module_name, class_name, view_fun, message)) + \ No newline at end of file diff --git a/dashboard/admin.py b/dashboard/admin.py index 8c1fe56a22..9c9da2d483 100644 --- a/dashboard/admin.py +++ b/dashboard/admin.py @@ -27,6 +27,7 @@ class Meta: model = PersonMeetingAttendance exclude = () + class FarmerAttendanceInline(admin.TabularInline): model = PersonMeetingAttendance raw_id_fields = ("person",) @@ -58,6 +59,7 @@ class Meta: model = Screening exclude = () + class ScreeningAdmin(admin.ModelAdmin): filter_horizontal = ('videoes_screened',) list_display = ('id', 'date', 'screening_location', 'observation_status', 'screening_grade', 'observer') @@ -106,11 +108,11 @@ class DirectBeneficiariesAdmin(admin.ModelAdmin): list_display = ['id', 'direct_beneficiaries_category'] search_fields = ['direct_beneficiaries_category'] + class PartnerAdmin(admin.ModelAdmin): list_display = ['id', 'partner_name'] - class VideoAdmin(admin.ModelAdmin): inlines = [NonNegotiablesInline] fieldsets = [ @@ -134,6 +136,7 @@ class Media: settings.STATIC_URL + "js/qa_video.js", ) + class AnimatorAssignedVillages(admin.StackedInline): model = AnimatorAssignedVillage @@ -149,11 +152,13 @@ class PersonGroupInline(admin.TabularInline): model = PersonGroup extra = 5 + class AnimatorInline(admin.TabularInline): model = Animator extra = 5 exclude = ('assigned_villages',) + class VillageAdmin(admin.ModelAdmin): list_display = ('id', 'village_name', 'block', 'active') search_fields = ['village_name', 'block__block_name', 'block__district__state__state_name'] @@ -184,7 +189,6 @@ class Media: #} - class PersonGroupAdmin(admin.ModelAdmin): inlines = [PersonInline] list_display = ('group_name','village') @@ -201,6 +205,7 @@ class PersonAdoptPracticeInline(admin.StackedInline): model = PersonAdoptPractice extra = 3 + class PersonAdoptPracticeAdmin(admin.ModelAdmin): formfield_overrides = { models.CharField: {'widget': forms.CheckboxSelectMultiple(choices=NONNEGOTIABLE_OPTION)}, @@ -229,55 +234,69 @@ class PersonAdmin(admin.ModelAdmin): search_fields = ['person_name','village__village_name','group__group_name'] raw_id_fields = ('village','group') + class BlockAdmin(admin.ModelAdmin): list_display = ('id', 'block_name', 'district', 'active') search_fields = ['block_name', 'district__district_name', 'district__state__state_name'] + class DistrictAdmin(admin.ModelAdmin): list_display = ('id', 'district_name', 'state', 'active') search_fields = ['district_name', 'state__state_name'] + class StateAdmin(admin.ModelAdmin): list_display = ('id', 'state_name','active') search_fields = ['state_name', 'country__country_name'] + class SubCategoryAdmin(admin.ModelAdmin): list_display = ('subcategory_name', 'category') search_fields = ['subcategory_name', 'category__category_name'] + class VideoPracticeAdmin(admin.ModelAdmin): list_display = ('videopractice_name', 'subcategory') search_fields = ['videopractice_name', 'subcategory__subcategory_name'] + class PracticesAdmin(admin.ModelAdmin): list_display = ('id', 'practice_name', 'practice_sector', 'practice_subject', 'practice_subsector', 'practice_topic', 'practice_subtopic') search_fields = ['id', 'practice_name', 'practice_sector__name', 'practice_subject__name', 'practice_subsector__name', 'practice_topic__name', 'practice_subtopic__name'] + class PracticeSectorAdmin(admin.ModelAdmin): search_fields = ['name'] + class PracticeSubSectorAdmin(admin.ModelAdmin): search_fields = ['name'] + class PracticeTopicAdmin(admin.ModelAdmin): search_fields = ['name'] + class PracticeSubtopicAdmin(admin.ModelAdmin): search_fields = ['name'] + class PracticeSubjectAdmin(admin.ModelAdmin): search_fields = ['name'] + class CocoUserAdmin(admin.ModelAdmin): form = CocoUserForm list_display = ('id', 'user', 'partner','get_villages') search_fields = ['user__username'] + class QACocoUserAdmin(admin.ModelAdmin): form = QACocoUserForm list_display = ('user','partner','get_blocks') search_fields = ['user__username'] + class ProjectAdmin(admin.ModelAdmin): filter_horizontal = ('associate_partner',) list_display = ('id','project_name') @@ -299,8 +318,6 @@ def __init__(self, *args, **kwargs): self.fields['video'].queryset = Video.objects.filter(partner_id__in=(50,72),village__block__district__state_id= 6).exclude(id__in=mapped_videos) - - class BluefrogSubcategoryAdmin(admin.ModelAdmin): list_display = ['crop_id', 'crop_name', 'crop_name_telgu'] search_fields = ['crop_id', 'crop_name', 'crop_name_telgu'] @@ -331,10 +348,6 @@ def save_related(self, request, form, formsets, change): form.instance.video.tags.add(*current_instance_tag) - - - - class AP_DistrictAdmin(admin.ModelAdmin): list_display = ['id', 'district_code', 'district_name', 'user_created', 'time_created', '_district'] @@ -426,12 +439,12 @@ def _animator(self, obj): _animator.allow_tags = True _animator.short_description = "COCO-DB-Animator-ID" + class AP_AnimatorAssignedVillageAdmin(admin.ModelAdmin): list_display = ['id', 'animator', 'village', 'user_created', 'time_created'] search_fields = ['animator'] - class JSLPS_AnimatorAdmin(admin.ModelAdmin): list_display = ('id','animator_code', 'user_created', 'time_created', '_animator', 'activity') @@ -525,6 +538,7 @@ def _village(self, obj): def has_add_permission(self, request): return False + class JSLPS_VideoAdmin(admin.ModelAdmin): list_display = ['id', 'vc', 'title', 'user_created', 'time_created', '_video', 'activity'] @@ -564,7 +578,6 @@ def has_add_permission(self, request): return False - class JSLPS_ScreeningAdmin(admin.ModelAdmin): list_display = ['id', 'screenig_code', 'activity', 'screening', '_village', '_dg_screening_id', @@ -597,4 +610,7 @@ def has_add_permission(self, request): return False +class ViewAdmin(admin.ModelAdmin): + list_display = ('id', 'view_name', 'permission_groups') + search_fields = ['id', 'view_name'] diff --git a/dg/base_settings.py b/dg/base_settings.py index 9833fc29df..d18c57e479 100644 --- a/dg/base_settings.py +++ b/dg/base_settings.py @@ -149,6 +149,7 @@ 'people', 'videos', 'activities', + 'api', #'debug_toolbar', 'output', 'django.contrib.humanize', @@ -185,8 +186,12 @@ 'mrppayment', 'smart_selects', 'loop_ivr', - 'dataexport' + 'dataexport', + 'rest_framework', # 3rd Party + 'django_extensions', + #drf TokenAuthentication + 'rest_framework.authtoken', ) # Store these package names here as they may change in the future since @@ -241,6 +246,12 @@ 'class':'logging.StreamHandler', 'formatter': 'standard' }, + 'api_access_log': { + 'level':'DEBUG', + 'class':'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(PROJECT_PATH, '../api/management/log/logfile'), + 'formatter': 'standard', + }, }, 'loggers': { @@ -259,6 +270,10 @@ 'geographies': { 'handlers': ['ap_migration_log'], 'level' : 'INFO', + }, + 'coco_api':{ + 'handlers': ['api_access_log'], + 'level': 'INFO', } } } @@ -270,3 +285,13 @@ VIDEOS_PAGE = ('%s%s')%(WEBSITE_DOMAIN, 'videos/') LOOP_APP_PAGE = ('%s')%('https://play.google.com/store/apps/details?id=loop.org.digitalgreen.loop') TRAINING_APP_PAGE = ('%s')%('https://play.google.com/store/apps/details?id=org.digitalgreen.trainingapp') + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], +} \ No newline at end of file diff --git a/dg/coco_admin.py b/dg/coco_admin.py index 742253dadc..b81e199679 100644 --- a/dg/coco_admin.py +++ b/dg/coco_admin.py @@ -1,4 +1,5 @@ from django.contrib.admin.sites import AdminSite +# admin from django.contrib.auth.admin import Group, GroupAdmin, User, UserAdmin from dashboard.admin import AnimatorAdmin from dashboard.admin import AnimatorAssignedVillageAdmin @@ -24,6 +25,9 @@ from dashboard.admin import ProjectAdmin from dashboard.admin import TagAdmin from dashboard.admin import PartnerAdmin +from dashboard.admin import ViewAdmin +# models +from api.models import View from activities.models import PersonAdoptPractice, Screening from coco.models import CocoUser from geographies.models import Block, Country, District, State, Village @@ -55,10 +59,10 @@ def has_permission(self, request): coco_admin.index_template = 'social_website/index.html' coco_admin.login_template = 'social_website/login.html' coco_admin.logout_template = 'social_website/home.html' - +# default admin coco_admin.register(User, UserAdmin) coco_admin.register(Group, GroupAdmin) - +# custom admin coco_admin.register(AnimatorAssignedVillage, AnimatorAssignedVillageAdmin) coco_admin.register(Video, VideoAdmin) coco_admin.register(Country) @@ -86,3 +90,4 @@ def has_permission(self, request): coco_admin.register(PracticeSubtopic, PracticeSubtopicAdmin) coco_admin.register(PracticeSubject, PracticeSubjectAdmin) coco_admin.register(CocoUser, CocoUserAdmin) +coco_admin.register(View, ViewAdmin) diff --git a/dg/media/Output/JS/adoption_module.js b/dg/media/Output/JS/adoption_module.js index 356c533733..1d0d2e0d7a 100644 --- a/dg/media/Output/JS/adoption_module.js +++ b/dg/media/Output/JS/adoption_module.js @@ -1,5 +1,9 @@ -google.load("visualization", "1", { packages: ["controls"] }); -google.setOnLoadCallback(drawCharts); +// google.load("visualization", "1", { packages: ["controls"] }); +// google.setOnLoadCallback(drawCharts); +google.charts.load("current", "1", { + packages: ["controls"] +}); +google.charts.setOnLoadCallback(drawCharts); function drawCharts() { $.getJSON('/coco/analytics/adoption_geog_pie_data/' + search_params, function(json) { geog_pie(json); }); diff --git a/dg/media/Output/JS/screening_module.js b/dg/media/Output/JS/screening_module.js index db031ec122..558f121417 100644 --- a/dg/media/Output/JS/screening_module.js +++ b/dg/media/Output/JS/screening_module.js @@ -1,5 +1,10 @@ -google.load("visualization", "1", { packages: ["controls"] }); -google.setOnLoadCallback(drawCharts); +// google.load("visualization", "1", { packages: ["controls"] }); +// google.setOnLoadCallback(drawCharts); + +google.charts.load("current", "1", { + packages: ["controls"] +}); +google.charts.setOnLoadCallback(drawCharts); var geog_pie_chart; var geog_pie_chart_data; diff --git a/dg/media/Output/JS/video_module.js b/dg/media/Output/JS/video_module.js index d51cc14cbb..f8a30c462e 100644 --- a/dg/media/Output/JS/video_module.js +++ b/dg/media/Output/JS/video_module.js @@ -1,5 +1,10 @@ -google.load("visualization", "1", { packages: ["controls"] }); -google.setOnLoadCallback(drawCharts); +// google.load("visualization", "1", { packages: ["controls"] }); +// google.setOnLoadCallback(drawCharts); + +google.charts.load("current", "1", { + packages: ["controls"] +}); +google.charts.setOnLoadCallback(drawCharts); var geog_pie_chart; var geog_pie_chart_data; @@ -49,8 +54,6 @@ function remove_loader(div_id) { function monthwise_column(json) { if (json.length > 1) { - - var monthwise_column_chart_data = google.visualization.arrayToDataTable(json, false); var options = jQuery.extend(true, {}, column_options); options['chartArea'] = { left: 70, top: 20, width: "80%", height: "80%" }; diff --git a/dg/templates/output/adoption_module.html b/dg/templates/output/adoption_module.html index ab90d83c75..fca4e482c9 100644 --- a/dg/templates/output/adoption_module.html +++ b/dg/templates/output/adoption_module.html @@ -352,7 +352,8 @@ - + + {% endblock contentbody %} \ No newline at end of file diff --git a/dg/templates/output/overview_module.html b/dg/templates/output/overview_module.html index 948e057d46..9d44040937 100644 --- a/dg/templates/output/overview_module.html +++ b/dg/templates/output/overview_module.html @@ -251,7 +251,8 @@ - + + {% endblock contentbody %} diff --git a/dg/templates/output/screening_module.html b/dg/templates/output/screening_module.html index ea51a83fad..6abc7e1952 100644 --- a/dg/templates/output/screening_module.html +++ b/dg/templates/output/screening_module.html @@ -254,7 +254,8 @@ - + + {% endblock contentbody %} \ No newline at end of file diff --git a/dg/templates/output/video_module.html b/dg/templates/output/video_module.html index 23bbe1865b..70765154b7 100644 --- a/dg/templates/output/video_module.html +++ b/dg/templates/output/video_module.html @@ -181,7 +181,8 @@ - + + {% endblock contentbody %} \ No newline at end of file diff --git a/dg/urls.py b/dg/urls.py index 30a9053e3d..9548c49451 100644 --- a/dg/urls.py +++ b/dg/urls.py @@ -20,6 +20,10 @@ import ivr.urls import training.urls import videos.urls +import people.urls +import activities.urls +import geographies.urls +import programs.urls from django.contrib import admin admin.autodiscover() @@ -65,6 +69,13 @@ # loop_ivr_admin.login_template = 'social_website/login.html' # loop_ivr_admin.logout_template = 'social_website/home.html' +from django.conf.urls import url, include +from django.contrib.auth.models import User +from rest_framework import routers, serializers, viewsets + +#drf token authentication +from rest_framework.authtoken import views + urlpatterns = patterns('', (r'^', include(social_website.urls)), #(r'^', include(website_archive_urls)), @@ -81,6 +92,17 @@ (r'^qacoco/', include(qacoco.urls)), (r'^dimagi/', include(dimagi.urls)), (r'^videos/', include(videos.urls)), + + + # coco api changes start here + (r'^api-token-auth', views.obtain_auth_token), + (r'^farmer/', include('people.urls', namespace='farmer')), + (r'^geo/', include('geographies.urls', namespace='geographies')), + (r'^activities/', include('activities.urls', namespace='activities')), + (r'^programs/', include('programs.urls', namespace='programs')), + # coco api changes end here + + # ivrsadmin/logout/ should be above admin/ URL url(r'^ivrsadmin/logout/?$', 'django.contrib.auth.views.logout', {'next_page': '/ivrsadmin'}), (r'^ivrsadmin/', include(ivr_admin.urls)), @@ -140,8 +162,10 @@ #(r'^agri/', include(videokheti.urls)), #AJAX for Feedback #url(r'^feedbacksubmit_json$', 'dg.feedback_view.ajax'), + + + + + ) -# Static files serving locally -if settings.DEBUG: - urlpatterns += staticfiles_urlpatterns() diff --git a/geographies/apps.py b/geographies/apps.py new file mode 100644 index 0000000000..dd836c6747 --- /dev/null +++ b/geographies/apps.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals +from django.apps import AppConfig + +class GeoConfig(AppConfig): + name = 'geo' \ No newline at end of file diff --git a/geographies/models.py b/geographies/models.py index 6c1a3d456c..c521086db7 100644 --- a/geographies/models.py +++ b/geographies/models.py @@ -1,7 +1,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models.signals import pre_delete, post_save - from coco.base_models import CocoModel, ACTIVITY_CHOICES from coco.data_log import delete_log, save_log from farmerbook.managers import VillageFarmerbookManager @@ -73,6 +72,7 @@ def clean(self): post_save.connect(enter_to_log, sender=District) pre_delete.connect(enter_to_log, sender=District) + class Block(CocoModel): id = models.AutoField(primary_key=True) old_coco_id = models.BigIntegerField(editable=False, null=True) @@ -111,6 +111,7 @@ def get_partner(self): def __unicode__(self): return self.village_name + post_save.connect(save_log, sender = Village) pre_delete.connect(delete_log, sender = Village) diff --git a/geographies/serializers.py b/geographies/serializers.py new file mode 100644 index 0000000000..59f811318e --- /dev/null +++ b/geographies/serializers.py @@ -0,0 +1,83 @@ +from rest_framework import serializers +from geographies.models import Country, State, District, Block, Village +from api.utils import DynamicFieldsModelSerializer + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class CountrySerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for Country model + """ + + class Meta: + model = Country + fields = '__all__' + + +class StateSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for State model + """ + + country_id = serializers.IntegerField(source='country.id', read_only=True) + country_name = serializers.CharField(source='country.country_name', read_only=True) + + class Meta: + model = State + fields = '__all__' + + +class DistrictSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for District model + """ + + country_id = serializers.IntegerField(source='state.country.id', read_only=True) + country_name = serializers.CharField(source='state.country.country_name', read_only=True) + state_id = serializers.IntegerField(source='state.id', read_only=True) + state_name = serializers.CharField(source='state.state_name', read_only=True) + + class Meta: + model = District + fields = '__all__' + + +class BlockSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for Block model + """ + + country_id = serializers.IntegerField(source='district.state.country.id', read_only=True) + country_name = serializers.CharField(source='district.state.country.country_name', read_only=True) + state_id = serializers.IntegerField(source='district.state.id', read_only=True) + state_name = serializers.CharField(source='district.state.state_name', read_only=True) + district_id = serializers.IntegerField(source='district.id', read_only=True) + district_name = serializers.CharField(source='district.district_name', read_only=True) + + class Meta: + model = Block + fields = '__all__' + + +class VillageSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for Village model + """ + + country_id = serializers.IntegerField(source='block.district.state.country.id', read_only=True) + country_name = serializers.CharField(source='block.district.state.country.country_name', read_only=True) + state_id = serializers.IntegerField(source='block.district.state.id', read_only=True) + state_name = serializers.CharField(source='block.district.state.state_name', read_only=True) + district_id = serializers.IntegerField(source='block.district.id', read_only=True) + district_name = serializers.CharField(source='block.district.district_name', read_only=True) + block_id = serializers.IntegerField(source='block.id', read_only=True) + block_name = serializers.CharField(source='block.block_name', read_only=True) + + class Meta: + model = Village + fields = '__all__' + + \ No newline at end of file diff --git a/geographies/urls.py b/geographies/urls.py new file mode 100644 index 0000000000..682c25cebf --- /dev/null +++ b/geographies/urls.py @@ -0,0 +1,16 @@ +from django.conf.urls import url, include +from geographies import views + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +urlpatterns=[ + url(r'^api/info', views.GeoInfoView.as_view(), name='info'), + url(r'^api/village', views.VillageAPIView.as_view(), name='village'), + url(r'^api/block', views.BlockAPIView.as_view(), name='block'), + url(r'^api/district', views.DistrictAPIView.as_view(), name='district'), + url(r'^api/state', views.StateAPIView.as_view(), name='state'), + url(r'^api/country', views.CountryAPIView.as_view(), name='country'), +] diff --git a/geographies/views.py b/geographies/views.py new file mode 100644 index 0000000000..d8169bf64e --- /dev/null +++ b/geographies/views.py @@ -0,0 +1,410 @@ +# django imports +from django.shortcuts import render +# rest framework imports +from rest_framework import viewsets, generics +from rest_framework import permissions +from rest_framework.response import Response +from rest_framework import status +# rest framework TokenAuthentication imports +from rest_framework.authentication import SessionAuthentication, BasicAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated +# pagination and permissions +import time +from api.utils import Utils, CustomPagination +from api.permissions import IsAllowed +# app imports +from geographies.serializers import * +from geographies.models import * + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class GeoInfoView(generics.ListAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to provide default message in JSON format. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes =[IsAuthenticated and IsAllowed] + + # GET request + def get(self, request, *args, **kwargs): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request): + # dictionary results as JSON format message + return Response({"message":"Welcome to COCO APIs", "base_url":"/geo/api/", + "url_list":["/geo/api/village", + "/geo/api/block", + "/geo/api/district", + "/geo/api/state", + "/geo/api/country"]}) + + +class VillageAPIView(generics.ListAPIView): + ''' + coco_api class-based view to query Village model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes =[IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = VillageSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Village obects: + 1.) id - to find village by id + 2.) fields - to pass comma separated value to be returned a value for each Village object, e.g. pass + fields value as id,village_name to get only these key-value pairs for each Village object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + village_id = self.request.POST.get('id', 0) # POST param 'id' + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + + if village_id: + queryset = Village.objects.filter(id__exact=village_id) # to search by id + else: + queryset = Village.objects.get_queryset().order_by('id') # basic query to be filtered later in this method + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = VillageSerializer(queryset, fields=fields_values ,many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = VillageSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response + + +class BlockAPIView(generics.ListAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to query Block model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes =[IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = BlockSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Block obects: + 1.) id - to find block by id + 2.) fields - to pass comma separated value to be returned a value for each Block object, e.g. pass + fields value as id,block_name to get only these key-value pairs for each Block object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + block_id = self.request.POST.get('id', 0) # POST param 'id' + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + + if block_id: + queryset = Block.objects.filter(id__exact=block_id) # to search by id + else: + queryset = Block.objects.get_queryset().order_by('id') # basic query to be filtered later in this method + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = BlockSerializer(queryset, fields=fields_values ,many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = BlockSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response + + +class DistrictAPIView(generics.ListAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to query District model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes =[IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = DistrictSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on District obects: + 1.) id - to find district by id + 2.) fields - to pass comma separated value to be returned a value for each District object, e.g. pass + fields value as id,district_name to get only these key-value pairs for each District object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + district_id = self.request.POST.get('id', 0) # POST param 'id' + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + + if district_id: + queryset = District.objects.filter(id__exact=district_id) # to search by id + else: + queryset = District.objects.get_queryset().order_by('id') # basic query to be filtered later in this method + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = DistrictSerializer(queryset, fields=fields_values ,many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = DistrictSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response + + +class StateAPIView(generics.ListAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to query State model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = StateSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on State obects: + 1.) id - to find state by id + 2.) fields - to pass comma separated value to be returned a value for each State object, e.g. pass + fields value as id,state_name to get only these key-value pairs for each State object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + state_id = self.request.POST.get('id', 0) # POST param 'id' + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + + if state_id: + queryset = State.objects.filter(id__exact=state_id) # to search by id + else: + queryset = State.objects.get_queryset().order_by('id') # basic query to be filtered later in this method + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = StateSerializer(queryset, fields=fields_values ,many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = StateSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response + + +class CountryAPIView(generics.ListAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to query Country model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = CountrySerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Country obects: + 1.) id - to find country by id + 2.) fields - to pass comma separated value to be returned a value for each Country object, e.g. pass + fields value as id,country_name to get only these key-value pairs for each Country object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + country_id = self.request.POST.get('id', 0) # POST param 'id' + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + + if country_id: + queryset = Country.objects.filter(id__exact=country_id) # to search by id + else: + queryset = Country.objects.get_queryset().order_by('id') # basic query to be filtered later in this method + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = CountrySerializer(queryset, fields=fields_values ,many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = CountrySerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response diff --git a/loop/management/commands/Outliers.py b/loop/management/commands/Outliers.py index 4de29c1a61..07184b5e0e 100644 --- a/loop/management/commands/Outliers.py +++ b/loop/management/commands/Outliers.py @@ -100,4 +100,6 @@ def file_creator_per_case(self, from_to_date, case=''): create_xlsx(workbook, aggregator_wise_outliers, table_properties, table_position_to_start, worksheet_name) - return file_to_send \ No newline at end of file + return file_to_send + + \ No newline at end of file diff --git a/output/database/utility.py b/output/database/utility.py index 5d2f2a3423..004015549f 100644 --- a/output/database/utility.py +++ b/output/database/utility.py @@ -10,6 +10,8 @@ def construct_query(var, context_dict): #This abstracts away sql part to return everything by cursor.fetchall() #which is a tuple of tuples containing row-values def run_query_raw(query_string, *query_args): + # change query to lower case + query_string = query_string.lower() if(not query_string): return () cursor = connection.cursor() @@ -18,6 +20,8 @@ def run_query_raw(query_string, *query_args): #This generates a list of dictionaries of key=column_header_name, value = row_value def run_query(query_string, *query_args): + # change query to lower case + query_string = query_string.lower() if(not query_string): return [] return_list = [] @@ -33,6 +37,8 @@ def run_query(query_string, *query_args): #{ dict_key : (tuple of remaing columns), ...} #dict_key should be the first column in returned value. def run_query_dict(query_string, dict_key, *query_args): + # change query to lower case + query_string = query_string.lower() if(not query_string): return {} return_list = {} @@ -51,6 +57,8 @@ def run_query_dict(query_string, dict_key, *query_args): #{ dict_key : [list of remaing columns], ...} #dict_key should be the first column in returned value. def run_query_dict_list(query_string, dict_key, *query_args): + # change query to lower case + query_string = query_string.lower() if(not query_string): return {} return_list = {} diff --git a/output/views/common.py b/output/views/common.py index 6c39d01c40..fde0ec81bf 100644 --- a/output/views/common.py +++ b/output/views/common.py @@ -483,22 +483,22 @@ def __cmp__(self,date1): def month_bar_data(sqlFunc, setting_from_date, setting_to_date, **args): rs = run_query(sqlFunc(**args)); if rs: - min = int(rs[0]['YEAR']) - max = int(rs[-1]['YEAR'])+1 + min = int(rs[0]['year']) + max = int(rs[-1]['year'])+1 dic = {} for y in range(min,max): dic[y] = {} for item in rs: - if int(item['YEAR'])>y: + if int(item['year'])>y: break - if int(item['YEAR'])==y: - dic[y][int(item['MONTH'])] = item['count'] + if int(item['year'])==y: + dic[y][int(item['month'])] = item['count'] else: return HttpResponse(json.dumps([['Name','Value']])); if(not(setting_from_date and setting_to_date)): - setting_from_date = str(rs[0]['YEAR'])+'-'+str(rs[0]['MONTH'])+'-01' + setting_from_date = str(rs[0]['year'])+'-'+str(rs[0]['month'])+'-01' setting_to_date = (datetime.datetime.utcnow() - datetime.timedelta(1)).strftime('%Y-%m-%d') setting_from_date = MyDate(* [int(x) for x in reversed(setting_from_date.split('-')[:2])]) setting_to_date = MyDate(* [int(x) for x in reversed(setting_to_date.split('-')[:2])]) diff --git a/people/models.py b/people/models.py index 5288a3d79e..0b27cf4372 100644 --- a/people/models.py +++ b/people/models.py @@ -1,6 +1,5 @@ from django.db import models from django.db.models.signals import pre_delete, post_save - from coco.data_log import delete_log, save_log from coco.base_models import CocoModel, DAY_CHOICES, GENDER_CHOICES, TYPE_OF_ROLE from farmerbook.managers import FarmerbookManager @@ -9,6 +8,7 @@ from programs.models import Partner from training.log.training_log import enter_to_log + class Animator(CocoModel): id = models.AutoField(primary_key = True) old_coco_id = models.BigIntegerField(editable=False, null=True) @@ -48,6 +48,7 @@ class AnimatorAssignedVillage(CocoModel): village = models.ForeignKey(Village) start_date = models.DateField(null=True, blank=True) + class PersonGroup(CocoModel): id = models.AutoField(primary_key=True) old_coco_id = models.BigIntegerField(editable=False, null=True) @@ -64,6 +65,7 @@ def __unicode__(self): post_save.connect(save_log, sender=PersonGroup) pre_delete.connect(delete_log, sender=PersonGroup) + class Person(CocoModel): id = models.AutoField(primary_key=True) old_coco_id = models.BigIntegerField(editable=False, null=True) @@ -94,6 +96,7 @@ def __unicode__(self): post_save.connect(save_log, sender=Person) pre_delete.connect(delete_log, sender=Person) + class JSLPS_Animator(CocoModel): id = models.AutoField(primary_key=True) animator_code = models.CharField(max_length=100) @@ -108,6 +111,7 @@ class Meta: def __unicode__(self): return self.animator_code + class JSLPS_AnimatorAssignedVillage(CocoModel): id = models.AutoField(primary_key=True) animator = models.ForeignKey(JSLPS_Animator) @@ -118,6 +122,7 @@ class Meta: verbose_name = "JSLPS AnimatorAssignedVillage" verbose_name_plural = "JSLPS AnimatorAssignedVillage" + class JSLPS_Persongroup(CocoModel): id = models.AutoField(primary_key=True) group_code = models.CharField(max_length=100) @@ -128,6 +133,7 @@ class Meta: verbose_name = "JSLPS Persongroup" verbose_name_plural = "JSLPS Persongroup" + class JSLPS_Person(CocoModel): id = models.AutoField(primary_key=True) person_code = models.CharField(max_length=100) @@ -153,6 +159,7 @@ class Meta: def __unicode__(self): return self.animator_code + class AP_AnimatorAssignedVillage(CocoModel): animator = models.ForeignKey(AP_Animator) village = models.ForeignKey(AP_Village) @@ -162,7 +169,6 @@ class Meta: verbose_name_plural = "AP AnimatorAssignedVillage" - class AP_Person(CocoModel): person_code = models.CharField(max_length=100) person = models.ForeignKey(Person, null=True, blank=True) @@ -172,4 +178,3 @@ class Meta: verbose_name = "AP Person" verbose_name_plural = "AP Person" - diff --git a/people/serializers.py b/people/serializers.py new file mode 100644 index 0000000000..69f31f1d1b --- /dev/null +++ b/people/serializers.py @@ -0,0 +1,35 @@ +from people.models import Person +from rest_framework import serializers +from geographies.models import Village, Block, District, State, Country +from geographies.serializers import VillageSerializer +from api.utils import DynamicFieldsModelSerializer + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class FarmerSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for Village model + """ + + village_id = serializers.CharField(source='village.id', read_only=True) + village = serializers.CharField(source='village.village_name', read_only=True) + + block_id = serializers.IntegerField(source='village.block.id', read_only=True) + block_name = serializers.CharField(source='village.block.block_name', read_only=True) + + district_id = serializers.IntegerField(source='village.block.district.id', read_only=True) + district_name = serializers.CharField(source='village.block.district.district_name', read_only=True) + + state_id = serializers.IntegerField(source='village.block.district.state.id', read_only=True) + state_name = serializers.CharField(source='village.block.district.state.state_name', read_only=True) + + country_id = serializers.IntegerField(source='village.block.district.state.country.id', read_only=True) + country_name = serializers.CharField(source='village.block.district.state.country.country_name', read_only=True) + + class Meta: + model = Person + fields = '__all__' + diff --git a/people/urls.py b/people/urls.py new file mode 100644 index 0000000000..c371eb81a0 --- /dev/null +++ b/people/urls.py @@ -0,0 +1,17 @@ +# django imports +from django.conf.urls import url, include +# app imports +from people import views + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +urlpatterns=[ + url(r'^api/info', views.FarmerInfoView.as_view(), name='info'), + url(r'^api/farmers', views.FarmersJsonAPIView.as_view({'post':'getAllFarmers'}), name='getAllFarmers'), + url(r'^api/match/phone', views.FarmersJsonAPIView.as_view({'post':'getPhoneMatchedResults'}), name='getPhoneMatchedResults'), + url(r'^api/csv', views.FarmersCsvAPIView.as_view({'post':'post'})), # r'^$' is used for regex of exact match as mentioned +] + diff --git a/people/views.py b/people/views.py new file mode 100644 index 0000000000..3dbe1263c4 --- /dev/null +++ b/people/views.py @@ -0,0 +1,267 @@ +# default imports +from django.shortcuts import render +# rest framework imports +from rest_framework import viewsets, generics +from rest_framework import permissions, filters +from rest_framework.response import Response +# app import +from people.serializers import FarmerSerializer +from people.models import * +from geographies.models import * +# django-rest-framework TokenAuthentication imports +from rest_framework.authentication import SessionAuthentication, BasicAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated +# CSV View imports +from rest_framework.renderers import JSONRenderer +from rest_framework.views import APIView +from rest_framework.settings import api_settings +from rest_framework_csv import renderers as r +# logging, pagination and permissions +import time +from api.utils import Utils, CustomPagination +from api.permissions import IsAllowed + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class FarmerInfoView(generics.ListCreateAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to provide default message in JSON format. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated and IsAllowed] + + # POST request + def post(self, request, *args, **kwargs): + # dictionary results as JSON format message + return Response({"message":"Welcome to COCO APIs", "base_url":"/farmer/api", + "url_list":["/farmer/api/farmers", "/farmer/api/csv"]}) + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed."}) + + +class FarmersJsonAPIView(viewsets.GenericViewSet): + ''' + This view is specifically written for coco api access. + This class-based view is to query Person model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = FarmerSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def getAllFarmers(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Person obects: + 1.) country_id - to find people belonging to a country + 2.) phoneNumberExists - to find people with valid phone numbers + 3.) fields - to pass comma separated value to be returned a value for each Person object, e.g. pass + fields value as id,person_name to get only these key-value pairs for each Person object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + country_id = self.request.POST.get('country_id', 0) # POST param 'country_id', default value is 0 + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + phoneNumberExists = request.POST.get('phoneNumberExists','') # POST param 'filter_phone_no', default value is empty string + + try: + # fetches country id from database model Country to verify param value + got_country_id = Country.objects.get(id=country_id).id + # if the country id is found same as param value entered, filters Person model + queryset = Person.objects.all().filter(village__block__district__state__country__exact=got_country_id).order_by('id') + except: + # in case of failure of above try statement, all Person objects are retrieved + queryset = Person.objects.all().order_by('id') + + # phone number exists or not + if phoneNumberExists.lower() in ["true","t","yes","y"]: + queryset = queryset.filter(phone_no__isnull=False).exclude(phone_no__in=['']) + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = FarmerSerializer(queryset, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = FarmerSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response + + # POST request + def getPhoneMatchedResults(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Person obects: + 1.) country_id - to find people belonging to a country + 2.) phoneNumberExists - to find people with valid phone numbers + 3.) phone_numbers - to pass comma separated value to search for exact phone numbers + 4.) fields - to pass comma separated value to be returned a value for each Person object, e.g. pass + fields value as id,person_name to get only these key-value pairs for each Person object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + queryset = Person.objects.all().order_by('id') + + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + phone_numbers = request.POST.get('phoneNumbers', '') # POST param 'fields', default value is empty string + phoneNumberExists = request.POST.get('phoneNumberExists','') # POST param 'phoneNumberExists', default value is empty string + + # phone number exists or not + if phoneNumberExists.lower() in ["true","t","yes","y"]: + queryset = queryset.filter(phone_no__isnull=False).exclude(phone_no__in=['']) + + # phone number matches + if phone_numbers: + ph_no_values = [ph.strip() for ph in phone_numbers.split(",")] + queryset = queryset.filter(phone_no__in=ph_no_values) + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = FarmerSerializer(queryset, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = FarmerSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response + + +class FarmersCsvAPIView(viewsets.GenericViewSet): + ''' + This view is specifically written for coco api access. + This class-based view is to query Person model and provide CSV response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # CSV Renderer class setting to return response of this view as CSV + renderer_classes = (r.CSVRenderer, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES) + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated and IsAllowed] + serializer_class = FarmerSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Person obects: + 1.) country_id - to find people belonging to a country + 2.) phoneNumberExists - to find people with valid phone numbers + 3.) fields - to pass comma separated value to be returned a value for each Person object, e.g. pass + fields value as id,person_name to get only these key-value pairs for each Person object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + country_id = self.request.POST.get('country_id', 0) # POST param 'country_id', default value is 0 + fields_values = request.POST.get('fields', '') # POST param 'fields', default value is empty string + phoneNumberExists = request.POST.get('phoneNumberExists','') # POST param 'filter_phone_no', default value is empty string + start_limit = request.POST.get('start_limit','') # POST param 'filter_phone_no', default value is empty string + end_limit = request.POST.get('end_limit','') # POST param 'filter_phone_no', default value is empty string + + try: + # fetches country id from database model Country to verify param value + got_country_id = Country.objects.get(id=country_id).id + # if the country id is found same as param value entered, filters Person model + queryset = Person.objects.all().filter(village__block__district__state__country__exact=got_country_id).order_by('id') + except: + # in case of failure of above try statement, all Person objects are retrieved + queryset = Person.objects.all().order_by('id') + + # phone number exists or not + if phoneNumberExists.lower() in ["true","t","yes","y"]: + queryset = queryset.filter(phone_no__isnull=False).exclude(phone_no__in=['']) + + queryset = utils.limitQueryset(queryset, start_limit, end_limit) + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = FarmerSerializer(queryset, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = FarmerSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response diff --git a/programs/models.py b/programs/models.py index 7b1665a352..17a625fee8 100644 --- a/programs/models.py +++ b/programs/models.py @@ -3,7 +3,6 @@ from django.db.models.signals import post_save, pre_delete from training.log.training_log import enter_to_log - class Partner(CocoModel): id = models.AutoField(primary_key=True) old_coco_id = models.BigIntegerField(editable=False, null=True, db_index=True) @@ -17,6 +16,7 @@ def __unicode__(self): post_save.connect(enter_to_log,sender=Partner) pre_delete.connect(enter_to_log,sender=Partner) + class Project(CocoModel): id = models.AutoField(primary_key=True) project_name = models.CharField(max_length=100, unique=True, help_text="Short Name of Project. It will be display on Analytics") @@ -27,3 +27,4 @@ class Project(CocoModel): def __unicode__(self): return self.project_name + diff --git a/programs/serializers.py b/programs/serializers.py new file mode 100644 index 0000000000..c8e620cfd0 --- /dev/null +++ b/programs/serializers.py @@ -0,0 +1,24 @@ +from programs.models import * +from rest_framework import serializers +from api.utils import DynamicFieldsModelSerializer + +class PartnerSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for Partner model + """ + + class Meta: + model = Partner + fields = '__all__' + + +class ProjectSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for Project model + """ + + class Meta: + model = Project + fields = '__all__' + + \ No newline at end of file diff --git a/programs/urls.py b/programs/urls.py new file mode 100644 index 0000000000..1dfca6427b --- /dev/null +++ b/programs/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url, include +from programs.views import * + +urlpatterns = [ + + url(r'^api/partner', PartnerAPIView.as_view(), name='partner'), + url(r'^api/project', ProjectAPIView.as_view(), name='project'), + +] + diff --git a/programs/views.py b/programs/views.py new file mode 100644 index 0000000000..be515b9424 --- /dev/null +++ b/programs/views.py @@ -0,0 +1,168 @@ +# default imports +from django.shortcuts import render +# rest_framework imports +from rest_framework import generics, viewsets +from rest_framework.response import Response +# app imports +from programs.models import * +from programs.serializers import * +# authentication imports +from rest_framework.authentication import TokenAuthentication +from rest_framework.permissions import IsAuthenticated +# pagination and permissions +import time +from api.utils import Utils, CustomPagination +from api.permissions import IsAllowed + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class PartnerAPIView(generics.ListCreateAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to query Partner model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = PartnerSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Partner obects: + 1.) id - to find partner by id + 2.) fields - to pass comma separated value to be returned a value for each Partner object, e.g. pass + fields value as id,partner_name to get only these key-value pairs for each Partner object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + fields_values = request.POST.get('fields', '') # POST param 'fields' + partner_id = self.request.POST.get('id', 0) # POST param 'id' + + serializer_class = PartnerSerializer + + if partner_id: # checks if partner id is present + queryset = Partner.objects.filter(id__exact=partner_id) + else: + queryset = Partner.objects.all().order_by('id') + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = serializer_class(queryset, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = serializer_class(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response + + +class ProjectAPIView(generics.ListCreateAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to query Project model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = ProjectSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Project obects: + 1.) id - to find project by id + 2.) fields - to pass comma separated value to be returned a value for each Project object, e.g. pass + fields value as id,project_name to get only these key-value pairs for each Project object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + fields_values = request.POST.get('fields', '') # POST param 'fields' + project_id = self.request.POST.get('id', 0) # POST param 'id' + + serializer_class = ProjectSerializer + + if project_id: # checks if project id is present + queryset = Project.objects.filter(id__exact=project_id) + else: + queryset = Project.objects.all().order_by('id') + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = serializer_class(queryset, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = serializer_class(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response + \ No newline at end of file diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ba7ac40c33..bfad13cc1b 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,4 +1,5 @@ appdirs==1.4.3 +appnope==0.1.0 backports.shutil-get-terminal-size==1.0.0 BeautifulSoup==3.2.1 beautifulsoup4==4.5.3 @@ -17,7 +18,10 @@ django-contrib-comments==1.8.0 django-cors-headers==2.1.0 django-debug-toolbar==0.8.5 django-easy-select2==1.3.4 +django-extensions==2.2.9 django-tastypie==0.12.2 +djangorestframework==3.6.4 +djangorestframework-csv==2.1.0 enum34==1.1.6 et-xmlfile==1.0.1 filebrowser-safe==0.4.6 @@ -34,7 +38,9 @@ ipython-genutils==0.2.0 isodate==0.5.4 jdcal==1.3 Mezzanine==4.1.0 -MySQL-python==1.2.3 +mysql-connector==2.2.9 +#mysqlclient==1.4.6 +mysql-python newrelic==2.82.0.62 numpy==1.12.0 oauth2==1.5.211 @@ -72,6 +78,7 @@ six==1.10.0 tinycss2==0.6.1 traitlets==4.3.2 twython==3.0.0 +typing==3.7.4.1 tzlocal==1.3 unicodecsv==0.9.4 uritemplate==3.0.0 diff --git a/videos/models.py b/videos/models.py index b7e2d7531f..33e7fc7bb8 100644 --- a/videos/models.py +++ b/videos/models.py @@ -238,6 +238,7 @@ def location(self): post_save.connect(save_log, sender=Video) pre_delete.connect(delete_log, sender=Video) + class NonNegotiable(CocoModel): id = models.AutoField(primary_key=True) video = models.ForeignKey(Video) diff --git a/videos/serializers.py b/videos/serializers.py new file mode 100644 index 0000000000..8855efa98d --- /dev/null +++ b/videos/serializers.py @@ -0,0 +1,18 @@ +from rest_framework import serializers +from videos.models import * +from api.utils import DynamicFieldsModelSerializer + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class VideoSerializer(DynamicFieldsModelSerializer): + """ + Serializer class inherited from DynamicFieldsModelSerializer for Video model + """ + + class Meta: + model = Video + fields = '__all__' + diff --git a/videos/urls.py b/videos/urls.py index 441f255063..ddf3ce8b14 100644 --- a/videos/urls.py +++ b/videos/urls.py @@ -1,16 +1,15 @@ +# django imports from django.conf.urls import include, patterns, url from django.views.generic import TemplateView from django.views.generic.base import RedirectView - -from social_website.views import collection_view, video_view, search_view, collection_edit_view, collection_add_view, partner_view -from social_website.urls import DirectTemplateView - +# app imports from output.views import video_analytics - from dg.base_settings import VIDEOS_PAGE from dg.website_admin import website_admin - +from social_website.views import collection_view, video_view, search_view, collection_edit_view, collection_add_view, partner_view +from social_website.urls import DirectTemplateView import videokheti.urls +from videos import views urlpatterns = patterns('', url(r'^$', RedirectView.as_view(url=VIDEOS_PAGE)), @@ -28,4 +27,9 @@ # admin/logout/ should be above admin/ URL url(r'^admin/logout/?$', 'django.contrib.auth.views.logout', {'next_page': '/videos/admin/'}), (r'^admin/', include(website_admin.urls)), + + # coco_api video urls + url(r'^api/video', views.VideoAPIView.as_view()), + ) + diff --git a/videos/views.py b/videos/views.py new file mode 100644 index 0000000000..7a91115ff5 --- /dev/null +++ b/videos/views.py @@ -0,0 +1,92 @@ +# django imports +from django.shortcuts import render +# rest framework imports +from rest_framework import viewsets, generics +from rest_framework import permissions +from rest_framework.response import Response +# django-rest-framework TokenAuthentication imports +from rest_framework.authentication import SessionAuthentication, BasicAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated +# logging, pagination and permissions +import time +from api.utils import Utils, CustomPagination +from api.permissions import IsAllowed +# app imports +from videos.models import * +from videos.serializers import * + +__author__ = "Stuti Verma" +__credits__ = ["Sujit Chaurasia", "Sagar Singh"] +__email__ = "stuti@digitalgreen.org" +__status__ = "Development" + +class VideoAPIView(generics.ListCreateAPIView): + ''' + This view is specifically written for coco api access. + This class-based view is to query Videos model and provide JSON response. + django-rest-framework based token passed in Header as {'Authorization': 'Token 12345exampleToken'} + is required to access data from this View. + Only POST method is allowed. + GET request sent will show a message : "Method \"GET\" not allowed." + ''' + + # django-rest-framework TokenAuthentication + authentication_classes = [TokenAuthentication] + permission_classes =[IsAuthenticated and IsAllowed] + pagination_class = CustomPagination + serializer_class = VideoSerializer + + # GET request + def get(self, request): + return Response({"detail":"Method \"GET\" not allowed"}) + + # POST request + def post(self, request, *args, **kwargs): + """ + This function can take following optional POST params to filter on Video obects: + 1.) id - to find video by id + 2.) fields - to pass comma separated value to be returned a value for each Video object, e.g. pass + fields value as id,title to get only these key-value pairs for each Video object + + If none of the above parameters are provided, then all the objects from respective model + will be sent to the response. + """ + + start_time = time.time() + utils = Utils() + + fields_values = request.POST.get('fields', '') # POST param 'fields' + video_id = self.request.POST.get('id', 0) # POST param 'id' + + if video_id: # checks if video id is present + queryset = Video.objects.filter(id__exact=video_id) + else: + queryset = Video.objects.all().order_by('id') + + page = self.paginate_queryset(queryset) + if page is not None: + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = self.get_serializer(page, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = self.get_serializer(page, many=True) + paginated_response = self.get_paginated_response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, paginated_response.status_code) + return paginated_response + + if fields_values: # fields provided in POST request and if not empty serves those fields only + fields_values = [val.strip() for val in fields_values.split(",")] + # updated queryset is passed and fields provided in POST request is passed to the dynamic serializer + serializer = VideoSerializer(queryset, fields=fields_values, many=True) + else: + # if fields param is empty then all the fields as mentioned in serializer are served to the response + serializer = VideoSerializer(queryset, many=True) + + response = Response(serializer.data) + processing_time = time.time() - start_time + utils.logRequest(request, self, self.post.__name__ , processing_time, response.status_code) + # JSON Response is provided + return response