for #294 : replaced manual school_list JSON with module, moved template to Django...
authorBryan <btbonval@gmail.com>
Mon, 10 Feb 2014 09:21:48 +0000 (04:21 -0500)
committerBryan <btbonval@gmail.com>
Mon, 10 Feb 2014 09:21:48 +0000 (04:21 -0500)
karmaworld/apps/courses/forms.py
karmaworld/apps/courses/models.py
karmaworld/apps/courses/views.py
karmaworld/assets/css/global.css
karmaworld/assets/js/add-course.js
karmaworld/settings/common.py
karmaworld/settings/vmdev.py
karmaworld/templates/partial/add_course.html
karmaworld/urls.py
reqs/common.txt

index bde2cfccd7821b944adb5c6aa250f65719d9ad04..db186bb8cb14832645fb7f18563d885c02b9c733 100644 (file)
@@ -4,11 +4,30 @@
 
 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')
index adbc3e2d3988e5ec28b4e6fd9de9ed5e2a48c39c..10f7770972b7c33928667dc67276dbf23d56461a 100644 (file)
@@ -14,6 +14,11 @@ import reversion
 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):
@@ -75,6 +80,24 @@ class School(models.Model):
         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):
@@ -118,6 +141,28 @@ class Department(models.Model):
         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):
@@ -205,7 +250,7 @@ class Course(models.Model):
     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)
@@ -215,7 +260,8 @@ class Course(models.Model):
     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)
index 7ef80ebf7e1685f774a5d17aedcd0d1197741eb7..55528a5fc363fcd939f20ab42787abea6b71fc5b 100644 (file)
@@ -91,6 +91,7 @@ class CourseDetailView(DetailView):
 
         return kwargs
 
+
 class AboutView(TemplateView):
     """ Display the About page with the Schools leaderboard """
     template_name = "about.html"
@@ -102,25 +103,6 @@ class AboutView(TemplateView):
         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 """
@@ -206,6 +188,7 @@ def flag_course(request, pk):
     """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
index 6386b43c3d404c8d12e9f3ab6004202df1c2153c..ad4ada9dab20818885bd5f800e8ce30cd40c7d75 100644 (file)
@@ -891,7 +891,7 @@ hr.midrule
   margin: 2em auto;
 }
 
-legend
+legend, label
 {
   font: 12px/2em "MuseoSlab-300";
   margin-top: 20px;
index a25928a48fa6fb947dadbde2a44bc34bb278eead..d03741254e37515b33291be9861889155143de2f 100644 (file)
@@ -38,49 +38,6 @@ $(function() {
   // 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;
index eb8b18dba1bfa9d67acdc8af80ab473b9e86cfb5..42c66f71737e7b924b4eb0c3c76125e32c52b365 100644 (file)
@@ -234,6 +234,10 @@ THIRD_PARTY_APPS = (
     # Version control
     'reversion',
 
+    # AJAX endpoints for autocompletion
+    'ajax_select',
+    'ajax_select_cascade',
+
     'allauth',
     'allauth.account',
     'allauth.socialaccount',
index caf300ad3245c73243ffff13e10d42c1d131de3b..2d873fdfe89a38750b0f1074df5b7d9aa6effdeb 100644 (file)
@@ -144,6 +144,11 @@ STATIC_URL = S3_URL
 ########## 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
index 2938f5be9188af3c395584e64dc497fcaba3bfe2..2d43e5e65b11bfcfb4adeaf901fc21db0fde68ef 100644 (file)
@@ -1,6 +1,5 @@
 {% 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 }}";
@@ -8,6 +7,7 @@
 <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">
index 339bbd50c9829d09770e1c6dd667d92b77b37cad..493e1e3bfa28296c462a11d4168eab2f2de07af9 100644 (file)
@@ -12,7 +12,6 @@ from karmaworld.apps.courses.models import Course
 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
@@ -22,14 +21,13 @@ from karmaworld.apps.moderation import moderator
 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_]+)
@@ -42,10 +40,6 @@ SLUG = r'(?P<{0}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
@@ -58,6 +52,9 @@ urlpatterns = patterns('',
     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'),
@@ -77,8 +74,6 @@ urlpatterns = patterns('',
     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
index 9a058cf9cada881b67628e85a23ecb8e8d92af1e..62168cfa89acecc8cf074cf02f6d315295303101 100644 (file)
@@ -22,3 +22,5 @@ django-allauth
 boto==2.6.0
 django-storages==1.1.4
 django-reversion
+django-ajax-selects
+git+https://github.com/btbonval/django-ajax-selects-cascade.git