Merge branch 'master' into feature/diana/rubric-input
This commit is contained in:
@@ -40,7 +40,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from datetime import date
|
||||
from collections import namedtuple
|
||||
|
||||
from courseware.courses import get_courses_by_university
|
||||
from courseware.courses import get_courses
|
||||
from courseware.access import has_access
|
||||
|
||||
from statsd import statsd
|
||||
@@ -74,16 +74,21 @@ def index(request, extra_context={}, user=None):
|
||||
domain = settings.MITX_FEATURES.get('FORCE_UNIVERSITY_DOMAIN') # normally False
|
||||
if domain==False: # do explicit check, because domain=None is valid
|
||||
domain = request.META.get('HTTP_HOST')
|
||||
universities = get_courses_by_university(None,
|
||||
domain=domain)
|
||||
|
||||
courses = get_courses(None, domain=domain)
|
||||
|
||||
# Sort courses by how far are they from they start day
|
||||
key = lambda course: course.metadata['days_to_start']
|
||||
courses = sorted(courses, key=key, reverse=True)
|
||||
|
||||
# Get the 3 most recent news
|
||||
top_news = _get_news(top=3)
|
||||
|
||||
context = {'universities': universities, 'news': top_news}
|
||||
context = {'courses': courses, 'news': top_news}
|
||||
context.update(extra_context)
|
||||
return render_to_response('index.html', context)
|
||||
|
||||
|
||||
def course_from_id(course_id):
|
||||
"""Return the CourseDescriptor corresponding to this course_id"""
|
||||
course_loc = CourseDescriptor.id_to_location(course_id)
|
||||
|
||||
48
common/djangoapps/track/migrations/0001_initial.py
Normal file
48
common/djangoapps/track/migrations/0001_initial.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'TrackingLog'
|
||||
db.create_table('track_trackinglog', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('dtcreated', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('username', self.gf('django.db.models.fields.CharField')(max_length=32, blank=True)),
|
||||
('ip', self.gf('django.db.models.fields.CharField')(max_length=32, blank=True)),
|
||||
('event_source', self.gf('django.db.models.fields.CharField')(max_length=32)),
|
||||
('event_type', self.gf('django.db.models.fields.CharField')(max_length=32, blank=True)),
|
||||
('event', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
('agent', self.gf('django.db.models.fields.CharField')(max_length=256, blank=True)),
|
||||
('page', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
|
||||
('time', self.gf('django.db.models.fields.DateTimeField')()),
|
||||
))
|
||||
db.send_create_signal('track', ['TrackingLog'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'TrackingLog'
|
||||
db.delete_table('track_trackinglog')
|
||||
|
||||
|
||||
models = {
|
||||
'track.trackinglog': {
|
||||
'Meta': {'object_name': 'TrackingLog'},
|
||||
'agent': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}),
|
||||
'dtcreated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'event': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'event_source': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'event_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ip': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
|
||||
'page': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['track']
|
||||
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import 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 'TrackingLog.host'
|
||||
db.add_column('track_trackinglog', 'host',
|
||||
self.gf('django.db.models.fields.CharField')(default='', max_length=64, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
# Changing field 'TrackingLog.event_type'
|
||||
db.alter_column('track_trackinglog', 'event_type', self.gf('django.db.models.fields.CharField')(max_length=512))
|
||||
|
||||
# Changing field 'TrackingLog.page'
|
||||
db.alter_column('track_trackinglog', 'page', self.gf('django.db.models.fields.CharField')(max_length=512, null=True))
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'TrackingLog.host'
|
||||
db.delete_column('track_trackinglog', 'host')
|
||||
|
||||
|
||||
# Changing field 'TrackingLog.event_type'
|
||||
db.alter_column('track_trackinglog', 'event_type', self.gf('django.db.models.fields.CharField')(max_length=32))
|
||||
|
||||
# Changing field 'TrackingLog.page'
|
||||
db.alter_column('track_trackinglog', 'page', self.gf('django.db.models.fields.CharField')(max_length=32, null=True))
|
||||
|
||||
models = {
|
||||
'track.trackinglog': {
|
||||
'Meta': {'object_name': 'TrackingLog'},
|
||||
'agent': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}),
|
||||
'dtcreated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'event': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'event_source': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'event_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ip': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
|
||||
'page': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['track']
|
||||
0
common/djangoapps/track/migrations/__init__.py
Normal file
0
common/djangoapps/track/migrations/__init__.py
Normal file
@@ -7,11 +7,12 @@ class TrackingLog(models.Model):
|
||||
username = models.CharField(max_length=32,blank=True)
|
||||
ip = models.CharField(max_length=32,blank=True)
|
||||
event_source = models.CharField(max_length=32)
|
||||
event_type = models.CharField(max_length=32,blank=True)
|
||||
event_type = models.CharField(max_length=512,blank=True)
|
||||
event = models.TextField(blank=True)
|
||||
agent = models.CharField(max_length=256,blank=True)
|
||||
page = models.CharField(max_length=32,blank=True,null=True)
|
||||
page = models.CharField(max_length=512,blank=True,null=True)
|
||||
time = models.DateTimeField('event time')
|
||||
host = models.CharField(max_length=64,blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
s = "[%s] %s@%s: %s | %s | %s | %s" % (self.time, self.username, self.ip, self.event_source,
|
||||
|
||||
@@ -17,7 +17,7 @@ from track.models import TrackingLog
|
||||
|
||||
log = logging.getLogger("tracking")
|
||||
|
||||
LOGFIELDS = ['username','ip','event_source','event_type','event','agent','page','time']
|
||||
LOGFIELDS = ['username','ip','event_source','event_type','event','agent','page','time','host']
|
||||
|
||||
def log_event(event):
|
||||
event_str = json.dumps(event)
|
||||
@@ -58,6 +58,7 @@ def user_track(request):
|
||||
"agent": agent,
|
||||
"page": request.GET['page'],
|
||||
"time": datetime.datetime.utcnow().isoformat(),
|
||||
"host": request.META['SERVER_NAME'],
|
||||
}
|
||||
log_event(event)
|
||||
return HttpResponse('success')
|
||||
@@ -83,6 +84,7 @@ def server_track(request, event_type, event, page=None):
|
||||
"agent": agent,
|
||||
"page": page,
|
||||
"time": datetime.datetime.utcnow().isoformat(),
|
||||
"host": request.META['SERVER_NAME'],
|
||||
}
|
||||
|
||||
if event_type.startswith("/event_logs") and request.user.is_staff: # don't log
|
||||
|
||||
@@ -217,11 +217,51 @@ def get_courses_by_university(user, domain=None):
|
||||
'''
|
||||
# TODO: Clean up how 'error' is done.
|
||||
# filter out any courses that errored.
|
||||
visible_courses = branding.get_visible_courses(domain)
|
||||
visible_courses = get_courses(user, domain)
|
||||
|
||||
universities = defaultdict(list)
|
||||
for course in visible_courses:
|
||||
if not has_access(user, course, 'see_exists'):
|
||||
continue
|
||||
universities[course.org].append(course)
|
||||
|
||||
return universities
|
||||
|
||||
|
||||
def get_courses(user, domain=None):
|
||||
'''
|
||||
Returns a list of courses available, sorted by course.number
|
||||
'''
|
||||
courses = branding.get_visible_courses(domain)
|
||||
courses = [c for c in courses if has_access(user, c, 'see_exists')]
|
||||
|
||||
# Add metadata about the start day and if the course is new
|
||||
for course in courses:
|
||||
days_to_start = _get_course_days_to_start(course)
|
||||
|
||||
metadata = course.metadata
|
||||
metadata['days_to_start'] = days_to_start
|
||||
metadata['is_new'] = course.metadata.get('is_new', days_to_start > 1)
|
||||
|
||||
courses = sorted(courses, key=lambda course:course.number)
|
||||
return courses
|
||||
|
||||
|
||||
def _get_course_days_to_start(course):
|
||||
from datetime import datetime as dt
|
||||
from time import mktime, gmtime
|
||||
|
||||
convert_to_datetime = lambda ts: dt.fromtimestamp(mktime(ts))
|
||||
|
||||
start_date = convert_to_datetime(course.start)
|
||||
|
||||
# If the course has a valid advertised date, use that instead
|
||||
advertised_start = course.metadata.get('advertised_start', None)
|
||||
if advertised_start:
|
||||
try:
|
||||
start_date = dt.strptime(advertised_start, "%Y-%m-%dT%H:%M")
|
||||
except ValueError:
|
||||
pass # Invalid date, keep using course.start
|
||||
|
||||
now = convert_to_datetime(gmtime())
|
||||
days_to_start = (start_date - now).days
|
||||
|
||||
return days_to_start
|
||||
|
||||
@@ -17,7 +17,7 @@ from django.views.decorators.cache import cache_control
|
||||
|
||||
from courseware import grades
|
||||
from courseware.access import has_access
|
||||
from courseware.courses import (get_course_with_access, get_courses_by_university)
|
||||
from courseware.courses import (get_courses, get_course_with_access, get_courses_by_university)
|
||||
import courseware.tabs as tabs
|
||||
from courseware.models import StudentModuleCache
|
||||
from module_render import toc_for_course, get_module, get_instance_module
|
||||
@@ -61,16 +61,19 @@ def user_groups(user):
|
||||
return group_names
|
||||
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def courses(request):
|
||||
'''
|
||||
Render "find courses" page. The course selection work is done in courseware.courses.
|
||||
'''
|
||||
universities = get_courses_by_university(request.user,
|
||||
domain=request.META.get('HTTP_HOST'))
|
||||
return render_to_response("courseware/courses.html", {'universities': universities})
|
||||
courses = get_courses(request.user, domain=request.META.get('HTTP_HOST'))
|
||||
|
||||
# Sort courses by how far are they from they start day
|
||||
key = lambda course: course.metadata['days_to_start']
|
||||
courses = sorted(courses, key=key, reverse=True)
|
||||
|
||||
return render_to_response("courseware/courses.html", {'courses': courses})
|
||||
|
||||
|
||||
def render_accordion(request, course, chapter, section):
|
||||
@@ -317,7 +320,7 @@ def jump_to(request, course_id, location):
|
||||
except NoPathToItem:
|
||||
raise Http404("This location is not in any class: {0}".format(location))
|
||||
|
||||
# choose the appropriate view (and provide the necessary args) based on the
|
||||
# choose the appropriate view (and provide the necessary args) based on the
|
||||
# args provided by the redirect.
|
||||
# Rely on index to do all error handling and access control.
|
||||
if chapter is None:
|
||||
@@ -328,7 +331,7 @@ def jump_to(request, course_id, location):
|
||||
return redirect('courseware_section', course_id=course_id, chapter=chapter, section=section)
|
||||
else:
|
||||
return redirect('courseware_position', course_id=course_id, chapter=chapter, section=section, position=position)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def course_info(request, course_id):
|
||||
"""
|
||||
@@ -435,6 +438,11 @@ def university_profile(request, org_id):
|
||||
# Only grab courses for this org...
|
||||
courses = get_courses_by_university(request.user,
|
||||
domain=request.META.get('HTTP_HOST'))[org_id]
|
||||
|
||||
# Sort courses by how far are they from they start day
|
||||
key = lambda course: course.metadata['days_to_start']
|
||||
courses = sorted(courses, key=key, reverse=True)
|
||||
|
||||
context = dict(courses=courses, org_id=org_id)
|
||||
template_file = "university_profile/{0}.html".format(org_id).lower()
|
||||
|
||||
|
||||
@@ -44,12 +44,6 @@ STATUS_MESSAGE_PATH = TEST_ROOT / "status_message.json"
|
||||
COURSES_ROOT = TEST_ROOT / "data"
|
||||
DATA_DIR = COURSES_ROOT
|
||||
|
||||
LOGGING = get_logger_config(TEST_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
dev_env=True,
|
||||
debug=True)
|
||||
|
||||
COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data"
|
||||
# Where the content data is checked out. This may not exist on jenkins.
|
||||
GITHUB_REPO_ROOT = ENV_ROOT / "data"
|
||||
|
||||
@@ -13,6 +13,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.courses-listing {
|
||||
@include clearfix();
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
.courses-listing-item {
|
||||
width: flex-grid(4);
|
||||
margin-right: flex-gutter();
|
||||
float: left;
|
||||
|
||||
&:nth-child(3n+3) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course {
|
||||
background: rgb(250,250,250);
|
||||
border: 1px solid rgb(180,180,180);
|
||||
@@ -24,6 +41,31 @@
|
||||
width: 100%;
|
||||
@include transition(all, 0.15s, linear);
|
||||
|
||||
.status {
|
||||
background: $blue;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
left: 10px;
|
||||
padding: 2px 10px;
|
||||
@include border-radius(2px);
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
top: -6px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.status:after {
|
||||
border-bottom: 6px solid shade($blue, 50%);
|
||||
border-right: 6px solid transparent;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
top: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
%>
|
||||
<%page args="course" />
|
||||
<article id="${course.id}" class="course">
|
||||
%if course.metadata.get('is_new'):
|
||||
<span class="status">New</span>
|
||||
%endif
|
||||
<a href="${reverse('about_course', args=[course.id])}">
|
||||
<div class="inner-wrapper">
|
||||
<header class="course-preview">
|
||||
|
||||
@@ -20,21 +20,13 @@
|
||||
## I'm removing this for now since we aren't using it for the fall.
|
||||
## <%include file="course_filter.html" />
|
||||
<section class="courses">
|
||||
<section class='university-column'>
|
||||
%for course in universities['MITx']:
|
||||
<ul class="courses-listing">
|
||||
%for course in courses:
|
||||
<li class="courses-listing-item">
|
||||
<%include file="../course.html" args="course=course" />
|
||||
</li>
|
||||
%endfor
|
||||
</section>
|
||||
<section class='university-column'>
|
||||
%for course in universities['HarvardX']:
|
||||
<%include file="../course.html" args="course=course" />
|
||||
%endfor
|
||||
</section>
|
||||
<section class='university-column last'>
|
||||
%for course in universities['BerkeleyX']:
|
||||
<%include file="../course.html" args="course=course" />
|
||||
%endfor
|
||||
</section>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -106,21 +106,13 @@
|
||||
</section>
|
||||
|
||||
<section class="courses">
|
||||
<section class='university-column'>
|
||||
%for course in universities['MITx']:
|
||||
<%include file="course.html" args="course=course" />
|
||||
<ul class="courses-listing">
|
||||
%for course in courses:
|
||||
<li class="courses-listing-item">
|
||||
<%include file="course.html" args="course=course" />
|
||||
</li>
|
||||
%endfor
|
||||
</section>
|
||||
<section class='university-column'>
|
||||
%for course in universities['HarvardX']:
|
||||
<%include file="course.html" args="course=course" />
|
||||
%endfor
|
||||
</section>
|
||||
<section class='university-column last'>
|
||||
%for course in universities['BerkeleyX']:
|
||||
<%include file="course.html" args="course=course" />
|
||||
%endfor
|
||||
</section>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
18
rakefile
18
rakefile
@@ -169,7 +169,7 @@ Dir["common/lib/*"].select{|lib| File.directory?(lib)}.each do |lib|
|
||||
TEST_TASK_DIRS << lib
|
||||
|
||||
desc "Run tests for common lib #{lib} (without coverage)"
|
||||
task "fasttest_#{lib}" do
|
||||
task "fasttest_#{lib}" do
|
||||
sh("nosetests #{lib}")
|
||||
end
|
||||
|
||||
@@ -309,16 +309,22 @@ task :builddocs do
|
||||
end
|
||||
end
|
||||
|
||||
desc "Show doc in browser (mac only for now) TODO add linux support"
|
||||
desc "Show docs in browser (mac and ubuntu)."
|
||||
task :showdocs do
|
||||
Dir.chdir('docs/build/html') do
|
||||
sh('open index.html')
|
||||
if RUBY_PLATFORM.include? 'darwin' # mac os
|
||||
sh('open index.html')
|
||||
elsif RUBY_PLATFORM.include? 'linux' # make more ubuntu specific?
|
||||
sh('sensible-browser index.html') # ubuntu
|
||||
else
|
||||
raise "\nUndefined how to run browser on your machine.
|
||||
Please use 'rake builddocs' and then manually open
|
||||
'mitx/docs/build/html/index.html."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "Build docs and show them in browser"
|
||||
task :doc => :builddocs do
|
||||
Dir.chdir('docs/build/html') do
|
||||
sh('open index.html')
|
||||
end
|
||||
Rake::Task["showdocs"].invoke
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user