Commit c9de3875 by Zia Fazal Committed by Jonathan Piacenti

initial changes

completed tests and added type filter

removed organizations app

migration to delete tables
parent 31adeb0c
......@@ -1713,6 +1713,10 @@ class CoursesMetricsCompletionsLeadersList(SecureAPIView):
```/api/courses/{course_id}/metrics/completions/leaders/?count=6```
To get only percentage of a user and course average skipleaders parameter can be used
```/api/courses/{course_id}/metrics/completions/leaders/?user_id={user_id}&skipleaders=true```
To get data for one or more orgnaizations organizations filter can be applied
* organizations filter can be a single id or multiple ids separated by comma
```/api/courses/{course_id}/metrics/completions/leaders/?organizations={organization_id1},{organization_id2}```
### Use Cases/Notes:
* Example: Display leaders in terms of completions in a given course
* Example: Display top 3 users leading in terms of completions in a given course
......
......@@ -16,7 +16,8 @@ from django.test import Client
from django.test.utils import override_settings
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from api_manager.models import GroupRelationship, GroupProfile, Organization
from api_manager.models import GroupRelationship, GroupProfile
from organizations.models import Organization
from projects.models import Project
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......
......@@ -13,7 +13,7 @@ from api_manager.courseware_access import get_course
from api_manager.models import GroupRelationship, CourseGroupRelationship, GroupProfile, APIUser as User
from api_manager.permissions import SecureAPIView, SecureListAPIView
from api_manager.utils import str2bool, generate_base_uri
from api_manager.organizations import serializers
from organizations import serializers
from projects.serializers import BasicWorkgroupSerializer, GroupSerializer
......
......@@ -6,7 +6,8 @@ import json
from django.contrib.auth.models import Group
from django.core.management.base import BaseCommand
from api_manager.models import GroupProfile, Organization
from api_manager.models import GroupProfile
from organizations.models import Organization
......
......@@ -9,7 +9,8 @@ from django.contrib.auth.models import Group, User
from django.test import TestCase
from api_manager.management.commands import migrate_orgdata
from api_manager.models import GroupProfile, GroupRelationship, Organization
from api_manager.models import GroupProfile, GroupRelationship
from organizations.models import Organization
class MigrateOrgDataTests(TestCase):
......
......@@ -8,7 +8,6 @@ from django.conf import settings
from model_utils.models import TimeStampedModel
from .utils import is_int
from projects.models import Workgroup
class GroupRelationship(TimeStampedModel):
......@@ -134,22 +133,6 @@ class CourseContentGroupRelationship(TimeStampedModel):
unique_together = ("course_id", "content_id", "group_profile")
class Organization(TimeStampedModel):
"""
Main table representing the Organization concept. Organizations are
primarily a collection of Users.
"""
name = models.CharField(max_length=255)
display_name = models.CharField(max_length=255, null=True, blank=True)
contact_name = models.CharField(max_length=255, null=True, blank=True)
contact_email = models.EmailField(max_length=255, null=True, blank=True)
contact_phone = models.CharField(max_length=50, null=True, blank=True)
logo_url = models.CharField(max_length=255, blank=True, null=True)
workgroups = models.ManyToManyField(Workgroup, related_name="organizations")
users = models.ManyToManyField(User, related_name="organizations")
groups = models.ManyToManyField(Group, related_name="organizations")
class CourseModuleCompletion(TimeStampedModel):
"""
The CourseModuleCompletion model contains user, course, module information
......
......@@ -13,7 +13,7 @@ from django.conf.urls import include, patterns, url
from rest_framework.routers import SimpleRouter
from api_manager.organizations.views import OrganizationsViewSet
from organizations.views import OrganizationsViewSet
from api_manager.system import views as system_views
from projects import views as project_views
......
......@@ -3,7 +3,7 @@
from rest_framework import serializers
from api_manager.models import APIUser
from api_manager.organizations.serializers import BasicOrganizationSerializer
from organizations.serializers import BasicOrganizationSerializer
from student.models import UserProfile
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
......
......@@ -50,7 +50,7 @@ from api_manager.courses.serializers import CourseModuleCompletionSerializer
from api_manager.courseware_access import get_course, get_course_child, get_course_key, course_exists
from api_manager.permissions import SecureAPIView, SecureListAPIView, IdsInFilterBackend, HasOrgsFilterBackend
from api_manager.models import GroupProfile, APIUser as User
from api_manager.organizations.serializers import OrganizationSerializer
from organizations.serializers import OrganizationSerializer
from api_manager.utils import generate_base_uri, dict_has_items, extract_data_params
from projects.serializers import BasicWorkgroupSerializer
from .serializers import UserSerializer, UserCountByCitySerializer, UserRolesSerializer
......
__author__ = 'zia'
"""
One-time data migration script -- should not need to run it again
"""
import logging
from django.core.management.base import BaseCommand
from django.db import connection, transaction
from organizations.models import Organization
log = logging.getLogger(__name__)
class Command(BaseCommand):
"""
Moves existing organizations data from api_manager app to organizations app
"""
help = "Command to move existing organizations from api_manager app to organizations app"
def handle(self, *args, **options):
existing_entries = Organization.objects.all().count()
if existing_entries == 0:
try:
cursor = connection.cursor()
cursor.execute('INSERT INTO organizations_organization SELECT * from api_manager_organization')
log_msg = 'organizations entries moved from api_manager to organizations app'
self.print_message(log_msg)
cursor.execute('INSERT INTO organizations_organization_workgroups '
'SELECT * from api_manager_organization_workgroups')
log_msg = 'organization_workgroups entries moved from api_manager to organizations app'
self.print_message(log_msg)
cursor.execute('INSERT INTO organizations_organization_users '
'SELECT * from api_manager_organization_users')
log_msg = 'organization_users entries moved from api_manager to organizations app'
self.print_message(log_msg)
cursor.execute('INSERT INTO organizations_organization_groups '
'SELECT * from api_manager_organization_groups')
log_msg = 'organization_groups entries moved from api_manager to organizations app'
self.print_message(log_msg)
transaction.commit()
except Exception as e:
log_msg = e.message
self.print_message(log_msg)
else:
log_msg = 'oroganizations_organization is not empty. You might have already filled it.'
self.print_message(log_msg)
def print_message(self, msg):
print msg
log.info(msg)
import mock
from django.test import TestCase
from django.db import connection, transaction, models
from organizations.management.commands import move_organizations_entries
from organizations.models import Organization
from student.tests.factories import UserFactory, GroupFactory
from django.contrib.auth.models import Group, User
from projects.models import Workgroup, Project
from model_utils.models import TimeStampedModel
from south.db import db
@mock.patch.dict("django.conf.settings.FEATURES", {'ORGANIZATIONS_APP': True})
class MoveOrganizationEntriesTests(TestCase):
"""
Test suite for organization table data copy from api_manager to organizations
"""
def setUp(self):
# Create tables and add data
user1 = UserFactory()
user2 = UserFactory()
group1 = GroupFactory()
group2 = GroupFactory()
proj = Project()
proj.course_id = 'slashes:test+cs234+ct323'
proj.content_id = 'location:test+cs234+ct323+chapter+b145cc8196734885ac8835b841d486ee'
proj.save()
workgroup = Workgroup()
workgroup.name = 'Test workgroup'
workgroup.project = proj
workgroup.save()
attrs = {
'name': models.CharField(max_length=255),
'display_name': models.CharField(max_length=255, null=True, blank=True),
'contact_name': models.CharField(max_length=255, null=True, blank=True),
'contact_email': models.EmailField(max_length=255, null=True, blank=True),
'contact_phone': models.CharField(max_length=50, null=True, blank=True),
'logo_url': models.CharField(max_length=255, blank=True, null=True),
'workgroups': models.ManyToManyField(Workgroup, related_name="organizations"),
'users': models.ManyToManyField(User, related_name="organizations"),
'groups': models.ManyToManyField(Group, related_name="organizations"),
'__module__': 'api_manager.models'
}
self.organization = type("Organization", (TimeStampedModel,), attrs)
fields = [(f.name, f) for f in self.organization._meta.local_fields]
table_name = self.organization._meta.db_table
db.create_table(table_name, fields)
attrs = {
'organization_id': models.IntegerField(),
'user_id': models.IntegerField(),
'__module__': 'api_manager.models'
}
self.organization_users = type("organization_users", (models.Model,), attrs)
fields = [(f.name, f) for f in self.organization_users._meta.local_fields]
table_name = self.organization_users._meta.db_table
db.create_table(table_name, fields)
attrs = {
'organization_id': models.IntegerField(),
'group_id': models.IntegerField(),
'__module__': 'api_manager.models'
}
self.organization_groups = type("organization_groups", (models.Model,), attrs)
fields = [(f.name, f) for f in self.organization_groups._meta.local_fields]
table_name = self.organization_groups._meta.db_table
db.create_table(table_name, fields)
attrs = {
'organization_id': models.IntegerField(),
'workgroup_id': models.IntegerField(),
'__module__': 'api_manager.models'
}
self.organization_workgroups = type("organization_workgroups", (models.Model,), attrs)
fields = [(f.name, f) for f in self.organization_workgroups._meta.local_fields]
table_name = self.organization_workgroups._meta.db_table
db.create_table(table_name, fields)
for i in xrange(1, 9):
org = self.organization()
org.name = 'test_and_company {}'.format(i)
org.display_name = 'test display name {}'.format(i)
org.contact_name = 'test contact name {}'.format(i)
org.contact_email = 'test{}@test.com'.format(i)
org.save()
org.users.add(user1, user2)
org.groups.add(group1, group2)
org.workgroups.add(workgroup)
def test_organization_entries_copy(self):
"""
Test organization entries copy from api_manager app to organizations app
"""
# Run the command
move_organizations_entries.Command().handle()
total_orgs_old = self.organization.objects.all().count()
total_orgs_new = Organization.objects.all().count()
self.assertEqual(total_orgs_old, total_orgs_new)
total_org_users_old = 0
total_org_users_new = 0
for org in self.organization.objects.all():
total_org_users_old += org.users.all().count()
for org in Organization.objects.all():
total_org_users_new += org.users.all().count()
self.assertEqual(total_org_users_old, total_org_users_new)
total_org_groups_old = 0
total_org_groups_new = 0
for org in self.organization.objects.all():
total_org_groups_old += org.groups.all().count()
for org in Organization.objects.all():
total_org_groups_new += org.groups.all().count()
self.assertEqual(total_org_groups_old, total_org_groups_new)
total_org_workgroups_old = 0
total_org_workgroups_new = 0
for org in self.organization.objects.all():
total_org_workgroups_old += org.workgroups.all().count()
for org in Organization.objects.all():
total_org_workgroups_new += org.workgroups.all().count()
self.assertEqual(total_org_workgroups_old, total_org_workgroups_new)
"""
Django database models supporting the organizations app
"""
from django.contrib.auth.models import Group, User
from django.db import models
from model_utils.models import TimeStampedModel
from projects.models import Workgroup
class Organization(TimeStampedModel):
"""
Main table representing the Organization concept. Organizations are
primarily a collection of Users.
"""
name = models.CharField(max_length=255)
display_name = models.CharField(max_length=255, null=True, blank=True)
contact_name = models.CharField(max_length=255, null=True, blank=True)
contact_email = models.EmailField(max_length=255, null=True, blank=True)
contact_phone = models.CharField(max_length=50, null=True, blank=True)
logo_url = models.CharField(max_length=255, blank=True, null=True)
workgroups = models.ManyToManyField(Workgroup, related_name="organizations")
users = models.ManyToManyField(User, related_name="organizations")
groups = models.ManyToManyField(Group, related_name="organizations")
""" Django REST Framework Serializers """
from django.contrib.auth.models import User
from rest_framework import serializers
from api_manager.models import Organization
from organizations.models import Organization
class OrganizationSerializer(serializers.ModelSerializer):
......@@ -13,8 +11,8 @@ class OrganizationSerializer(serializers.ModelSerializer):
class Meta:
""" Serializer/field specification """
model = Organization
fields = ('url', 'id', 'name', 'display_name', 'contact_name', 'contact_email', 'contact_phone'
, 'logo_url', 'workgroups', 'users', 'groups', 'created', 'modified')
fields = ('url', 'id', 'name', 'display_name', 'contact_name', 'contact_email', 'contact_phone',
'logo_url', 'workgroups', 'users', 'groups', 'created', 'modified')
read_only = ('url', 'id', 'created')
......
......@@ -267,11 +267,12 @@ class OrganizationsApiTests(ModuleStoreTestCase):
org_id = response.data['id']
# create groups
max_groups, groups = 4, []
max_groups, groups, contactgroup_count = 4, [], 2
for i in xrange(1, max_groups + 1):
grouptype = 'contactgroup' if i <= contactgroup_count else 'series'
data = {
'name': '{} {}'.format('Test Group', i),
'type': 'contactgroup',
'type': grouptype,
'data': {'display_name': 'organization contacts group'}
}
response = self.do_post(self.base_groups_uri, data)
......@@ -288,6 +289,11 @@ class OrganizationsApiTests(ModuleStoreTestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), max_groups)
# get organization groups with type filter
response = self.do_get('{}?type=contactgroup'.format(groups_uri))
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), contactgroup_count)
# post an invalid group
data = {"id": '45533333'}
response = self.do_post(groups_uri, data)
......@@ -337,7 +343,6 @@ class OrganizationsApiTests(ModuleStoreTestCase):
self.assertEqual(response.data[0]['id'], self.test_user.id)
self.assertEqual(response.data[0]['course_count'], 2)
def test_organizations_metrics_get(self):
users = []
for i in xrange(1, 6):
......@@ -380,7 +385,6 @@ class OrganizationsApiTests(ModuleStoreTestCase):
self.assertEqual(response.data['users_grade_average'], 0.838)
self.assertEqual(response.data['users_grade_complete_count'], 4)
def test_organizations_metrics_get_courses_filter(self):
users = []
for i in xrange(1, 12):
......
......@@ -11,7 +11,7 @@ from rest_framework.decorators import action
from rest_framework.response import Response
from api_manager.courseware_access import get_course_key
from api_manager.models import Organization
from organizations.models import Organization
from api_manager.users.serializers import UserSerializer
from api_manager.groups.serializers import GroupSerializer
from api_manager.utils import str2bool
......@@ -28,7 +28,7 @@ class OrganizationsViewSet(viewsets.ModelViewSet):
serializer_class = OrganizationSerializer
model = Organization
@action(methods=['get',])
@action(methods=['get', ])
def metrics(self, request, pk):
"""
Provide statistical information for the specified Organization
......@@ -95,7 +95,10 @@ class OrganizationsViewSet(viewsets.ModelViewSet):
Add a Group to a organization or retrieve list of groups in organization
"""
if request.method == 'GET':
group_type = request.QUERY_PARAMS.get('type', None)
groups = Group.objects.filter(organizations=pk)
if group_type:
groups = groups.filter(groupprofile__group_type=group_type)
response_data = []
if groups:
for group in groups:
......
......@@ -15,7 +15,7 @@ class Project(TimeStampedModel):
course_id = models.CharField(max_length=255)
content_id = models.CharField(max_length=255)
organization = models.ForeignKey(
'api_manager.Organization',
'organizations.Organization',
blank=True,
null=True,
related_name="projects",
......
......@@ -712,6 +712,10 @@ if FEATURES.get('STUDENT_GRADEBOOK') and "'gradebook'" not in INSTALLED_APPS:
if FEATURES.get('STUDENT_PROGRESS') and "progress" not in INSTALLED_APPS:
INSTALLED_APPS += ('progress',)
############# Organizations app #################
if FEATURES.get('ORGANIZATIONS_APP') and "organizations" not in INSTALLED_APPS:
INSTALLED_APPS += ('organizations',)
##### SET THE LIST OF ALLOWED IP ADDRESSES FOR THE API ######
API_ALLOWED_IP_ADDRESSES = ENV_TOKENS.get('API_ALLOWED_IP_ADDRESSES')
......
......@@ -432,6 +432,9 @@ FEATURES = {
# In order to use the "progress", you must add it to the list of INSTALLED_APPS in
# addition to setting the flag to True here. A reference is available in aws.py
'STUDENT_PROGRESS': False,
# Enable the Organizations,
'ORGANIZATIONS_APP': False,
}
# Ignore static asset files on import which match this pattern
......
......@@ -529,3 +529,7 @@ FEATURES['STUDENT_PROGRESS'] = True
if FEATURES.get('STUDENT_PROGRESS', False) and "'progress'" not in INSTALLED_APPS:
INSTALLED_APPS += ('progress',)
############# Organizations app #################
if FEATURES.get('ORGANIZATIONS_APP') and "organizations" not in INSTALLED_APPS:
INSTALLED_APPS += ('organizations',)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment