from django.forms import ModelForm
+from ajax_select.fields import AutoCompleteSelectField
+from ajax_select.fields import AutoCompleteSelectWidget
+from ajax_select_cascade.fields import AutoCompleteDependentSelectField
+from ajax_select_cascade.fields import AutoCompleteDependentSelectWidget
+
from karmaworld.apps.courses.models import Course
class CourseForm(ModelForm):
+ school = AutoCompleteSelectField(
+ 'school',
+ widget=AutoCompleteSelectWidget(
+ 'school',
+ attrs={'id': 'dom_autocomplete_school'}
+ )
+ )
+ department = AutoCompleteDependentSelectField(
+ 'dept_given_school',
+ widget=AutoCompleteDependentSelectWidget(
+ 'dept_given_school',
+ attrs={'data-upstream-id': 'dom_autocomplete_school'},
+ )
+ )
class Meta:
model = Course
- fields = ('name', 'school', 'url', 'instructor_name', \
- 'instructor_email')
-
+ # order the fields
+ fields = ('school', 'department', 'name', 'instructor_name',
+ 'instructor_email', 'url')
from django.db import models
from django.utils.text import slugify
from karmaworld.settings.manual_unique_together import auto_add_check_unique_together
+from ajax_select import LookupChannel
+from ajax_select_cascade import DependentLookupChannel
+from ajax_select_cascade import register_channel_name
+
+from karmaworld.utils.ajax_selects import register_channel_name
class SchoolManager(models.Manager):
self.save()
+@register_channel_name('school')
+class SchoolLookup(LookupChannel):
+ """
+ Handles AJAX lookups against the school model's name and value fields.
+ """
+ model = School
+
+ def get_query(self, q, request):
+ """ Search against both name and alias. """
+ query = models.Q(name__icontains=q) | models.Q(alias__icontains=q)
+ return School.objects.filter(query)
+
+ def check_auth(self, request):
+ """ Allow anonymous access. """
+ # By default, Lookups require request.is_staff. Don't require anything!
+ pass
+
+
class DepartmentManager(models.Manager):
""" Handle restoring data. """
def get_by_natural_key(self, name, school):
super(Department, self).save(*args, **kwargs)
+@register_channel_name('dept_given_school')
+class DeptGivenSchoolLookup(DependentLookupChannel):
+ """
+ Handles AJAX lookups against the department model's name field given a
+ school.
+ """
+ model = Department
+
+ def get_dependent_query(self, q, request, dependency):
+ """ Search against department name given a school. """
+ if dependency:
+ return Department.objects.filter(name__icontains=q,
+ school__id=dependency)
+ else:
+ return []
+
+ def check_auth(self, request):
+ """ Allow anonymous access. """
+ # By default, Lookups require request.is_staff. Don't require anything!
+ pass
+
+
class ProfessorManager(models.Manager):
""" Handle restoring data. """
def get_by_natural_key(self, name, email):
objects = CourseManager()
# Core metadata
- name = models.CharField(max_length=255)
+ name = models.CharField(max_length=255, verbose_name="Course:")
slug = models.SlugField(max_length=150, null=True)
# department should remove nullable when school gets yoinked
department = models.ForeignKey(Department, blank=True, null=True)
file_count = models.IntegerField(default=0)
desc = models.TextField(max_length=511, blank=True, null=True)
- url = models.URLField(max_length=511, blank=True, null=True)
+ url = models.URLField(max_length=511, blank=True, null=True,
+ verbose_name="Course URL:")
# instructor_* is vestigial, replaced by Professor+ProfessorTaught models.
instructor_name = models.CharField(max_length=255, blank=True, null=True)
return kwargs
+
class AboutView(TemplateView):
""" Display the About page with the Schools leaderboard """
template_name = "about.html"
return kwargs
-def school_list(request):
- """ Return JSON describing Schools that match q query on name """
- if not (request.method == 'POST' and request.is_ajax()
- and request.POST.has_key('q')):
- #return that the api call failed
- return HttpResponseBadRequest(json.dumps({'status':'fail'}), mimetype="application/json")
-
- # if an ajax get request with a 'q' name query
- # get the schools as a id name dict,
- _query = request.POST['q']
- matching_school_aliases = list(School.objects.filter(alias__icontains=_query))
- matching_school_names = list(School.objects.filter(name__icontains=_query)[:20])
- _schools = matching_school_aliases[:2] + matching_school_names
- schools = [{'id': s.id, 'name': s.name} for s in _schools]
-
- # return as json
- return HttpResponse(json.dumps({'status':'success', 'schools': schools}), mimetype="application/json")
-
-
def school_course_list(request):
"""Return JSON describing courses we know of at the given school
that match the query """
"""Record that somebody has flagged a note."""
return ajax_increment(Course, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_course_flag_events)
+
def edit_course(request, pk):
"""
Saves the edited course content
margin: 2em auto;
}
-legend
+legend, label
{
font: 12px/2em "MuseoSlab-300";
margin-top: 20px;
// page header
$('#add_course_header_button').click(addCourse);
- $("#str_school").autocomplete({
- source: function(request, response){
- $.ajax({
- url: json_school_list,
- data: {q: request.term},
- success: function(data) {
- if (data['status'] === 'success') {
- response($.map(data['schools'], function(item) {
- return {
- value: item.name,
- real_value: item.id,
- label: item.name,
- };
- }));
- } else {
- // FIXME: do something if school not found
- $('#create_school_link').show();
- }
- },
- dataType: "json",
- type: 'POST'
- });
- },
- select: function(event, ui) {
- // set the school id as the value of the hidden field
- $('#id_school').val(ui.item.real_value);
- schoolSelected = true;
- $('#str_school').removeClass('error');
- $('#save-btn').removeClass('disabled');
- fieldEdited();
- },
- change: function(event, ui) {
- if (ui.item == null) {
- $('#id_school').val('');
- schoolSelected = false;
- $('#str_school').addClass('error');
- $('#save-btn').addClass('disabled');
- fieldEdited();
- }
- },
- minLength: 3
- });
-
KARMAWORLD.Course.initCourseNameAutocomplete({
select: function(event, ui) {
courseNameSelected = true;
# Version control
'reversion',
+ # AJAX endpoints for autocompletion
+ 'ajax_select',
+ 'ajax_select_cascade',
+
'allauth',
'allauth.account',
'allauth.socialaccount',
########## END STORAGE CONFIGURATION
+########## SSL FORWARDING CONFIGURATION
+SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+########## END SSL FORWARDING CONFIGURATION
+
+
########## COMPRESSION CONFIGURATION
# See: http://django_compressor.readthedocs.org/en/latest/settings/#django.conf.settings.COMPRESS_OFFLINE
COMPRESS_OFFLINE = True
{% load url from future %}
<script>
- var json_school_list = "{% url 'json_school_list' %}"
var json_school_course_list = "{% url 'json_school_course_list' %}"
var json_school_course_instructor_list = "{% url 'json_school_course_instructor_list' %}"
var csrf_token = "{{ csrf_token }}";
<script src="{{ STATIC_URL }}js/setup-ajax.js"></script>
<script src="{{ STATIC_URL }}js/course.js"></script>
<script src="{{ STATIC_URL }}js/add-course.js"></script>
+{{ course_form.media }}
<section id="add-course-form">
<form method="POST" action="{% url 'home' %}">
</div>
</div>
-
- <div class="row">
- <div class="small-12 columns">
- <legend>
- School
- {% if course_form.school.errors %}
- <span style="color:red">
- Please select a school from the drop-down
- </span>
- {% endif %}
- </legend>
- <div>
- <input id="str_school" class="" type="text" name="str_school" placeholder="Select a school"/>
- <input id="id_school" name="school" type='hidden'/>
- </div>
- </div>
- </div> <!-- .row -->
-
- <div class="row">
- <div class="small-12 columns">
- <legend>Course Name:
- {% if course_form.name.errors %}
- <span style="color:red">
- * there was an error with this field
- </span>
- {% endif %}
- </legend>
- <input id="id_name" class="" type="text" name="name" maxlength="255" />
- </div>
- </div> <!-- .row -->
-
- <div class="row">
- <div class="small-12 columns large-6">
- <legend class="">
- Instructor Name:
- {% if course_form.instructor_name.errors %}
- <span style="color:red">
- * there was an error with this field
- </span>
- {% endif %}
- </legend><!-- -->
- <input id="id_instructor_name" class="" type="text" name="instructor_name" maxlength="75" />
- </div>
-
- <div class="small-12 columns large-6">
- <legend class="">
- Instructor Email:
- {% if course_form.instructor_email.errors %}
- <span style="color:red">
- * there was an error with this field
- </span>
- {% endif %}
- </legend>
- <input id="id_instructor_email" class="" type="text" name="instructor_email" maxlength="75" />
- </div><!-- -->
- </div> <!-- .row -->
-
- <div class="row">
- <div class="small-12 columns">
- <legend>Course url:
- {% if course_form.url.errors %}
- <span style="color:red">
- * there was an error with this field
- </span>
- {% endif %}
- </legend>
- <input id="id_url" class="" type="text" name="url" maxlength="255" />
- </div>
- </div> <!-- .row -->
+ {{ course_form }}
<div class="row">
<div class="small-4 large-8 columns small-centered text-center">
from karmaworld.apps.courses.views import AboutView, flag_course, edit_course
from karmaworld.apps.courses.views import CourseDetailView
from karmaworld.apps.courses.views import CourseListView
-from karmaworld.apps.courses.views import school_list
from karmaworld.apps.courses.views import school_course_list
from karmaworld.apps.courses.views import school_course_instructor_list
from karmaworld.apps.notes.views import NoteView, thank_note, NoteSearchView, flag_note, downloaded_note
from karmaworld.apps.document_upload.views import save_fp_upload
from karmaworld.apps.users.views import ProfileView
-# See: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#hooking-adminsite-instances-into-your-urlconf
-
+from ajax_select import urls as ajax_select_urls
+# See: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#hooking-adminsite-instances-into-your-urlconf
admin.autodiscover()
# reused named regex capture groups
SLUG = r'(?P<{0}slug>[-A-Za-z0-9_]+)'
-
"""
# ex: SLUG.format('') :> (?P<slug>[-A-Za-z0-9_]+)
# ex: SLUG.format('school_') :> (?P<school_slug>[-A-Za-z0-9_]+)
NoteView.as_view(), name='note_detail_pk'),
"""
-SCHOOL_SLUG = r'(?P<school_slug>[-A-Za-z0-9_]+)'
-COURSE_SLUG = r'(?P<course_slug>[-A-Za-z0-9_]+)'
-NOTE_SLUG = r'(?P<slug>[-A-Za-z0-9_]+)'
-
# See: https://docs.djangoproject.com/en/dev/topics/http/urls/
urlpatterns = patterns('',
## Administrative URLpatterns
url(r'^moderator/doc/', include('django.contrib.admindocs.urls')),
url(r'^moderator/', include(moderator.site.urls)),
+ # support AJAX lookup endpoints
+ url(r'^lookups/', include('ajax_select.urls')),
+
## Single-serving page URLpatterns
url(r'^terms/$', TemplateView.as_view(template_name='terms.html'), name='terms'),
url(r'^about/$', AboutView.as_view(), name='about'),
url(r'^api/upload$', save_fp_upload, name='upload_post'),
# ---- JSON views ----#
- # return json list of schools
- url(r'^school/list/$', school_list, name='json_school_list'),
# return json list of courses for a given school
url(r'^school/course/list/$', school_course_list, name='json_school_course_list'),
# return json list of instructors for a given school and course
boto==2.6.0
django-storages==1.1.4
django-reversion
+django-ajax-selects
+git+https://github.com/btbonval/django-ajax-selects-cascade.git