Notes have categories, fixes #367
authorCharles Connell <charles@connells.org>
Wed, 30 Apr 2014 17:47:01 +0000 (13:47 -0400)
committerCharles Connell <charles@connells.org>
Wed, 30 Apr 2014 17:47:01 +0000 (13:47 -0400)
12 files changed:
karmaworld/apps/courses/views.py
karmaworld/apps/document_upload/forms.py
karmaworld/apps/document_upload/views.py
karmaworld/apps/notes/migrations/0018_auto__add_field_note_category.py [new file with mode: 0644]
karmaworld/apps/notes/models.py
karmaworld/assets/css/note_course_pages.css
karmaworld/assets/js/filepicker.js
karmaworld/assets/js/note-list.js
karmaworld/templates/courses/course_detail.html
karmaworld/templates/notes/note_detail.html
karmaworld/templates/notes/note_list_entry.html
karmaworld/templates/partial/filepicker.html

index 6ba463241a32644e1be3d82785645c4125627269..b6d6fc8b5b8817c4cd5967dcad764699785c7cd9 100644 (file)
@@ -116,6 +116,7 @@ class CourseDetailView(DetailView):
 
         # For the Filepicker Partial template
         kwargs['file_upload_form'] = FileUploadForm()
+        kwargs['note_categories'] = Note.NOTE_CATEGORIES
 
         if self.request.user.is_authenticated():
             try:
index 1484367a7d6dae1fee96615cab9991a6817fc0a3..15afdbb5b26e472d296fe36c1c95503c1e4dc2bc 100644 (file)
@@ -9,4 +9,4 @@ from karmaworld.apps.document_upload.models import RawDocument
 class RawDocumentForm(ModelForm):
     class Meta:
         model = RawDocument
-        fields = ('name', 'tags', 'course', 'fp_file', 'mimetype')
+        fields = ('name', 'tags', 'course', 'fp_file', 'mimetype', 'category')
index 98b21e417097d49ba6162c202777962820e80c19..bee94b8960cdac1c8b3a839df02744aaf4db654e 100644 (file)
@@ -22,7 +22,6 @@ def save_fp_upload(request):
         raw_document.ip = request.META['REMOTE_ADDR']
         raw_document.uploaded_at = datetime.datetime.utcnow()
 
-        # note that .save() has the side-effect of kicking of a celery processing task
         if request.user.is_authenticated():
             raw_document.save()
         else:
