208 - Edit course properties - first pass
authorJacob Hilker <hilker.j@gmail.com>
Tue, 28 Jan 2014 22:37:43 +0000 (17:37 -0500)
committerJacob Hilker <hilker.j@gmail.com>
Sat, 1 Feb 2014 21:39:56 +0000 (16:39 -0500)
karmaworld/apps/courses/views.py
karmaworld/assets/css/global.css
karmaworld/assets/js/add-course.js
karmaworld/assets/js/course-detail.js
karmaworld/assets/js/course.js [new file with mode: 0644]
karmaworld/templates/courses/course_detail.html
karmaworld/templates/partial/add_course.html
karmaworld/urls.py
karmaworld/utils/ajax_increment.py

index bb12926f020db67297dd48f6aa88c8cc85db44b9..a6b9c05a59a608ac5fa35e37c6e11402282be33c 100644 (file)
@@ -5,6 +5,7 @@
 
 import json
 
+from django.core import serializers
 from django.core.exceptions import MultipleObjectsReturned
 from django.core.exceptions import ObjectDoesNotExist
 
@@ -20,7 +21,7 @@ from karmaworld.apps.courses.models import Course
 from karmaworld.apps.courses.models import School
 from karmaworld.apps.notes.models import Note
 from karmaworld.apps.users.models import CourseKarmaEvent
-from karmaworld.utils import ajax_increment, format_session_increment_field
+from karmaworld.utils.ajax_increment import *
 
 FLAG_FIELD = 'flags'
 
@@ -81,7 +82,6 @@ class CourseDetailView(DetailView):
 
         return kwargs
 
-
 class AboutView(TemplateView):
     """ Display the About page with the Schools leaderboard """
     template_name = "about.html"
@@ -197,3 +197,29 @@ def flag_course(request, pk):
     """Record that somebody has flagged a note."""
     return ajax_increment(Course, request, pk, FLAG_FIELD, process_course_flag_events)
 
+def edit_course(request, pk):
+    """
+    Saves the edited course content
+    """
+    course = Course.objects.get(pk=pk)
+    course_form = CourseForm(request.POST or None, instance=course)
+
+    if request.method == "POST" and request.is_ajax():
+        if course_form.is_valid():
+
+            # This will fail if name and school match one that already exists
+            # Same happens for add, should add specialized validation?
+            course_form.save()
+
+            # Make a helper for this? Kinda Ugly...
+            course_json = serializers.serialize('json', [course,])
+            return HttpResponse(json.dumps(json.loads(course_json)[0]),
+                                mimetype="application/json")
+        else:
+            print course_form.errors
+            return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Validation error',
+                                          'errors': course_form.errors}),
+                                          mimetype="application/json")
+    else:
+        return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
+                                      mimetype="application/json")
index 0116f235534e270a704d17b23b10a241a9a46dd3..208c14f2d11755fe779b3219aafa414888207c2c 100644 (file)
@@ -36,6 +36,10 @@ input[type="text"]:focus, textarea:focus
   border-radius: 0;
 }
 
+.hidden {
+  display:none;
+}
+
 
 .half-padding
 {
@@ -614,7 +618,8 @@ a.activity_target:hover
 #add-note-btn,
 #add-course-btn,
 #existing-course-btn,
-#save-btn
+#save-btn,
+#edit-save-btn
 {
   border: none;
   background-color: white;
@@ -628,18 +633,24 @@ a.activity_target:hover
 }
 
 #existing-course-btn,
-#save-btn {
+#save-btn,
+#edit-save-btn {
   margin-top: 13px;
 }
 
 #existing-course-btn.disabled,
-#save-btn.disabled
+#save-btn.disabled,
+#edit-save-btn.disabled
 {
   color: #afafaf;
   cursor: auto;
 }
 
-#add-note-form, #add-course-form
+.validation_error {
+  color:red;
+}
+
+#add-note-form, #add-course-form, #edit-course-form
 {
   display: none;
 }
