Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
de15f8ec
Commit
de15f8ec
authored
Aug 18, 2015
by
Diana Huang
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #9360 from edx/diana/add-last-activity
Add a new last_activity_at field.
parents
c49d51f9
ce8f3112
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
158 additions
and
8 deletions
+158
-8
lms/djangoapps/teams/migrations/0004_auto__add_field_courseteam_discussion_topic_id__add_field_courseteam_l.py
+94
-0
lms/djangoapps/teams/models.py
+12
-1
lms/djangoapps/teams/serializers.py
+4
-3
lms/djangoapps/teams/tests/factories.py
+7
-0
lms/djangoapps/teams/tests/test_models.py
+17
-3
lms/djangoapps/teams/tests/test_views.py
+12
-1
lms/djangoapps/teams/views.py
+12
-0
No files found.
lms/djangoapps/teams/migrations/0004_auto__add_field_courseteam_discussion_topic_id__add_field_courseteam_l.py
0 → 100644
View file @
de15f8ec
# -*- coding: utf-8 -*-
import
pytz
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding field 'CourseTeam.last_activity_at'
db
.
add_column
(
'teams_courseteam'
,
'last_activity_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2015
,
8
,
17
,
0
,
0
)
.
replace
(
tzinfo
=
pytz
.
utc
)),
keep_default
=
False
)
# Adding field 'CourseTeamMembership.last_activity_at'
db
.
add_column
(
'teams_courseteammembership'
,
'last_activity_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2015
,
8
,
17
,
0
,
0
)
.
replace
(
tzinfo
=
pytz
.
utc
)),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting field 'CourseTeam.last_activity_at'
db
.
delete_column
(
'teams_courseteam'
,
'last_activity_at'
)
# Deleting field 'CourseTeamMembership.last_activity_at'
db
.
delete_column
(
'teams_courseteammembership'
,
'last_activity_at'
)
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'teams.courseteam'
:
{
'Meta'
:
{
'object_name'
:
'CourseTeam'
},
'country'
:
(
'django_countries.fields.CountryField'
,
[],
{
'max_length'
:
'2'
,
'blank'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'date_created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'300'
}),
'discussion_topic_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'language'
:
(
'student.models.LanguageField'
,
[],
{
'max_length'
:
'16'
,
'blank'
:
'True'
}),
'last_activity_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'team_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
}),
'topic_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'users'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'db_index'
:
'True'
,
'related_name'
:
"'teams'"
,
'symmetrical'
:
'False'
,
'through'
:
"orm['teams.CourseTeamMembership']"
,
'to'
:
"orm['auth.User']"
})
},
'teams.courseteammembership'
:
{
'Meta'
:
{
'unique_together'
:
"(('user', 'team'),)"
,
'object_name'
:
'CourseTeamMembership'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'last_activity_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{}),
'team'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'membership'"
,
'to'
:
"orm['teams.CourseTeam']"
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'teams'
]
lms/djangoapps/teams/models.py
View file @
de15f8ec
"""Django models related to teams functionality."""
from
uuid
import
uuid4
import
pytz
from
datetime
import
datetime
from
django.contrib.auth.models
import
User
from
django.db
import
models
...
...
@@ -23,13 +25,13 @@ class CourseTeam(models.Model):
course_id
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
topic_id
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
,
blank
=
True
)
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
# last_activity is computed through a query
description
=
models
.
CharField
(
max_length
=
300
)
country
=
CountryField
(
blank
=
True
)
language
=
LanguageField
(
blank
=
True
,
help_text
=
ugettext_lazy
(
"Optional language the team uses as ISO 639-1 code."
),
)
last_activity_at
=
models
.
DateTimeField
()
users
=
models
.
ManyToManyField
(
User
,
db_index
=
True
,
related_name
=
'teams'
,
through
=
'CourseTeamMembership'
)
@classmethod
...
...
@@ -62,6 +64,7 @@ class CourseTeam(models.Model):
description
=
description
,
country
=
country
if
country
else
''
,
language
=
language
if
language
else
''
,
last_activity_at
=
datetime
.
utcnow
()
.
replace
(
tzinfo
=
pytz
.
utc
)
)
return
course_team
...
...
@@ -88,6 +91,14 @@ class CourseTeamMembership(models.Model):
user
=
models
.
ForeignKey
(
User
)
team
=
models
.
ForeignKey
(
CourseTeam
,
related_name
=
'membership'
)
date_joined
=
models
.
DateTimeField
(
auto_now_add
=
True
)
last_activity_at
=
models
.
DateTimeField
()
def
save
(
self
,
*
args
,
**
kwargs
):
""" Customize save method to set the last_activity_at if it does not currently exist. """
if
not
self
.
last_activity_at
:
self
.
last_activity_at
=
datetime
.
utcnow
()
.
replace
(
tzinfo
=
pytz
.
utc
)
super
(
CourseTeamMembership
,
self
)
.
save
(
*
args
,
**
kwargs
)
@classmethod
def
get_memberships
(
cls
,
username
=
None
,
course_ids
=
None
,
team_id
=
None
):
...
...
lms/djangoapps/teams/serializers.py
View file @
de15f8ec
...
...
@@ -58,9 +58,10 @@ class CourseTeamSerializer(serializers.ModelSerializer):
"description"
,
"country"
,
"language"
,
"last_activity_at"
,
"membership"
,
)
read_only_fields
=
(
"course_id"
,
"date_created"
,
"discussion_topic_id"
)
read_only_fields
=
(
"course_id"
,
"date_created"
,
"discussion_topic_id"
,
"last_activity_at"
)
class
CourseTeamCreationSerializer
(
serializers
.
ModelSerializer
):
...
...
@@ -118,8 +119,8 @@ class MembershipSerializer(serializers.ModelSerializer):
class
Meta
(
object
):
"""Defines meta information for the ModelSerializer."""
model
=
CourseTeamMembership
fields
=
(
"user"
,
"team"
,
"date_joined"
)
read_only_fields
=
(
"date_joined"
,)
fields
=
(
"user"
,
"team"
,
"date_joined"
,
"last_activity_at"
)
read_only_fields
=
(
"date_joined"
,
"last_activity_at"
)
class
PaginatedMembershipSerializer
(
PaginationSerializer
):
...
...
lms/djangoapps/teams/tests/factories.py
View file @
de15f8ec
"""Factories for testing the Teams API."""
import
pytz
from
datetime
import
datetime
from
uuid
import
uuid4
import
factory
...
...
@@ -8,6 +10,9 @@ from factory.django import DjangoModelFactory
from
..models
import
CourseTeam
,
CourseTeamMembership
LAST_ACTIVITY_AT
=
datetime
(
2015
,
8
,
15
,
0
,
0
,
0
,
tzinfo
=
pytz
.
utc
)
class
CourseTeamFactory
(
DjangoModelFactory
):
"""Factory for CourseTeams.
...
...
@@ -20,8 +25,10 @@ class CourseTeamFactory(DjangoModelFactory):
discussion_topic_id
=
factory
.
LazyAttribute
(
lambda
a
:
uuid4
()
.
hex
)
name
=
"Awesome Team"
description
=
"A simple description"
last_activity_at
=
LAST_ACTIVITY_AT
class
CourseTeamMembershipFactory
(
DjangoModelFactory
):
"""Factory for CourseTeamMemberships."""
FACTORY_FOR
=
CourseTeamMembership
last_activity_at
=
LAST_ACTIVITY_AT
lms/djangoapps/teams/tests/test_models.py
View file @
de15f8ec
...
...
@@ -29,9 +29,23 @@ class TeamMembershipTest(SharedModuleStoreTestCase):
self
.
team1
=
CourseTeamFactory
(
course_id
=
COURSE_KEY1
,
team_id
=
'team1'
)
self
.
team2
=
CourseTeamFactory
(
course_id
=
COURSE_KEY2
,
team_id
=
'team2'
)
self
.
team_membership11
=
CourseTeamMembershipFactory
(
user
=
self
.
user1
,
team
=
self
.
team1
)
self
.
team_membership12
=
CourseTeamMembershipFactory
(
user
=
self
.
user2
,
team
=
self
.
team1
)
self
.
team_membership21
=
CourseTeamMembershipFactory
(
user
=
self
.
user1
,
team
=
self
.
team2
)
self
.
team_membership11
=
CourseTeamMembership
(
user
=
self
.
user1
,
team
=
self
.
team1
)
self
.
team_membership11
.
save
()
self
.
team_membership12
=
CourseTeamMembership
(
user
=
self
.
user2
,
team
=
self
.
team1
)
self
.
team_membership12
.
save
()
self
.
team_membership21
=
CourseTeamMembership
(
user
=
self
.
user1
,
team
=
self
.
team2
)
self
.
team_membership21
.
save
()
def
test_membership_last_activity_set
(
self
):
current_last_activity
=
self
.
team_membership11
.
last_activity_at
# Assert that the first save in the setUp sets a value.
self
.
assertIsNotNone
(
current_last_activity
)
self
.
team_membership11
.
save
()
# Verify that we only change the last activity_at when it doesn't
# already exist.
self
.
assertEqual
(
self
.
team_membership11
.
last_activity_at
,
current_last_activity
)
@ddt.data
(
(
None
,
None
,
None
,
3
),
...
...
lms/djangoapps/teams/tests/test_views.py
View file @
de15f8ec
# -*- coding: utf-8 -*-
"""Tests for the teams API at the HTTP request level."""
import
json
import
pytz
from
datetime
import
datetime
from
dateutil
import
parser
import
ddt
...
...
@@ -13,7 +16,7 @@ from courseware.tests.factories import StaffFactory
from
student.tests.factories
import
UserFactory
,
AdminFactory
,
CourseEnrollmentFactory
from
student.models
import
CourseEnrollment
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
.factories
import
CourseTeamFactory
from
.factories
import
CourseTeamFactory
,
LAST_ACTIVITY_AT
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
django_comment_common.models
import
Role
,
FORUM_ROLE_COMMUNITY_TA
...
...
@@ -542,6 +545,13 @@ class TestCreateTeamAPI(TeamAPITestCase):
team_membership
=
team
[
'membership'
]
del
team
[
'membership'
]
# verify that it's been set to a time today.
self
.
assertEqual
(
parser
.
parse
(
team
[
'last_activity_at'
])
.
date
(),
datetime
.
utcnow
()
.
replace
(
tzinfo
=
pytz
.
utc
)
.
date
()
)
del
team
[
'last_activity_at'
]
# Verify that the creating user gets added to the team.
self
.
assertEqual
(
len
(
team_membership
),
1
)
member
=
team_membership
[
0
][
'user'
]
...
...
@@ -590,6 +600,7 @@ class TestDetailTeamAPI(TeamAPITestCase):
if
status
==
200
:
self
.
assertEqual
(
team
[
'description'
],
self
.
test_team_1
.
description
)
self
.
assertEqual
(
team
[
'discussion_topic_id'
],
self
.
test_team_1
.
discussion_topic_id
)
self
.
assertEqual
(
parser
.
parse
(
team
[
'last_activity_at'
]),
LAST_ACTIVITY_AT
)
def
test_does_not_exist
(
self
):
self
.
get_team_detail
(
'no_such_team'
,
404
)
...
...
lms/djangoapps/teams/views.py
View file @
de15f8ec
...
...
@@ -222,6 +222,9 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
* language: Optionally specifies which language the team is
associated with.
* last_activity_at: The date of the last activity of any team member
within the team.
* membership: A list of the users that are members of the team.
See membership endpoint for more detail.
...
...
@@ -451,6 +454,9 @@ class TeamsDetailView(ExpandableFieldViewMixin, RetrievePatchAPIView):
* membership: A list of the users that are members of the team. See
membership endpoint for more detail.
* last_activity_at: The date of the last activity of any team member
within the team.
For all text fields, clients rendering the values should take care
to HTML escape them to avoid script injections, as the data is
stored exactly as specified. The intention is that plain text is
...
...
@@ -754,6 +760,9 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView):
* date_joined: The date and time the membership was created.
* last_activity_at: The date of the last activity of the user
within the team.
For all text fields, clients rendering the values should take care
to HTML escape them to avoid script injections, as the data is
stored exactly as specified. The intention is that plain text is
...
...
@@ -958,6 +967,9 @@ class MembershipDetailView(ExpandableFieldViewMixin, GenericAPIView):
* date_joined: The date and time the membership was created.
* last_activity_at: The date of the last activity of any team member
within the team.
For all text fields, clients rendering the values should take care
to HTML escape them to avoid script injections, as the data is
stored exactly as specified. The intention is that plain text is
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment