From: Bryan Date: Mon, 10 Feb 2014 09:21:48 +0000 (-0500) Subject: for #294 : replaced manual school_list JSON with module, moved template to Django... X-Git-Tag: release-20150131~164 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=7e400b56e7e31c92f61763c63fdb358c2db264f0;p=oweals%2Fkarmaworld.git for #294 : replaced manual school_list JSON with module, moved template to Django Form, added Department to Add Course with autocomplete dependent upon school. --- diff --git a/karmaworld/apps/courses/forms.py b/karmaworld/apps/courses/forms.py index bde2cfc..db186bb 100644 --- a/karmaworld/apps/courses/forms.py +++ b/karmaworld/apps/courses/forms.py @@ -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') diff --git a/karmaworld/apps/courses/models.py b/karmaworld/apps/courses/models.py index adbc3e2..10f7770 100644 --- a/karmaworld/apps/courses/models.py +++ b/karmaworld/apps/courses/models.py @@ -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) diff --git a/karmaworld/apps/courses/views.py b/karmaworld/apps/courses/views.py index 7ef80eb..55528a5 100644 --- a/karmaworld/apps/courses/views.py +++ b/karmaworld/apps/courses/views.py @@ -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 diff --git a/karmaworld/assets/css/global.css b/karmaworld/assets/css/global.css index 6386b43..ad4ada9 100644 --- a/karmaworld/assets/css/global.css +++ b/karmaworld/assets/css/global.css @@ -891,7 +891,7 @@ hr.midrule margin: 2em auto; } -legend +legend, label { font: 12px/2em "MuseoSlab-300"; margin-top: 20px; diff --git a/karmaworld/assets/js/add-course.js b/karmaworld/assets/js/add-course.js index a25928a..d037412 100644 --- a/karmaworld/assets/js/add-course.js +++ b/karmaworld/assets/js/add-course.js @@ -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; diff --git a/karmaworld/settings/common.py b/karmaworld/settings/common.py index eb8b18d..42c66f7 100644 --- a/karmaworld/settings/common.py +++ b/karmaworld/settings/common.py @@ -234,6 +234,10 @@ THIRD_PARTY_APPS = ( # Version control 'reversion', + # AJAX endpoints for autocompletion + 'ajax_select', + 'ajax_select_cascade', + 'allauth', 'allauth.account', 'allauth.socialaccount', diff --git a/karmaworld/settings/vmdev.py b/karmaworld/settings/vmdev.py index caf300a..2d873fd 100644 --- a/karmaworld/settings/vmdev.py +++ b/karmaworld/settings/vmdev.py @@ -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 diff --git a/karmaworld/templates/partial/add_course.html b/karmaworld/templates/partial/add_course.html index 2938f5b..2d43e5e 100644 --- a/karmaworld/templates/partial/add_course.html +++ b/karmaworld/templates/partial/add_course.html @@ -1,6 +1,5 @@ {% load url from future %} +{{ course_form.media }}
@@ -19,75 +19,7 @@ - -
-
- - School - {% if course_form.school.errors %} - - Please select a school from the drop-down - - {% endif %} - -
- - -
-
-
- -
-
- Course Name: - {% if course_form.name.errors %} - - * there was an error with this field - - {% endif %} - - -
-
- -
-
- - Instructor Name: - {% if course_form.instructor_name.errors %} - - * there was an error with this field - - {% endif %} - - -
- -
- - Instructor Email: - {% if course_form.instructor_email.errors %} - - * there was an error with this field - - {% endif %} - - -
-
- -
-
- Course url: - {% if course_form.url.errors %} - - * there was an error with this field - - {% endif %} - - -
-
+ {{ course_form }}
diff --git a/karmaworld/urls.py b/karmaworld/urls.py index 339bbd5..493e1e3 100644 --- a/karmaworld/urls.py +++ b/karmaworld/urls.py @@ -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[-A-Za-z0-9_]+) # ex: SLUG.format('school_') :> (?P[-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[-A-Za-z0-9_]+)' -COURSE_SLUG = r'(?P[-A-Za-z0-9_]+)' -NOTE_SLUG = r'(?P[-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 diff --git a/reqs/common.txt b/reqs/common.txt index 9a058cf..62168cf 100644 --- a/reqs/common.txt +++ b/reqs/common.txt @@ -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