index 5253db18f9443a89772b2539ba68204528aaab8e..a25928a48fa6fb947dadbde2a44bc34bb278eead 100644 (file)
@@ -17,7 +17,7 @@ $(function() {
       $('#save-btn').removeClass('disabled');
       $('#existing-course-msg').hide();
     }
-  }
+  };
 
   addCourse = function() {
     // Show the add a course form
@@ -81,26 +81,7 @@ $(function() {
     minLength: 3
   });
 
-  $("#id_name").autocomplete({
-    source: function(request, response){
-      var school_id = $('#id_school').val();
-      $.ajax({
-        url: json_school_course_list,
-        data: {q: request.term, school_id: school_id},
-        success: function(data) {
-          if (data['status'] === 'success') {
-            response($.map(data['courses'], function(item) {
-              return {
-                  value: item.name,
-                  label: item.name,
-              };
-            }));
-          }
-        },
-        dataType: "json",
-        type: 'POST'
-      });
-    },
+  KARMAWORLD.Course.initCourseNameAutocomplete({
     select: function(event, ui) {
       courseNameSelected = true;
       fieldEdited();
@@ -110,33 +91,10 @@ $(function() {
         courseNameSelected = false;
         fieldEdited();
       }
-    },
-    minLength: 3
+    }
   });
 
-  $("#id_instructor_name").autocomplete({
-    source: function(request, response) {
-      var school_id = $('#id_school').val();
-      var course_name = $('#id_name').val();
-      $.ajax({
-        url: json_school_course_instructor_list,
-        data: {q: request.term, school_id: school_id, course_name: course_name},
-        success: function(data) {
-          if (data['status'] === 'success') {
-            // Fill in the autocomplete entries
-            response($.map(data['instructors'], function(item) {
-              return {
-                  value: item.name,
-                  label: item.name,
-                  url:   item.url
-              };
-            }));
-          }
-        },
-        dataType: "json",
-        type: 'POST'
-      });
-    },
+  KARMAWORLD.Course.initInstructorNameAutocomplete({
     select: function(event, ui) {
       instructorSelected = true;
       $('#existing-course-btn').attr('href', ui.item.url);
@@ -148,9 +106,7 @@ $(function() {
         $('#existing-course-btn').attr('href', '');
         fieldEdited();
       }
-    },
-    minLength: 3
+    }
   });
 
-
 });
index c0149a59374cf1135e5c51d030b49632bc36979c..9c1d089d0af0c09486ab877baf26b185d7f183d4 100644 (file)
@@ -1,6 +1,5 @@
-
 $(function() {
-  $("#flag-button").click(function(event) {
+  $('#flag-button').click(function(event) {
     event.preventDefault();
 
     if (confirm('Do you wish to flag this course for deletion?')) {
@@ -14,9 +13,69 @@ $(function() {
       // this note
       $.ajax({
         url: course_flag_url,
-        dataType: "json",
+        dataType: 'json',
         type: 'POST'
       });
     }
   });
+
+  $('#edit-button').click(function(event) {
+    $('#edit-course-form').slideToggle();
+  });
+
+  $('#edit-save-btn').click(function(event) {
+    $.ajax({
+      url: course_edit_url,
+      dataType: 'json',
+      data: $('#edit-course-form').children().serialize(),
+      type: 'POST',
+      success: function(data) {
+        // We might want to use a template here instead of rehashing logic
+        // on both the client and server side
+        $('#edit-course-form').slideUp();
+        $('.validation_error').remove()
+        $('#course_form_errors').text('');
+        $('#course_name').text(data.fields.name);
+        $('#course_instructor_name').text(data.fields.instructor_name);
+
+        var $externalLinkSquare = $('<i>', {'class': 'fa fa-external-link-square'});
+        $('#course_url').text(data.fields.url.slice(0, 50) + ' ');
+        $('#course_url').append($externalLinkSquare);
+        $('#course_url').attr('href', data.fields.url);
+        if (data.fields.url === '') {
+          $('#course_link').parent().hide();
+        } else {
+          $('#course_link').parent().show();
+        }
+      },
+      error: function(resp) {
+        var json = JSON.parse(resp.responseText);
+        var errors = json.errors;
+
+        // Delete all errors that currently exist
+        $('.validation_error').remove()
+
+        // Failed responses with no errors -> display message
+        if (!errors) {
+          $('#course_form_errors').text(json.message);
+        } else {
+          if (errors.instructor_email) {
+            $.each(errors.instructor_email, function(index, value) {
+              $('#id_instructor_email').parent().children('legend').append($('<span>', { class: 'validation_error', text: value }));
+            });
+          }
+
+          if (errors.url) {
+            $.each(errors.url, function(index, value) {
+              $('#id_url').parent().children('legend').append($('<span>', { class: 'validation_error' , text: value }));
+            });
+          }
+        }
+      }
+    });
+  });
+
+  KARMAWORLD.Course.initCourseNameAutocomplete({});
+  KARMAWORLD.Course.initInstructorNameAutocomplete({});
+
 });
diff --git a/karmaworld/assets/js/course.js b/karmaworld/assets/js/course.js
new file mode 100644 (file)
index 0000000..b0ddc76
--- /dev/null
@@ -0,0 +1,58 @@
+window.KARMAWORLD = window.KARMAWORLD ||  {};
+window.KARMAWORLD.Course = {
+  initCourseNameAutocomplete: function(autocompleteOpts) {
+    var opts = $.extend( {}, autocompleteOpts, {
+      source: function(request, response){
+        var school_id = $('#id_school').val();
+        $.ajax({
+          url: json_school_course_list,
+          data: {q: request.term, school_id: school_id},
+          success: function(data) {
+            if (data['status'] === 'success') {
+              response($.map(data['courses'], function(item) {
+                return {
+                    value: item.name,
+                    label: item.name,
+                };
+              }));
+            }
+          },
+          dataType: "json",
+          type: 'POST'
+        });
+      },
+      minLength: 3
+    });
+    $("#id_name").autocomplete(opts);
+  },
+
+  initInstructorNameAutocomplete: function(autocompleteOpts) {
+    var opts = $.extend( {}, autocompleteOpts, {
+      source: function(request, response) {
+        var school_id = $('#id_school').val();
+        var course_name = $('#id_name').val();
+        $.ajax({
+          url: json_school_course_instructor_list,
+          data: {q: request.term, school_id: school_id, course_name: course_name},
+          success: function(data) {
+            if (data['status'] === 'success') {
+              // Fill in the autocomplete entries
+              response($.map(data['instructors'], function(item) {
+                return {
+                    value: item.name,
+                    label: item.name,
+                    url:   item.url
+                };
+              }));
+            }
+          },
+          dataType: "json",
+          type: 'POST'
+        });
+      },
+      minLength: 3
+    });
+
+    $("#id_instructor_name").autocomplete(opts);
+  }
+};
index f539f02e0665bb8fd0e03466bfdd273b1fc2b2f9..61ccbee3747ebe4c0ad6ba7b4ec9bdf9760fedd6 100644 (file)
@@ -5,14 +5,17 @@
 {% block pagescripts %}
   <script src="{{ STATIC_URL }}js/bootstrap-modal.js" ></script>
   <script src="{{ STATIC_URL }}js/setup-ajax.js"></script>
+  <script src="{{ STATIC_URL }}js/course.js"></script>
   <script src="{{ STATIC_URL }}js/course-detail.js" ></script>
   <script>
+    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 }}";
-    var course_flag_url = "{% url 'flag_course' course.id %}"
+    var course_flag_url = "{% url 'flag_course' course.id %}";
+    var course_edit_url = "{% url 'edit_course' course.id %}";
   </script>
 {% endblock %}
 
-
 {% block pagestyle %}
   <link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/note_course_pages.css">
 {% endblock %}
 
     <div id="course_header" class="hero_gradient_bar">
       <div class="row">
-        <div class="small-12 columns header_title">
+        <div id="course_name" class="small-12 columns header_title">
           {{ course.name }}
-        </div><!-- /note_name -->
+        </div><!-- /course_name -->
       </div>
 
       <div class="row">
         <div id="course_meta" class="twelve columns">
           <div class="activity_details_context">
-            {{ course.instructor_name }} {% if course.department %}// {{ course.department.name }}{% endif %}
+            <span id="course_instructor_name">{{ course.instructor_name }}</span>
+            <span id="course_department">{% if course.department %}// {{ course.department.name }}{% endif %}</span>
           </div><!-- /activity_details_context -->
         </div><!-- /course_meta -->
       </div>
         </div><!-- /course_meta -->
       </div>
 
-      {% if course.url %}
-        <div class="row">
-          <div id="course_link" class="twelve columns">
-            <div class="activity_details_context">
-              <a rel="nofollow" target="_blank" href="{{ course.url }}">
-                {{ course.url|slice:":50" }}
-                <i class="fa fa-external-link-square"></i>
-              </a>
-            </div><!-- /activity_details_context -->
-          </div><!-- /course_meta -->
-        </div>
-      {% endif %}
+      <div class="row{% if not course.url %} hidden{% endif %}">
+        <div id="course_link" class="twelve columns">
+          <div class="activity_details_context">
+            <a id="course_url" rel="nofollow" target="_blank" href="{{ course.url }}">
+              {{ course.url|slice:":50" }}
+              <i class="fa fa-external-link-square"></i>
+            </a>
+          </div><!-- /activity_details_context -->
+        </div><!-- /course_meta -->
+      </div>
 
-        <div class="row">
+      <div class="row">
         <div id="course_actions" class="large-3 medium-6 small-12 columns small-centered">
           <div class="row">
             <div class="small-12 column center">
+              <a href="#" id="edit-button"><img src="{{ STATIC_URL }}img/note_thank.png" alt="edit_flag" width="25" height="35"/></a>
               <a href="#" id="flag-button" {% if already_flagged %} class="hide" {% endif %}><img src="{{ STATIC_URL }}img/note_flag.png" alt="note_flag" width="25" height="35"/></a>
               <a href="#" id="flag-button-disabled" {% if not already_flagged %} class="hide" {% endif %}><img src="{{ STATIC_URL }}img/note_flag_disabled.png" alt="note_flag" width="25" height="35"/></a>
             </div>
           </div>
-        </div><!-- /note_actions -->
+        </div><!-- /course_actions -->
       </div>
     </div><!-- /course_header -->
 
+    <section id="edit-course-form">
+      <form>
+        {% csrf_token %}
+
+        <div class="row">
+          <div id="course_form_errors" class="small-12 columns">
+            {{ course_form.non_field_errors }}
+          </div>
+        </div>
 
+        <div class="row hidden">
+          <div class="small-12 columns">
+            <legend>School</legend>
+            <div>
+              <input id="id_school" value="{{ course.school.id }}" name="school" type='hidden'/>
+            </div>
+          </div>
+        </div> <!-- .row -->
+
+        <div class="row">
+          <div class="small-12 columns">
+            <legend>Course Name:</legend>
+            <input id="id_name" class="" type="text" name="name" maxlength="255" value="{{ course.name }}"/>
+          </div>
+        </div> <!-- .row -->
+
+        <div class="row">
+          <div class="small-12 columns large-6">
+            <legend>Instructor Name:</legend>
+            <input id="id_instructor_name" class="" type="text" name="instructor_name" maxlength="75" value="{{ course.instructor_name }}"/>
+          </div>
+
+          <div class="small-12 columns large-6">
+            <legend>Instructor Email:</legend>
+            <input id="id_instructor_email" class="" type="text" name="instructor_email" maxlength="75" value="{{ course.instructor_email }}"/>
+          </div>
+        </div> <!-- .row -->
+
+        <div class="row">
+          <div class="small-12 columns">
+            <legend>Course url:</legend>
+            <input id="id_url" class="" type="text" name="url" maxlength="255" value="{{ course.url }}"/>
+          </div>
+        </div> <!-- .row -->
+
+        <div class="row">
+          <div class="small-4 large-8 columns small-centered text-center">
+            <button id="edit-save-btn" type=button><i class="fa fa-save"></i> Save</button>
+          </div>
+        </div>
+      </form>
+    </section>
 
     <div class="row">
       <div class="small-10 columns small-offset-1"> <hr/> </div>
       </div>
     </div>
     {% endcomment %}
-  </section><!--/note_content-->
+  </section><!--/course_content-->
+
 {% endblock %}
index 3da42b5349b71f53962c42bc31e86abed9bcb7cb..2938f5be9188af3c395584e64dc497fcaba3bfe2 100644 (file)
@@ -5,7 +5,8 @@
   var json_school_course_instructor_list = "{% url 'json_school_course_instructor_list' %}"
   var csrf_token = "{{ csrf_token }}";
 </script>
-  <script src="{{ STATIC_URL }}js/setup-ajax.js"></script>
+<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>
 
 <section id="add-course-form">
@@ -38,7 +39,7 @@
 
     <div class="row">
       <div class="small-12 columns">
-        <legend>Course Name: 
+        <legend>Course Name:
           {% if course_form.name.errors %}
             <span style="color:red">
             * there was an error with this field
index ee571f234968cd3e42d701d5b5366fa920ef5664..339bbd50c9829d09770e1c6dd667d92b77b37cad 100644 (file)
@@ -9,7 +9,7 @@ from django.conf.urls import patterns, include, url
 from django.views.generic.base import TemplateView
 
 from karmaworld.apps.courses.models import Course
-from karmaworld.apps.courses.views import AboutView, flag_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
@@ -100,6 +100,8 @@ urlpatterns = patterns('',
     url(r'^ajax/note/downloaded/(?P<pk>[\d]+)/$', downloaded_note, name='downloaded_note'),
     # Ajax endpoint to flag a course
     url(r'^ajax/course/flag/(?P<pk>[\d]+)/$', flag_course, name='flag_course'),
+    # Ajax endpoint to edit a course
+    url(r'^ajax/course/edit/(?P<pk>[\d]+)/$', edit_course, name='edit_course'),
 
     # Valid url cases to the Note page
     # a: school/course/id
index 2e6e4668afbc763a4dfdd0e349a07b06ce9f4a3a..9e705550a24ac5c2a5403569f998cfe80daaf5ff 100644 (file)
@@ -39,4 +39,3 @@ def ajax_increment(cls, request, pk, field, event_processor=None):
         request.session[format_session_increment_field(cls, pk, field)] = True
 
     return ajax_base(cls, request, pk, ajax_increment_work)
-