diff --git a/karmaworld/apps/notes/migrations/0018_auto__add_field_note_category.py b/karmaworld/apps/notes/migrations/0018_auto__add_field_note_category.py
new file mode 100644 (file)
index 0000000..4fdf748
--- /dev/null
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as 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 'Note.category'
+        db.add_column(u'notes_note', 'category',
+                      self.gf('django.db.models.fields.CharField')(max_length=50, null=True, blank=True),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'Note.category'
+        db.delete_column(u'notes_note', 'category')
+
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        u'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        u'courses.course': {
+            'Meta': {'ordering': "['-file_count', 'school', 'name']", 'unique_together': "(('name', 'school'),)", 'object_name': 'Course'},
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'department': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.Department']", 'null': 'True', 'blank': 'True'}),
+            'desc': ('django.db.models.fields.TextField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}),
+            'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'flags': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'instructor_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'instructor_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'professor': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['courses.Professor']", 'null': 'True', 'blank': 'True'}),
+            'school': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.School']", 'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}),
+            'updated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'})
+        },
+        u'courses.department': {
+            'Meta': {'unique_together': "(('name', 'school'),)", 'object_name': 'Department'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'school': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.School']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'})
+        },
+        u'courses.professor': {
+            'Meta': {'unique_together': "(('name', 'email'),)", 'object_name': 'Professor'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        u'courses.school': {
+            'Meta': {'ordering': "['-file_count', '-priority', 'name']", 'object_name': 'School'},
+            'alias': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'facebook_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'hashtag': ('django.db.models.fields.CharField', [], {'max_length': '16', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'priority': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'blank': 'True'}),
+            'usde_id': ('django.db.models.fields.BigIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        u'licenses.license': {
+            'Meta': {'object_name': 'License'},
+            'html': ('django.db.models.fields.TextField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'})
+        },
+        u'notes.note': {
+            'Meta': {'ordering': "['-uploaded_at']", 'unique_together': "(('fp_file', 'upstream_link'),)", 'object_name': 'Note'},
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'course': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.Course']"}),
+            'flags': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'fp_file': ('django_filepicker.models.FPFileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'gdrive_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
+            'is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'license': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['licenses.License']", 'null': 'True', 'blank': 'True'}),
+            'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
+            'text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'thanks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'tweeted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'null': 'True'}),
+            'upstream_link': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'})
+        },
+        u'notes.notemarkdown': {
+            'Meta': {'object_name': 'NoteMarkdown'},
+            'markdown': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'note': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['notes.Note']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        u'notes.useruploadmapping': {
+            'Meta': {'unique_together': "(('user', 'fp_file'),)", 'object_name': 'UserUploadMapping'},
+            'fp_file': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+        },
+        u'taggit.tag': {
+            'Meta': {'object_name': 'Tag'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        u'taggit.taggeditem': {
+            'Meta': {'object_name': 'TaggedItem'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
+        }
+    }
+
+    complete_apps = ['notes']
\ No newline at end of file
index d3933d971a668045552ec5ab8224a4b59f20b999..3f8e9f05821dad53c8d0b28a247d51468e7f75db 100644 (file)
@@ -64,7 +64,20 @@ class Document(models.Model):
     tags            = TaggableManager(blank=True)
     name            = models.CharField(max_length=255, blank=True, null=True)
     slug            = models.SlugField(max_length=255, unique=True)
-    
+
+    LECTURE_NOTES = 'LECTURE_NOTES'
+    STUDY_GUIDE = 'STUDY_GUIDE'
+    SYLLABUS = 'SYLLABUS'
+    ASSIGNMENT = 'ASSIGNMENT'
+    OTHER = 'OTHER'
+    NOTE_CATEGORIES = (
+        (LECTURE_NOTES, 'Lecture Notes'),
+        (STUDY_GUIDE, 'Study Guide'),
+        (SYLLABUS, 'Syllabus'),
+        (ASSIGNMENT, 'Assignment'),
+        (OTHER, 'Other'),
+    )
+    category = models.CharField(max_length=50, choices=NOTE_CATEGORIES, blank=True, null=True)
 
     # license if different from default
     license         = models.ForeignKey(License, blank=True, null=True)
index 0bd7a7fdbf5a16e8a869969030f31d71edc301cc..eca5bbd925ec28bc77e635d10be7a0cfb106988f 100644 (file)
@@ -137,3 +137,7 @@ button.add-note-btn {
   font-size: 0.8em;
 }
 
+#note-category {
+  margin-bottom: 10px;
+}
+
index b588ffba4a489c1fe03540e8ca8d5d93e441a2b9..88b255358c8e878d33c6c729edd22e0c23fa4838 100644 (file)
@@ -50,6 +50,7 @@ $(function(){
         name = $(el).find('.intext').val();
         fp_file = $(el).find('.fpurl').val();
         tags = $(el).find('.taggit-tags').val();
+        category = $(el).find('.category').val();
         course = $(el).find('.course_id').val();
         csrf = $(el).find('.csrf').val();
         email = $('#id_email').val();
@@ -59,6 +60,7 @@ $(function(){
           'name': name,
           'fp_file': fp_file,
           'tags': tags,
+          'category': category,
           'course': course,
           'csrfmiddlewaretoken': csrf,
           'mimetype': mimetype,
index 9022f0118912c98adf5ee901c9c8e1cbe100c134..4e351e17004fbfa4fbd8488cbaadc75f0fccfcd3 100644 (file)
@@ -9,21 +9,6 @@ $(function() {
     'iDisplayLength': 20,
     // Position the filter bar at the top
     'sDom': dataTable_sDom,
-    // Specify options for each column
-    "aoColumnDefs": [
-      {
-        // 2nd element: thanks
-        "aTargets": [ 1 ],
-        "bSortable": true,
-        "bVisible": true
-      },
-      {
-        // 1st element: date
-        "aTargets": [ 0 ],
-        "bSortable": true,
-        "bVisible": true
-      }
-    ],
     // Initial sorting
     'aaSorting': [[1,'desc']]
   });
@@ -37,5 +22,15 @@ $(function() {
     // sort by current value of sort chooser, since
     // the browser may change this from our default
     dataTable.fnSort([[$('select.note-sort').val(), 'desc']]);
+
+    $('select.note-category').change(function() {
+      var category = $(this).val();
+      if (category === 'ALL') {
+        dataTable.fnFilter('');
+      } else {
+        dataTable.fnFilter(category);
+      }
+    });
+
   }
 });
index 72df39bb77dc3820d7a8b738912e1a93a9e6b3d7..a235aef163424a6b415ba396d2084b487ae365ce 100644 (file)
     </div>
 
     <div class="row filter-options show-for-large-up">
-      <div class="large-3 columns end">
+      <div class="large-3 columns">
         <div class="sort-label">Sort By</div>
         <div class="select-wrapper">
           <select class="note-sort">
           </select>
         </div>
       </div>
+
+      <div class="large-3 columns end">
+        <div class="sort-label">Category</div>
+        <div class="select-wrapper">
+          <select class="note-category">
+            <option value="ALL" selected>All Categories</option>
+            {% for category in note_categories %}
+              <option value="{{ category.0 }}">{{ category.1 }}</option>
+            {% endfor %}
+          </select>
+        </div>
+      </div>
     </div>
 
     <div id="course_container">
                 <tr>
                   <th class="no-display" id="data-table-date"> Date </th>
                   <th class="no-display" id="data-table-thanks"> Popularity </th>
+                  <th class="no-display" id="data-table-category"> Category </th>
                   <th class="no-display" id="data-table-note"> Note </th>
                 </tr>
               </thead>
index 4b2d6169dad0813b597b16e7363723fcae288c88..a08222fc516ae5eced424390752b41917e5da6c6 100644 (file)
         </div>
       </div>
 
+      {% if note.category %}
+      <div id="note-category" class="row show-for-large-up">
+        <div class="small-12 columns">
+          <em>{{ note.get_category_display }}</em>
+        </div>
+      </div>
+      {% endif %}
+
       <div id="note-tags" class="row show-for-large-up">
         <div class="small-12 columns">
           <strong>Tags: </strong>
index 2675858d5de0494d2c25f4d6ee3140a5a3997a8d..ebdc9a0a6047d1ceb84113804563504d6a9286d8 100644 (file)
@@ -1,6 +1,7 @@
 <tr class="table-row">
   <td class="hide">{{ note.uploaded_at|date:"U" }}</td>
   <td class="hide">{{ note.thanks|stringformat:"010g" }}</td>
+  <td class="hide">{{ note.category }}</td>
 
   <td class="data-table-entry-wrapper">
     <div class="data-table-entry">
       {% else %}
         <div class="note-text show-for-large-up">{{ note.text|slice:":500" }} &hellip;</div>
       {% endif %}
+      <div class="note-category show-for-large-up">
+        {% if note.category %}
+          <em>{{ note.get_category_display }}</em>
+        {% endif %}
+      </div>
       <div class="note-tags show-for-large-up">
         {% if note.tags.count > 0 %}
-          <span class="activity_details_context"><strong>Tags:</strong>
-            {% for tag in note.tags.all %}
-              {{ tag.name }}{% if not forloop.last %}, {% endif %}
-            {% endfor %}
-          </span>
+          <strong>Tags:</strong>
+          {% for tag in note.tags.all %}
+            {{ tag.name }}{% if not forloop.last %}, {% endif %}
+          {% endfor %}
         {% endif %}
       </div>
     </div>
index 3183ac8c8648b916474763f43e303086a5e806c5..933e8aecf0e155573d6d03a80b7d8512fc108160 100644 (file)
 
     <div id="form-template" class="hide form-template">
       <form class="inline-form" method="POST" action="{% url 'upload_post' %}">
-        <div class="small-12 large-6 columns">
+        <div class="small-12 large-5 columns">
           <label for="id_name">Title of Note <small class="form">* Required</small></label>
           <input type="text" class="intext" id="id_name" name="name"
                 placeholder="">
         </div>
-        <div class="small-12 large-5 columns">
+        <div class="small-12 large-3 columns">
+          <label for="id_category">Category <small class="form">* Required</small></label>
+          <div class="select-wrapper">
+            <select id="id_category" name="category" class="category">
+              {% for category in note_categories %}
+                <option value="{{ category.0 }}">{{ category.1 }}</option>
+              {% endfor %}
+            </select>
+          </div>
+        </div>
+        <div class="small-12 large-3 columns">
           <label for="id_tags">Tags (comma seperated)</label>
           <input type="text" id="id_tags" name="tags" class="taggit-tags"
                 placeholder="shakespeare, econ, physics">