From 6d7e77d00f8b0b4cc5d44db415fccd9a6af234de Mon Sep 17 00:00:00 2001 From: Charles Holbrow Date: Tue, 18 Dec 2012 14:50:15 -0500 Subject: [PATCH 01/29] Incomplete refactor -- renaming Files Model to notes Changed UserProfile.files to UserProfile.viewed_notes -- may not have fixed this in all places Some very messy naming still exists in utils.py and forms.py. for example: file = Note.objects.all() At this point I ran the following forwards migration, and things are working. suspect there are a lot of bugs still still # Removing M2M table for field tags on 'File' db.delete_table('notes_file_tags') # Adding M2M table for field tags on 'Note' db.create_table('notes_file_tags', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), ('note', models.ForeignKey(orm['notes.note'], null=False)), ('tag', models.ForeignKey(orm['notes.tag'], null=False)) )) db.create_unique('notes_file_tags', ['note_id', 'tag_id']) # Changing field 'Vote.note' db.alter_column('notes_vote', 'note_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['notes.Note'])) # Changing field 'ReputationEvent.file' db.alter_column('notes_reputationevent', 'file_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['notes.Note'], null=True)) --- notes/admin.py | 2 +- notes/forms.py | 10 +++--- notes/gdrive.py | 8 ++--- notes/models.py | 56 +++++++++++++++++--------------- notes/search_indexes.py | 10 +++--- notes/templates/n_dashboard.html | 2 +- notes/utils.py | 14 ++++---- notes/views.py | 28 ++++++++-------- 8 files changed, 67 insertions(+), 63 deletions(-) diff --git a/notes/admin.py b/notes/admin.py index e371ee0..ae9f4a3 100644 --- a/notes/admin.py +++ b/notes/admin.py @@ -20,7 +20,7 @@ admin.site.register(models.DriveAuth) admin.site.register(models.Course) admin.site.register(models.Instructor) -admin.site.register(models.File) +admin.site.register(models.Note) admin.site.register(models.Tag) admin.site.register(models.ReputationEvent) admin.site.register(models.ReputationEventType) diff --git a/notes/forms.py b/notes/forms.py index 8f1079b..8e59e6c 100644 --- a/notes/forms.py +++ b/notes/forms.py @@ -11,7 +11,7 @@ from simple_autocomplete.widgets import AutoCompleteWidget from simplemathcaptcha.fields import MathCaptchaField -from models import School, Course, File, Tag, Instructor +from models import School, Course, Note, Tag, Instructor class UserCreateForm(UserCreationForm): @@ -39,7 +39,7 @@ class FileMetaDataForm(forms.Form): widget=forms.HiddenInput(attrs={'id': 'file-form-school_pk'})) course_pk = forms.CharField(max_length=255, \ widget=forms.HiddenInput(attrs={'id': 'file-form-course_pk'})) - type = forms.ChoiceField(choices=File.FILE_PTS) + type = forms.ChoiceField(choices=Note.FILE_PTS) title = forms.CharField(max_length=50, \ error_messages={'required': 'Enter a title.'}, \ widget=forms.TextInput(attrs={'class': 'text-input'})) @@ -78,7 +78,7 @@ class FileMetaDataFormNoCaptcha(forms.Form): attrs={'id': 'file-form-course_pk'} ) ) - type = forms.ChoiceField(choices=File.FILE_PTS, required=False) + type = forms.ChoiceField(choices=Note.FILE_PTS, required=False) created_on = forms.DateField(required=False) title = forms.CharField(max_length=50, error_messages={'required': 'Enter a title.'}, @@ -176,7 +176,7 @@ class ModelSearchForm(forms.Form): class UsherUploadFileForm(forms.Form): - type = forms.ChoiceField(choices=File.FILE_PTS) + type = forms.ChoiceField(choices=Note.FILE_PTS) title = forms.CharField(max_length=50, \ error_messages={'required': 'Enter a title.'}) description = forms.CharField(required=False, max_length=511, \ @@ -318,7 +318,7 @@ class SmartSchoolForm(forms.Form): # Upload file form class UploadFileForm(forms.Form): - type = forms.ChoiceField(choices=File.FILE_PTS) + type = forms.ChoiceField(choices=Note.FILE_PTS) title = forms.CharField(max_length=50, error_messages={'required': 'Enter a title.'}) description = forms.CharField(max_length=511, error_messages={'required': 'Enter a description.'}) #school = forms.ModelChoiceField(queryset=School.objects.all(), empty_label="") diff --git a/notes/gdrive.py b/notes/gdrive.py index fbc92af..a9abbcb 100755 --- a/notes/gdrive.py +++ b/notes/gdrive.py @@ -11,7 +11,7 @@ from apiclient.http import MediaFileUpload from oauth2client.client import flow_from_clientsecrets -from notes.models import DriveAuth, File +from notes.models import DriveAuth, Note CLIENT_SECRET = './notes/client_secrets.json' from credentials import GOOGLE_USER @@ -135,7 +135,7 @@ def convert_with_google_drive(u_file): # set u_file.is_pdf if file_type == 'application/pdf': # Get a new copy of the file from the database with the new metadata from filemeta - new_file = File.objects.get(id=u_file.id) + new_file = Note.objects.get(id=u_file.id) # If it's a pdf, instead save an embed_url from resource['selfLink'] new_file.is_pdf = True new_file.embed_url = file_dict[u'selfLink'] @@ -155,13 +155,13 @@ def convert_with_google_drive(u_file): if resp.status in [200]: print "\t downloaded!" - # save to the File.property resulting field + # save to the Note.property resulting field content_dict[download_type] = content else: print "\t Download failed: %s" % resp.status # Get a new copy of the file from the database with the new metadata from filemeta - new_file = File.objects.get(id=u_file.id) + new_file = Note.objects.get(id=u_file.id) # set the .odt as the download from google link new_file.gdrive_url = file_dict[u'exportLinks']['application/vnd.oasis.opendocument.text'] diff --git a/notes/models.py b/notes/models.py index d04603e..92c06e1 100644 --- a/notes/models.py +++ b/notes/models.py @@ -96,7 +96,7 @@ def decrement(sender, **kwargs): """ # TODO, impement this as a method on the SiteStat object, rather than in the global scope of models stats = SiteStats.objects.get(pk=1) - if isinstance(sender, File): + if isinstance(sender, Note): if sender.type == 'N': stats.numNotes -= 1 elif sender.type == 'G': @@ -121,7 +121,7 @@ def increment(sender, **kwargs): # TODO, modify decrement to increment or decrement based on a passed flag, rather than duplicating this if else logic stats = SiteStats.objects.get(pk=1) #print stats.numNotes - if isinstance(sender, File): + if isinstance(sender, Note): print sender.type if sender.type == 'N': stats.numNotes += 1 @@ -295,7 +295,7 @@ class Course(models.Model): instructor_email= models.EmailField(blank=True, null=True) last_updated = models.DateTimeField(default=datetime.datetime.now) desc = models.TextField(max_length=1023, blank=True, null=True) - # last_updated is updated with the datetime of the latest File.save() ran. Not on user join/drop + # last_updated is updated with the datetime of the latest Note.save() ran. Not on user join/drop browsable = models.BooleanField(default=False) karma = models.IntegerField(default=0) @@ -323,7 +323,7 @@ def get_notes(course_query, school): else: print "No course found, so no notes" raise Http404 - return _course, File.objects.filter(course=_course).order_by('timestamp').distinct() + return _course, Note.objects.filter(course=_course).order_by('timestamp').distinct() def sum_karma(self): """calculate the total karma for all ReputationEvents for this course @@ -360,7 +360,7 @@ class Meta: post_delete.connect(decrement, sender=Course) -class File(models.Model): +class Note(models.Model): # FIXME: list of tuples can't be addressed, dicts can # FILE_TYPES['N'] @@ -462,7 +462,7 @@ def save(self, *args, **kwargs): self.html = re.escape(self.html) self.cleaned = True - super(File, self).save(*args, **kwargs) + super(Note, self).save(*args, **kwargs) # update associated course last_updated try: self.course.last_updated = datetime.datetime.now() @@ -485,7 +485,7 @@ def vote(self, voter, vote_value): upvote - vote_value=1 , downvote - vote_value=-1 :voter: a User object who is trying to register a vote """ - print "models.File.vote: Creating Vote object" + print "models.Note.vote: Creating Vote object" print "voter:", voter print "value:", vote_value @@ -528,7 +528,7 @@ def vote(self, voter, vote_value): # Award karma if self.owner: - print 'models.File.vote: profile', self.owner.get_profile() + print 'models.Note.vote: profile', self.owner.get_profile() self.owner.get_profile().award_karma( event=event, course=self.course, @@ -537,7 +537,7 @@ def vote(self, voter, vote_value): user=voter) else: - print "models.File.vote: file has no owner:", self + print "models.Note.vote: file has no owner:", self self.save() @@ -564,8 +564,12 @@ def karmaValue(self): except: return 0 -# On File delete, decrement appropriate stat -post_delete.connect(decrement, sender=File) + class Meta: + # we re-factored the model. Old name is 'File'. New name is 'Note' + db_table = 'notes_file' + +# On Note delete, decrement appropriate stat +post_delete.connect(decrement, sender=Note) class Vote(models.Model): @@ -575,7 +579,7 @@ class Vote(models.Model): """ user = models.ForeignKey(User) up = models.BooleanField(default=True) - note = models.ForeignKey(File) + note = models.ForeignKey(Note) def __unicode__(self): return u"%s voted %s" % (self.user, str(self.up)) @@ -609,7 +613,7 @@ class ReputationEvent(models.Model): # optional fkeys to related models. used for displaying activity for user/school/course user = models.ForeignKey(User, blank=True, null=True, related_name='actor') # FIXME: rename actor_user target = models.ForeignKey(User, blank=True, null=True, related_name='target') - file = models.ForeignKey(File, blank=True, null=True) + file = models.ForeignKey(Note, blank=True, null=True) course = models.ForeignKey(Course, blank=True, null=True) school = models.ForeignKey(School, blank=True, null=True) @@ -661,7 +665,7 @@ class UserProfile(models.Model): can_moderate = models.BooleanField(default=False) # user-submitted files and those the user has "paid for" - files = models.ManyToManyField(File, blank=True, null=True) + viewed_notes = models.ManyToManyField(Note, blank=True, null=True) # courses a user is currently, or has been enrolled courses = models.ManyToManyField(Course, null=True, blank=True) @@ -805,7 +809,7 @@ def award_karma(self, event, target_user=None, school=None, course=None, user=No :school: is a School object (optional) :course: a Course object (optional) :user: a User object (optional), for recalling username when showing other's karmaevents - :file: a notes.models.File object (optional) + :file: a notes.models.Note object (optional) returns True or False """ @@ -837,27 +841,27 @@ def award_karma(self, event, target_user=None, school=None, course=None, user=No print e return False - def addFile(self, File): - """ Called by notes.views.upload after saving File + def addFile(self, Note): + """ Called by notes.views.upload after saving Note Generates the appropriate ReputationEvent, and modifies the user's karma """ - # Set File.owner to the user - File.owner = self.user - File.save() + # Set Note.owner to the user + Note.owner = self.user + Note.save() # Add this file to the user's collection - self.files.add(File) + self.files.add(Note) # Generate a reputation event title = "" - if File.type == 'N': + if Note.type == 'N': title = 'lecture-note' - elif File.type == 'G': + elif Note.type == 'G': title = 'mid-term-study-guide' - elif File.type == 'S': + elif Note.type == 'S': title = 'syllabus' - elif File.type == 'A': + elif Note.type == 'A': title = 'assignment' - elif File.type == 'E': + elif Note.type == 'E': title = 'exam-or-quiz' # Remember to load all ReputationEventTypes with diff --git a/notes/search_indexes.py b/notes/search_indexes.py index bac575a..6a2b1db 100644 --- a/notes/search_indexes.py +++ b/notes/search_indexes.py @@ -4,7 +4,7 @@ from haystack.indexes import * from haystack.fields import EdgeNgramField from haystack import site -from models import School, Course, File +from models import School, Course, Note class SchoolIndex(SearchIndex): @@ -51,7 +51,7 @@ def prepare_school(self, obj): return None -class FileIndex(SearchIndex): +class NoteIndex(SearchIndex): text = CharField(document=True, use_template=True) title = CharField(model_attr='title') school = CharField(null=True) @@ -79,7 +79,7 @@ def prepare_course(self, obj): # Use Apache Solr's Rich Content Extraction # To index document text for search def prepare(self, obj): - data = super(FileIndex, self).prepare(obj) + data = super(NoteIndex, self).prepare(obj) try: # This could also be a regular Python open() call, a StringIO instance # or the result of opening a URL. Note that due to a library limitation @@ -95,11 +95,11 @@ def prepare(self, obj): data['text'] = t.render(Context({'object': obj, 'extracted': extracted_data})) except IOException: - print "FileIndex: error accessing " + obj.file.path + print "NoteIndex: error accessing " + obj.file.path # actual file is not available return data ''' -site.register(File, FileIndex) +site.register(Note, NoteIndex) site.register(School, SchoolIndex) site.register(Course, CourseIndex) diff --git a/notes/templates/n_dashboard.html b/notes/templates/n_dashboard.html index b14f762..586096b 100644 --- a/notes/templates/n_dashboard.html +++ b/notes/templates/n_dashboard.html @@ -36,7 +36,7 @@ {{ upload_count|default:"0" }}
- {{ user.get_profile.files.count }} + {{ user.get_profile.viewed_notes.count|default:"0" }}
{{ upvote_count }} diff --git a/notes/utils.py b/notes/utils.py index fe551d2..97e0293 100644 --- a/notes/utils.py +++ b/notes/utils.py @@ -10,7 +10,7 @@ from simple_autocomplete.widgets import AutoCompleteWidget from KNotes import settings -from models import School, Course, File, Tag, UserProfile +from models import School, Course, Note, Tag, UserProfile from notes import profile_tasks def _post_user_create_session_hook(request): @@ -25,7 +25,7 @@ def _post_user_create_session_hook(request): print 'found unclaimed files session key' for unclaimed_file_pk in request.session[settings.SESSION_UNCLAIMED_FILES_KEY]: try: - unclaimed_file = File.objects.get(pk=unclaimed_file_pk) + unclaimed_file = Note.objects.get(pk=unclaimed_file_pk) unclaimed_file.owner = request.user unclaimed_file.save() # Handles generating Event + Awarding Karma print "saved " + str(unclaimed_file.title) @@ -68,7 +68,7 @@ def nav_helper(request, response={}): # If user has a school selected, fetch recent additions to School # For the user's news feed - #response['recent_files'] = File.objects.filter(school=request.user.get_profile().school).order_by('-timestamp')[:5] + #response['recent_files'] = Note.objects.filter(school=request.user.get_profile().school).order_by('-timestamp')[:5] response['messages'] = complete_profile_prompt(user_profile) response['share_url'] = u"http://karmanotes.org/sign-up/{0}".format(user_profile.get_name()) @@ -139,7 +139,7 @@ def jsonifyModel(model, depth=0, user_pk=-1): for note in model.files.all().order_by('-timestamp'): note_json = jsonifyModel(model=note, user_pk=user_pk) json_result["notes"].append(note_json) - elif isinstance(model, File): + elif isinstance(model, Note): json_result["_id"] = model.pk json_result["notedesc"] = model.title json_result["views"] = model.viewCount @@ -202,10 +202,10 @@ def jsonifyModel(model, depth=0, user_pk=-1): def processCsvTags(file, csvString): """ Retrieve or Create a tag corresponding to each string and assign it to file - file: a File object + file: a Note object csvString: a csv string of Tags """ - if not isinstance(file, File): + if not isinstance(file, Note): return False tagStrs = csvString.split(',') for tagStr in tagStrs: @@ -265,7 +265,7 @@ def complete_profile_prompt(user_profile): def userCanView(user, file): """ :user: django user - :file: a notes.models.File object + :file: a notes.models.Note object returns True/False """ if file.owner == user or user.get_profile().files.filter(pk=file.pk).exists(): diff --git a/notes/views.py b/notes/views.py index 7fd1c1f..f599916 100644 --- a/notes/views.py +++ b/notes/views.py @@ -32,7 +32,7 @@ from models import School from models import Course -from models import File +from models import Note from models import Instructor from models import SiteStats from models import Level @@ -74,7 +74,7 @@ def dashboard(request): response = {} response['events'] = request.user.get_profile().reputationEvents.order_by('-id').all() - response['upload_count'] = File.objects.filter(owner=request.user).count() + response['upload_count'] = Note.objects.filter(owner=request.user).count() # Count the reputation events where the user was the actor and the type was 'upvote' response['upvote_count'] = request.user.actor.filter(type__title='upvote').count() @@ -90,10 +90,10 @@ def home(request): # Get the 'singleton' SiteStats instance stats = SiteStats.objects.get(pk=1) #Get recently uploaded files - recent_files = File.objects.exclude(title__exact='') \ + recent_files = Note.objects.exclude(title__exact='') \ .order_by('-timestamp')[:7] #print recent_files - file_count = File.objects.count() + file_count = Note.objects.count() return render(request, 'n_home.html', {'stats': stats, 'recent_files': recent_files,\ 'file_count': file_count}) @@ -202,13 +202,13 @@ def profile(request): """ User Profile """ response = nav_helper(request) response['course_json_url'] = '/jq_course' # FIXME: replace this with a reverse urls.py query - response['your_files'] = File.objects.filter(owner=request.user).all() + response['your_files'] = Note.objects.filter(owner=request.user).all() return render(request, 'navigation.html', response) @login_required def raw_file(request, note_pk): - """ Display the raw html from a File object for embedding in an iframe """ - note = get_object_or_404(File, pk=note_pk) + """ Display the raw html from a Note object for embedding in an iframe """ + note = get_object_or_404(Note, pk=note_pk) return HttpResponse(note.html) """ =========================================== @@ -224,7 +224,7 @@ def file(request, note_pk, action=None): """ response = {} user_profile = request.user.get_profile() - file = get_object_or_404(File, pk=note_pk) + file = get_object_or_404(Note, pk=note_pk) # If this file is not in the user's collection, # add this file to the user_profile as a viewd file @@ -291,7 +291,7 @@ def fileMeta(request): response["message"] = "Please check your form data." return HttpResponse(json.dumps(response), mimetype="application/json") - file = File.objects.get(pk=form.cleaned_data["file_pk"]) + file = Note.objects.get(pk=form.cleaned_data["file_pk"]) file.type = form.cleaned_data["type"] file.title = form.cleaned_data["title"] file.description = form.cleaned_data["description"] @@ -571,7 +571,7 @@ def editFileMeta(request): Now, just title and description ''' if request.is_ajax() and 'file_pk' in request.POST: - file = get_object_or_404(File, pk=request.POST['file_pk']) + file = get_object_or_404(Note, pk=request.POST['file_pk']) if request.user == file.owner: do_save = False response = {} @@ -875,7 +875,7 @@ def vote(request, file_pk): # Check that GET parameters are valid - voting_file = get_object_or_404(File, pk=file_pk) + voting_file = get_object_or_404(Note, pk=file_pk) voting_user = request.user user_profile = request.user.get_profile() @@ -919,7 +919,7 @@ def multisearch(request): .models(Course).load_all() response['courses'] = [c.object for c in courses] notes = SearchQuerySet().filter(content__icontains=query) \ - .models(File).load_all() + .models(Note).load_all() response['notes'] = [n.object for n in notes] response['instructors'] = [] response['users'] = [] @@ -931,7 +931,7 @@ def browse(request): response['schools'] = School.objects.filter(browsable=True) response['courses'] = Course.objects.filter(browsable=True) - response['notes'] = File.objects.filter(browsable=True) + response['notes'] = Note.objects.filter(browsable=True) response['instructors'] = [] response['users'] = [] @@ -951,7 +951,7 @@ def search(request): #results = SearchQuerySet().filter(content__icontains=query).highlight() #Exact match results w/Highlighting. Target notes - #results = SearchQuerySet().filter(content__icontains=query).models(File).highlight() + #results = SearchQuerySet().filter(content__icontains=query).models(Note).highlight() #highlight = Highlighter(query) #highlight_test = highlight.highlight('this is a test ' + query + 'yes, a test') From df8b6e89c8969248e32e108c5dcfef988638ffe1 Mon Sep 17 00:00:00 2001 From: Charles Holbrow Date: Tue, 18 Dec 2012 15:21:54 -0500 Subject: [PATCH 02/29] changing instances of UserProfile.files to UserProfile.viewed_notes --- notes/models.py | 4 ++-- notes/profile_tasks.py | 2 +- notes/utils.py | 4 ++-- notes/views.py | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/notes/models.py b/notes/models.py index 92c06e1..1ac1f41 100644 --- a/notes/models.py +++ b/notes/models.py @@ -474,7 +474,7 @@ def ownedBy(self, user_pk): """ Returns true if the user owns or has "paid" for this file """ # If the file is in the user's collection, or the user owns the file - if self.owner == User.objects.get(pk=user_pk) or User.objects.get(pk=user_pk).get_profile().files.filter(pk=self.pk).exists(): + if self.owner == User.objects.get(pk=user_pk) or User.objects.get(pk=user_pk).get_profile().viewed_notes.filter(pk=self.pk).exists(): return True return False @@ -850,7 +850,7 @@ def addFile(self, Note): Note.owner = self.user Note.save() # Add this file to the user's collection - self.files.add(Note) + self.viewed_notes.add(Note) # Generate a reputation event title = "" if Note.type == 'N': diff --git a/notes/profile_tasks.py b/notes/profile_tasks.py index b7a9369..3534580 100644 --- a/notes/profile_tasks.py +++ b/notes/profile_tasks.py @@ -65,7 +65,7 @@ class UploadedFile(): karma = u"5-10" def check(self, user_profile): - if len(user_profile.files.all()) >= 1: + if len(user_profile.viewed_notes.all()) >= 1: return True return False diff --git a/notes/utils.py b/notes/utils.py index 97e0293..cbb09c2 100644 --- a/notes/utils.py +++ b/notes/utils.py @@ -181,7 +181,7 @@ def jsonifyModel(model, depth=0, user_pk=-1): json_result["canvote"] = 0 json_result["vote"] = 1 # Else If the valid user has viewd the file, allow voting - elif request_user.get_profile().files.filter(pk=model.pk).exists(): + elif request_user.get_profile().viewed_notes.filter(pk=model.pk).exists(): #print "*** user has viewed file!" json_result["canvote"] = 1 json_result["owns"] = 0 @@ -268,7 +268,7 @@ def userCanView(user, file): :file: a notes.models.Note object returns True/False """ - if file.owner == user or user.get_profile().files.filter(pk=file.pk).exists(): + if file.owner == user or user.get_profile().viewed_notes.filter(pk=file.pk).exists(): print "user can view!" return True return False diff --git a/notes/views.py b/notes/views.py index f599916..8be3f6d 100644 --- a/notes/views.py +++ b/notes/views.py @@ -228,12 +228,12 @@ def file(request, note_pk, action=None): # If this file is not in the user's collection, # add this file to the user_profile as a viewd file - if not user_profile.files.filter(pk=note_pk).exists(): + if not user_profile.viewed_notes.filter(pk=note_pk).exists(): # Buy Note viewing privelege for karma # award_karma will handle deducting appropriate karma user_profile.award_karma('view-file', school=file.school, course=file.course, file=file, user=request.user) # Add 'purchased' file to user's collection - user_profile.files.add(file) + user_profile.viewed_notes.add(file) user_profile.save() print user_profile, 'purchased', file, 'with karma.' # Increment note view count @@ -506,7 +506,7 @@ def browse_one_course(request, course_query, school): response['events'] = course.reputationevent_set.order_by('-timestamp').all() # FIXME: possibly order-by # we don't use these, FIXME: CLEAN THIS UP """ - response['viewed_files'] = request.user.get_profile().files.all() + response['viewed_files'] = request.user.get_profile().viewed_notes.all() # FIXME: I don't like this logic one bit, either annotate the db query or fix the schema to NEVER do this response['thanked_files'] = [] @@ -890,7 +890,7 @@ def vote(request, file_pk): return HttpResponse("You have all ready voted on this file") # Else If the valid user has viewd the file, allow voting - elif user_profile.files.filter(pk=voting_file.pk).exists(): + elif user_profile.viewed_notes.filter(pk=voting_file.pk).exists(): print "casting vote" voting_file.vote(voter=voting_user, vote_value=vote_value) if vote_value == 1: From ff11cff2ddde188099cc7dc1fd931253b9b2ee78 Mon Sep 17 00:00:00 2001 From: Charles Holbrow Date: Tue, 18 Dec 2012 15:51:19 -0500 Subject: [PATCH 03/29] fixing course_title undefined check in app.js --- notes/static/js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notes/static/js/app.js b/notes/static/js/app.js index 7f94c55..30169ce 100644 --- a/notes/static/js/app.js +++ b/notes/static/js/app.js @@ -86,7 +86,7 @@ $(document).ready(function(){ // Show Add Note lightbox $("#global_header_addnote").click(function() { - if (course_title != null){ + if (typeof course_title != 'undefined'){ $("#lightbox_upload_course_sidebar").text(course_title); } $("#lightbox_add_note").toggle(); From 28b2f1627a6a74803beb606ba1bf038ed0459ad3 Mon Sep 17 00:00:00 2001 From: Charles Holbrow Date: Tue, 18 Dec 2012 17:25:23 -0500 Subject: [PATCH 04/29] deprecating some functions --- notes/views.py | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/notes/views.py b/notes/views.py index 8be3f6d..d6325bb 100644 --- a/notes/views.py +++ b/notes/views.py @@ -174,37 +174,6 @@ def register(request, invite_user): """ ===================================================================== People pages, pages that are heavily customized for a particular user ===================================================================== """ -def getting_started(request): - """ View for introducing a user to the site and asking them to accomplish intro tasks """ - response = nav_helper(request) - response['tasks'] = [] - for task in tasks: - t = {} - t['message'] = task().message - t['status'] = task().check(request.user.get_profile()) - t['karma'] = task().karma - response['tasks'].append(t) - return render(request, 'getting-started.html', response) - - -def karma_events(request): - """ Shows a time sorted log of your events that affect your - karma score positively or negatively. - """ - # navigation.html - response = nav_helper(request) - response['events'] = request.user.get_profile().reputationEvents.order_by('-timestamp').all() - return render(request, 'karma-events.html', response) - - -@login_required -def profile(request): - """ User Profile """ - response = nav_helper(request) - response['course_json_url'] = '/jq_course' # FIXME: replace this with a reverse urls.py query - response['your_files'] = Note.objects.filter(owner=request.user).all() - return render(request, 'navigation.html', response) - @login_required def raw_file(request, note_pk): """ Display the raw html from a Note object for embedding in an iframe """ @@ -428,20 +397,6 @@ def smartModelQuery(request): raise Http404 -def browse_schools(request): - """ Server-side templated browsing of notes by school, course and note """ - response = nav_helper(request) - # make this order by the most notes in a school - response['title'] = u"Schools" - response['schools'] = School.objects.annotate(num_course=Count('course'))\ - .order_by('num_course').reverse().filter(num_course__gt=0).all() - if request.user.get_profile().school not in response['schools']: - # create a new list of the user's school, extend it with the current list of schools - # this prepends the user's school to the top of the school list - response['schools'] = [request.user.get_profile().school] + list(response['schools']) - # this converts the QuerySet into a list, so this is not typesafe, but django templates do not care - return render(request, 'browse_schools.html', response) - def school(request, school_query): """ View for a school, lists courses and school activity :school_query: comes as unicode, if can be int, pass as int @@ -456,8 +411,6 @@ def school(request, school_query): response['school'], response['courses'] = School.get_courses(school_query) return render(request, 'n_school.html', response) - - def browse_courses(request, school_query): """ View for courses beloging to :school_query: :school_query: comes as unicode, if can be int, pass as int From a7fac34784cb8392c9b235fbd774f60ffd83cda4 Mon Sep 17 00:00:00 2001 From: Charles Holbrow Date: Tue, 18 Dec 2012 17:43:06 -0500 Subject: [PATCH 05/29] Removing SiteStat singleton, increment and decrement --- KNotes/urls.py | 7 ---- notes/admin.py | 1 - notes/models.py | 97 +------------------------------------------------ notes/views.py | 5 +-- 4 files changed, 2 insertions(+), 108 deletions(-) diff --git a/KNotes/urls.py b/KNotes/urls.py index f7f300f..c17a1c8 100644 --- a/KNotes/urls.py +++ b/KNotes/urls.py @@ -48,13 +48,6 @@ url(r'^terms$', 'notes.views.terms', name='terms'), url(r'^jobs$', 'notes.views.jobs', name='jobs'), - # --------------------------------------------------- - ## Personal pages - # Karma events - url(r'^getting-started$', 'notes.views.getting_started', name='getting-started'), - url(r'^karma-events$', 'notes.views.karma_events', name='karma-events'), - url(r'^profile$', 'notes.views.profile', name='profile'), - # --------------------------------------------------- # Search url(r'^search/', 'notes.views.search'), diff --git a/notes/admin.py b/notes/admin.py index ae9f4a3..ff6b0d2 100644 --- a/notes/admin.py +++ b/notes/admin.py @@ -24,7 +24,6 @@ admin.site.register(models.Tag) admin.site.register(models.ReputationEvent) admin.site.register(models.ReputationEventType) -admin.site.register(models.SiteStats) admin.site.register(models.Level) admin.site.register(models.Vote) admin.site.register(models.UsdeSchool) diff --git a/notes/models.py b/notes/models.py index 1ac1f41..bf8fd19 100644 --- a/notes/models.py +++ b/notes/models.py @@ -69,77 +69,6 @@ def __unicode__(self): return u"%s %d" % (self.title, self.karma) -class SiteStats(models.Model): - """ Used to incrementally tally site statistics - For display on landing page, etc. - This is more efficient then calculating totals on every request - Upon installing the app we should initialize ONE instance of SiteStats - The increment/decrement methods will act only on the first instance (pk=1) - """ - # TODO: make this class name singular - numNotes = models.IntegerField(default=0) - numStudyGuides = models.IntegerField(default=0) - numSyllabi = models.IntegerField(default=0) - numAssignments = models.IntegerField(default=0) - numExams = models.IntegerField(default=0) - - numCourses = models.IntegerField(default=0) - numSchools = models.IntegerField(default=0) - - def __unicode__(self): - return u"%d Notes, %d Guides, %d Syllabi, %d Assignments, %d Exams for %d total Courses at %d Schools" % (self.numNotes, self.numStudyGuides, self.numSyllabi, self.numAssignments, self.numExams, self.numCourses, self.numSchools) - - -def decrement(sender, **kwargs): - """ Decrease the appropriate stat given a Model - Called in Model save() and post_delete() (not delete() due to queryset behavior) - """ - # TODO, impement this as a method on the SiteStat object, rather than in the global scope of models - stats = SiteStats.objects.get(pk=1) - if isinstance(sender, Note): - if sender.type == 'N': - stats.numNotes -= 1 - elif sender.type == 'G': - stats.numStudyGuides -= 1 - elif sender.type == 'S': - stats.numSyllabi -= 1 - elif sender.type == 'A': - stats.numAssignments -= 1 - elif sender.type == 'E': - stats.numExams -= 1 - elif isinstance(sender, School): - stats.numSchools -= 1 - elif isinstance(sender, Course): - stats.numCourses -= 1 - stats.save() - - -def increment(sender, **kwargs): - """ Increment the appropriate stat given a Model - Called in Model save() and post_delete() (not delete() due to queryset behavior) - """ - # TODO, modify decrement to increment or decrement based on a passed flag, rather than duplicating this if else logic - stats = SiteStats.objects.get(pk=1) - #print stats.numNotes - if isinstance(sender, Note): - print sender.type - if sender.type == 'N': - stats.numNotes += 1 - elif sender.type == 'G': - stats.numStudyGuides += 1 - elif sender.type == 'S': - stats.numSyllabi += 1 - elif sender.type == 'A': - stats.numAssignments += 1 - elif sender.type == 'E': - stats.numExams += 1 - elif isinstance(sender, School): - stats.numSchools += 1 - elif isinstance(sender, Course): - stats.numCourses += 1 - stats.save() - - class Tag(models.Model): """ This class represents a meta-tag of a note Used for searching @@ -217,18 +146,12 @@ def sum_karma(self): def save(self, *args, **kwargs): - # If a new School is being saved, increment SiteStat School count - if not self.pk: - increment(self) if not self.slug: # FIXME: make this unique # TODO: add a legacy slugs table that provide redirects to new slug pages self.slug = slugify(self.name) super(School, self).save(*args, **kwargs) -# On School delete, decrement numSchools -post_delete.connect(decrement, sender=School) - class UsdeSchool(models.Model): """Table of schools imported from the U.S. Department of Education @@ -337,9 +260,6 @@ def sum_karma(self): self.save() def save(self, *args, **kwargs): - # If a new Course is being saved, increment SiteStat Course count - if not self.pk: - increment(self) if not self.slug: # FIXME: make this unique # TODO: add a legacy slugs table that provide redirects to new slug pages @@ -356,9 +276,6 @@ class Meta: # can't refer to more than one course unique_together = ('school', 'slug') -# On Course delete, decrement numCourses -post_delete.connect(decrement, sender=Course) - class Note(models.Model): @@ -464,19 +381,9 @@ def save(self, *args, **kwargs): super(Note, self).save(*args, **kwargs) # update associated course last_updated - try: + if self.course: self.course.last_updated = datetime.datetime.now() self.course.save - except: - pass - - def ownedBy(self, user_pk): - """ Returns true if the user owns or has "paid" for this file - """ - # If the file is in the user's collection, or the user owns the file - if self.owner == User.objects.get(pk=user_pk) or User.objects.get(pk=user_pk).get_profile().viewed_notes.filter(pk=self.pk).exists(): - return True - return False def vote(self, voter, vote_value): """ Calls UserProfile.award_karma @@ -568,8 +475,6 @@ class Meta: # we re-factored the model. Old name is 'File'. New name is 'Note' db_table = 'notes_file' -# On Note delete, decrement appropriate stat -post_delete.connect(decrement, sender=Note) class Vote(models.Model): diff --git a/notes/views.py b/notes/views.py index 61c61fe..03c54b1 100644 --- a/notes/views.py +++ b/notes/views.py @@ -34,7 +34,6 @@ from models import Course from models import Note from models import Instructor -from models import SiteStats from models import Level from models import Vote from models import ReputationEventType @@ -87,15 +86,13 @@ def home(request): if request.user.is_authenticated(): return dashboard(request) else: - # Get the 'singleton' SiteStats instance - stats = SiteStats.objects.get(pk=1) #Get recently uploaded files recent_files = Note.objects.exclude(title__exact='') \ .order_by('-timestamp')[:7] #print recent_files file_count = Note.objects.count() return render(request, 'n_home.html', - {'stats': stats, 'recent_files': recent_files,\ + {'recent_files': recent_files,\ 'file_count': file_count}) From 155ccd79161fa558587c2c3b6b8960d2916b7098 Mon Sep 17 00:00:00 2001 From: Seth Woodworth Date: Tue, 18 Dec 2012 17:46:09 -0500 Subject: [PATCH 06/29] removing obsolete model_utils file and the fast_hash function --- notes/model_utils.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 notes/model_utils.py diff --git a/notes/model_utils.py b/notes/model_utils.py deleted file mode 100644 index 7024df1..0000000 --- a/notes/model_utils.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf8 -*- -""" Utilities for model classes. Separate namespace here to avoid circular imports -""" -from random import choice -import string - -def fast_hash(letters=6): - """ Generates a unique pseudo-random identifier of hex values. - Used for invite codes on user_profiles - """ - return ''.join([choice(string.hexdigits) for n in xrange(letters)]) From d25db8810a7a57e81b766b2bf325dc1f3db04694 Mon Sep 17 00:00:00 2001 From: Seth Woodworth Date: Tue, 18 Dec 2012 17:56:30 -0500 Subject: [PATCH 07/29] removing get_picture function from user_profile, we handle this as an attribute --- notes/models.py | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/notes/models.py b/notes/models.py index bf8fd19..abb7b4e 100644 --- a/notes/models.py +++ b/notes/models.py @@ -645,47 +645,13 @@ def getLevel(self): response['current_level'] = levels[len(levels) - 1] return response - def get_picture(self, size='small'): - """ get the url of an appropriately size image for a user - :size: if size is set to anything but small, it will return a 180px image - if size = default of 'small' then it will return a 50px image - returns a facebook url if the user has a fb_id - returns a gravatar url if the user has an image there - returns a placeholder url if the user has neither - """ - # TODO: get and use default user icon if none, use gravatar's 404 function - # - # Make absolute url so they work w/ gravatar 404 function - if BETA: - small_default = u'http://beta.karmanotes.org/static/img/avatar-180.png' - large_default = u'http://beta.karmanotes.org/static/img/avatar-50.png' - else: - small_default = u'http://karmanotes.org/static/img/avatar-180.png' - large_default = u'http://karmanotes.org/static/img/avatar-50.png' - if self.fb_id: - url = u"https://graph.facebook.com/{0}/picture".format(self.user.username) - if size == 'small': - return url - else: - return url + u'?type=large' - else: - if not self.gravatar: - gravatar_hash = self.update_gravatar(self) # FIXME - else: - gravatar_hash = self.gravatar - url = u"https://secure.gravatar.com/avatar/{0}".format(gravatar_hash) - if size == 'small': - return u'{0}?s=50&d={1}'.format(url, small_default) - else: - return u'{0}?s=180d={1}'.format(url, large_default) - - # Get the "name" of this user for display - # If no first_name, user username def get_name(self): """ Generate the front-facing username for this user. Prefer user-supplied alias first, Second, username given on standard account signup Lastly, first name last initial (from social login) + # Get the "name" of this user for display + # If no first_name, user username """ # TODO make a template tag of this if self.alias and self.alias != "": From eca783a3f5cb1877975a17638c38f5a33a6b13a9 Mon Sep 17 00:00:00 2001 From: Seth Woodworth Date: Tue, 18 Dec 2012 18:00:33 -0500 Subject: [PATCH 08/29] Revert "removing get_picture function from user_profile, we handle this as an attribute" This reverts commit d25db8810a7a57e81b766b2bf325dc1f3db04694. --- notes/models.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/notes/models.py b/notes/models.py index abb7b4e..bf8fd19 100644 --- a/notes/models.py +++ b/notes/models.py @@ -645,13 +645,47 @@ def getLevel(self): response['current_level'] = levels[len(levels) - 1] return response + def get_picture(self, size='small'): + """ get the url of an appropriately size image for a user + :size: if size is set to anything but small, it will return a 180px image + if size = default of 'small' then it will return a 50px image + returns a facebook url if the user has a fb_id + returns a gravatar url if the user has an image there + returns a placeholder url if the user has neither + """ + # TODO: get and use default user icon if none, use gravatar's 404 function + # + # Make absolute url so they work w/ gravatar 404 function + if BETA: + small_default = u'http://beta.karmanotes.org/static/img/avatar-180.png' + large_default = u'http://beta.karmanotes.org/static/img/avatar-50.png' + else: + small_default = u'http://karmanotes.org/static/img/avatar-180.png' + large_default = u'http://karmanotes.org/static/img/avatar-50.png' + if self.fb_id: + url = u"https://graph.facebook.com/{0}/picture".format(self.user.username) + if size == 'small': + return url + else: + return url + u'?type=large' + else: + if not self.gravatar: + gravatar_hash = self.update_gravatar(self) # FIXME + else: + gravatar_hash = self.gravatar + url = u"https://secure.gravatar.com/avatar/{0}".format(gravatar_hash) + if size == 'small': + return u'{0}?s=50&d={1}'.format(url, small_default) + else: + return u'{0}?s=180d={1}'.format(url, large_default) + + # Get the "name" of this user for display + # If no first_name, user username def get_name(self): """ Generate the front-facing username for this user. Prefer user-supplied alias first, Second, username given on standard account signup Lastly, first name last initial (from social login) - # Get the "name" of this user for display - # If no first_name, user username """ # TODO make a template tag of this if self.alias and self.alias != "": From 145abed5d059960059e92029295419ee5059a165 Mon Sep 17 00:00:00 2001 From: Charles Holbrow Date: Tue, 18 Dec 2012 18:08:21 -0500 Subject: [PATCH 09/29] Cleaning lots of unused code --- notes/admin.py | 1 - notes/models.py | 45 ------------- notes/utils.py | 165 ------------------------------------------------ notes/views.py | 31 --------- 4 files changed, 242 deletions(-) diff --git a/notes/admin.py b/notes/admin.py index ff6b0d2..f337e5c 100644 --- a/notes/admin.py +++ b/notes/admin.py @@ -24,7 +24,6 @@ admin.site.register(models.Tag) admin.site.register(models.ReputationEvent) admin.site.register(models.ReputationEventType) -admin.site.register(models.Level) admin.site.register(models.Vote) admin.site.register(models.UsdeSchool) admin.site.unregister(User) diff --git a/notes/models.py b/notes/models.py index bf8fd19..f8d9ec4 100644 --- a/notes/models.py +++ b/notes/models.py @@ -58,17 +58,6 @@ def __unicode__(self): (self.email, self.stored_at) -class Level(models.Model): - """ Define User Levels - Each slug title is related to a minimum karma level - """ - title = models.SlugField(max_length=255) - karma = models.IntegerField(default=0) - - def __unicode__(self): - return u"%s %d" % (self.title, self.karma) - - class Tag(models.Model): """ This class represents a meta-tag of a note Used for searching @@ -621,30 +610,6 @@ def add_course(self, course_title=None, course_id=None): self.courses.add(course) # implies save() return True - def getLevel(self): - """ Determine the current level of the user - based on their karma and the Levels. - Returns a dictionary of - [current_level] -> Level - [next_level] -> Next Level - - """ - response = {} - levels = Level.objects.all().order_by('karma') - for (counter, level) in enumerate(levels): - if self.karma < level.karma: - if counter > 0: - response['next_level'] = level - response['current_level'] = levels[counter - 1] - else: - # If the user has not surpassed the first level - response['current_level'] = level - response['next_level'] = levels[counter + 1] - break - if not 'next_level' in response: - response['current_level'] = levels[len(levels) - 1] - return response - def get_picture(self, size='small'): """ get the url of an appropriately size image for a user :size: if size is set to anything but small, it will return a 180px image @@ -809,16 +774,6 @@ def save(self, *args, **kwargs): self.submitted_school = True self.award_karma('profile-school', user=self.user) - # Add read permissions if Prospect karma level is reached - if not self.can_read and self.karma >= Level.objects.get(title='Prospect').karma: - self.can_read = True - - # Add vote permissions if Prospect karma level is reached - #if self.can_vote == False and self.karma >= Level.objects.get(title='Prospect').karma: - # self.can_vote = True - - # TODO: Add other permissions... - super(UserProfile, self).save(*args, **kwargs) diff --git a/notes/utils.py b/notes/utils.py index cbb09c2..e8759d0 100644 --- a/notes/utils.py +++ b/notes/utils.py @@ -34,171 +34,6 @@ def _post_user_create_session_hook(request): del request.session[settings.SESSION_UNCLAIMED_FILES_KEY] - -def nav_helper(request, response={}): - """ calculates information for the navigation sidebar for logged in users - :request: a Request object that contains a user &etc - :response: (optional) a response dictionary to pass to the template - returns: a response dictionary - """ - # TODO: turn this into a middleware or decorator - # TODO: implement the zero-user-model - - # Calculate User's progress towards next Karma level - # Depends on models.Level objects - user_profile = request.user.get_profile() - - user_level = request.user.get_profile().getLevel() - response['current_level'] = user_level['current_level'] - if user_profile.school != None: - response['school_pk'] = user_profile.school.pk - else: - response['school_pk'] = 0 - - # The user has reached the top level - if not 'next_level' in user_level: - #print user_level['current_level'].title + " Top Level" - response['progress'] = 100 - else: - #print user_level['current_level'].title + " " + user_level['next_level'].title - response['next_level'] = user_level['next_level'] - response['progress'] = (user_profile.karma / float(response['next_level'].karma)) * 100 - - #Pre-populate ProfileForm with user's data - - # If user has a school selected, fetch recent additions to School - # For the user's news feed - #response['recent_files'] = Note.objects.filter(school=request.user.get_profile().school).order_by('-timestamp')[:5] - - response['messages'] = complete_profile_prompt(user_profile) - response['share_url'] = u"http://karmanotes.org/sign-up/{0}".format(user_profile.get_name()) - response['user_profile'] = user_profile - response = get_upload_form(response) - - # Check for uploads made during this django session - # while user was not authenticated - _post_user_create_session_hook(request) - - # home built auto-complete - ''' - if not user_profile.school: - response['available_schools'] = [(str(school.name), school.pk) for school in School.objects.all().order_by('name')] - if not user_profile.grad_year: - response['available_years'] = range(datetime.datetime.now().year, datetime.datetime.now().year + 10) - ''' - response['available_schools'] = [(unicode(school.name), school.pk) for school in School.objects.all().order_by('name')] - response['available_years'] = range(datetime.datetime.now().year, datetime.datetime.now().year + 10) - - return response - - -def get_upload_form(response): - """ Appends forms required for upload form to response - The way to make this smooth is: - user types option in autocomplete field > - after 3 characters, we search that via ajax - user is presented with options - when user clicks one of the options or hits enter, submits and saves school on field - the submit button also triggers the course field to appear like the school - when course is selected / created submits and saves course to file - course submit makes the metadata section appear - """ - response['school_form'] = SmartSchoolForm - return response - - -def jsonifyModel(model, depth=0, user_pk=-1): - """ Returns a python dictionary representation of a model - The resulting model is ready for json.dumps() - model is a Django Model - - optional: depth is how many levels of foreignKey introspection should be performed - jsonifyModel(model=School, depth=1) returns school json at course detail - jsonifyModel(model=School, depth=2) returns school json at note detail - optional: user_pk for inclusion of moderation data in file response - - i.e: has the user voted on this file? - """ - json_result = {} - if isinstance(model, School): - json_result["_id"] = model.pk - json_result["name"] = model.name - json_result["location"] = model.location - json_result["courses"] = [] - if(depth > 0): - for course in model.course_set.all().order_by('title'): - course_json = jsonifyModel(model=course, depth=depth - 1, user_pk=user_pk) - json_result["courses"].append(course_json) - elif isinstance(model, Course): - json_result["_id"] = model.pk - json_result["title"] = model.title - json_result["instructor"] = model.instructor.name - json_result["notes"] = [] - json_result["num_notes"] = len(model.files.all()) - if(depth > 0): - for note in model.files.all().order_by('-timestamp'): - note_json = jsonifyModel(model=note, user_pk=user_pk) - json_result["notes"].append(note_json) - elif isinstance(model, Note): - json_result["_id"] = model.pk - json_result["notedesc"] = model.title - json_result["views"] = model.viewCount - json_result["pts"] = model.numUpVotes - model.numDownVotes - json_result["upvotes"] = model.numUpVotes - - # If the file has an owner, provide it - if model.owner != None: - json_result["user"] = model.owner.get_profile().get_name() - else: - json_result["user"] = "KN Staff" - - # If a valid user_pk is provided, and that user has voted on this file add vote data - # If a valid user_pk is provided, and that user matches the file owner, indicate that - # For performance, validate user_pk before calling jsonifyModel - # now only check that user_pk != -1 - # Before: 2.42 s - # After: 2.11 s - if int(user_pk) != -1: - print user_pk - request_user = User.objects.get(pk=user_pk) - # If the valid user has voted on this file, bundle vote value: - if model.votes.filter(user=request_user).exists(): - #print "*** user voted" - # user has all ready voted - json_result["canvote"] = 1 - user_file_vote = model.votes.get(user=request_user).up - if user_file_vote == True: - json_result["vote"] = 1 # upvote - elif user_file_vote == False: - json_result["vote"] = -1 # downvote - else: - # The valid user has not voted on this file - json_result["vote"] = 0 # novote - # If the valid user owns the file, don't allow voting - if model.owner != None and model.owner == request_user: - json_result["owns"] = 1 - #print "*** user owns file" - json_result["canvote"] = 0 - json_result["vote"] = 1 - # Else If the valid user has viewd the file, allow voting - elif request_user.get_profile().viewed_notes.filter(pk=model.pk).exists(): - #print "*** user has viewed file!" - json_result["canvote"] = 1 - json_result["owns"] = 0 - # Else the valid user does not own, and has not viewed, so don't allow voting - else: - #print "*** no user connection" - json_result["canvote"] = 0 - json_result["owns"] = 0 - else: - #print "*** user dne" - # A valid user_pk was not provided - json_result["canvote"] = 0 - json_result["vote"] = 0 # novote - - return json_result - - def processCsvTags(file, csvString): """ Retrieve or Create a tag corresponding to each string and assign it to file diff --git a/notes/views.py b/notes/views.py index 03c54b1..e68b907 100644 --- a/notes/views.py +++ b/notes/views.py @@ -34,14 +34,11 @@ from models import Course from models import Note from models import Instructor -from models import Level from models import Vote from models import ReputationEventType from models import UsdeSchool from profile_tasks import tasks from utils import complete_profile_prompt -from utils import jsonifyModel -from utils import nav_helper from utils import userCanView @@ -799,34 +796,6 @@ def nurl_file(request, school_query, course_query, file_id, action=None): return file(request, file_id, action) -def searchBySchool(request): - """ Ajax: Return user's school's courses in JSON - Used by search page javascript. - If user has no school, show all schools - """ - response_json = [] - - if request.is_ajax(): - if request.user.is_authenticated and request.user.get_profile().school is not None: - school = get_object_or_404(School, pk=request.user.get_profile().school.pk) - response_json.append(jsonifyModel(model=school, depth=1)) - else: - schools = School.objects.all() - for school in schools: - school_json = jsonifyModel(model=school, depth=1) - response_json.append(school_json) - #print 'searchBySchool: ' + str(response_json) - return HttpResponse(json.dumps(response_json), mimetype="application/json") - else: - raise Http404 - - #A nicer way to do this would be to override the queryset serializer - #data = serializers.serialize("json", School.objects.all()) - #return HttpResponse(data, mimetype='application/json') - #json_serializer = serializers.get_serializer("json")() - #json_serializer.serialize(queryset, ensure_ascii=False, stream=response) - - @login_required def vote(request, file_pk): vote_value = int(request.POST.get('vote', 0)) From 729ee32887e5a234014c1d324c53584a06b26e88 Mon Sep 17 00:00:00 2001 From: Seth Woodworth Date: Wed, 19 Dec 2012 13:18:33 -0500 Subject: [PATCH 10/29] removing more obsolete functions from utils.py --- notes/utils.py | 50 +------------------------------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/notes/utils.py b/notes/utils.py index e8759d0..b13614c 100644 --- a/notes/utils.py +++ b/notes/utils.py @@ -34,60 +34,12 @@ def _post_user_create_session_hook(request): del request.session[settings.SESSION_UNCLAIMED_FILES_KEY] -def processCsvTags(file, csvString): - """ Retrieve or Create a tag corresponding to each string - and assign it to file - file: a Note object - csvString: a csv string of Tags - """ - if not isinstance(file, Note): - return False - tagStrs = csvString.split(',') - for tagStr in tagStrs: - # If the tag is empty, ignore it - if slugify(tagStr) == "": - continue - #print tagStr + " : " + str(slugify(tagStr)) - tag, created = Tag.objects.get_or_create(name=slugify(tagStr)) - #print "tag created: " + str(created) - #print "tag name: " + tag.name - file.tags.add(tag) - return True - - -def uploadForm(user): - """ Creates an UploadFileForm and pre-populates the school field - With the user's school, if available - is currently unused? - """ - #print request.user.username - user_profile = user.get_profile() - if user_profile.school: - print "The user has a school, so we will auto populate it" - # This isn't the ideal way to override the field system. I would rather extend or replicate the existing UploadFileForm, but I am slightly unsure how to do that - # Alternatively, I could figure out how to use the 'school' kv argument - form = UploadFileForm(initial={'course': -1, 'school': user_profile.school.pk}) - form.fields['school'] = djangoforms.ModelChoiceField( - queryset=School.objects.all(), - widget=AutoCompleteWidget( - url='/schools', - initial_display=user_profile.school.name - ), - error_messages={'invalid_choice': 'Enter a valid school. Begin typing a school name to see available choices.', - 'required': 'Enter a school.'}, - ) - else: - # Provide bogus default school and course data to ensure - # legitimate data is chosen - form = UploadFileForm(initial={'course': -1, 'school': -1}) - return form - - def complete_profile_prompt(user_profile): """ Creates a list of prompts for the user to do to complete their profile Takes a User object Returns a list of template strings """ + #FIXME: not currently used, but for the nag messaging system profile_todo = [] for task in profile_tasks.tasks: if not task.check(task(), user_profile): From 7b48567545d745a32a2347f65c7973a1bff6ac5f Mon Sep 17 00:00:00 2001 From: Seth Woodworth Date: Wed, 19 Dec 2012 13:39:54 -0500 Subject: [PATCH 11/29] moving requirements to subfolder and updating our README to match --- README.md | 6 +++--- requirements.txt => requirements/common.txt | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename requirements.txt => requirements/common.txt (100%) diff --git a/README.md b/README.md index 3c794a7..b884c07 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Deployment (Fresh Install) 1. Checkout code from the git repository. -2. install requirements with : `sudo pip install -r requirements` from `$SRC_ROOT`. +2. install requirements with : `sudo pip install -r requirements/common.txt` from `$SRC_ROOT`. 3. Setup postresql: @@ -74,7 +74,7 @@ Deployment ---------- 1. checkout the git repository, we use `/var/www/djKarma` and will refer to this as the root of the repo -2. install requirements with `sudo pip install -r requirements.txt` +2. install requirements with `sudo pip install -r requirements/common.txt` 3. Create the database if a new deployment with `./manage.py syncdb` If this is not a new deployment, see the section below on database migrations. NOTE: You can't create a superuser BEFORE loading the fixtures. 4. Use south to migrate `djcelery` and `kombu.transport.django`: ./manage.py migrate djcelery @@ -277,7 +277,7 @@ Importing finalsclub database 3. install requirements - sudo pip install -r requirements.txt + sudo pip install -r requirements/common.txt 4. Re-populate the contents of the static ./manage.py collectstatic diff --git a/requirements.txt b/requirements/common.txt similarity index 100% rename from requirements.txt rename to requirements/common.txt From 62a68155e9629743ecbc481f6a542444a5762e4a Mon Sep 17 00:00:00 2001 From: Charles Holbrow Date: Wed, 19 Dec 2012 14:07:40 -0500 Subject: [PATCH 12/29] renaming local file variable in view.note to note --- notes/templates/n_note.html | 36 ++++++++++++++++++------------------ notes/views.py | 34 ++++++++++++++++------------------ 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/notes/templates/n_note.html b/notes/templates/n_note.html index 6e458ca..e3a3de3 100644 --- a/notes/templates/n_note.html +++ b/notes/templates/n_note.html @@ -7,10 +7,10 @@ {% block pagescripts %} - -{% endblock %} diff --git a/KNotes/templates/ajaxFormResponse_min.html b/KNotes/templates/ajaxFormResponse_min.html deleted file mode 100644 index 2239634..0000000 --- a/KNotes/templates/ajaxFormResponse_min.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load url from future %} - -

{{ message }}


-
-
- {% csrf_token %} - {% for field in form.hidden_fields %} - {{field}} - {% endfor %} - {% for field in form.visible_fields %} -
- -
- {{ field }} - {{field.errors}} -
-
- {% endfor %}
- -
-
- \ No newline at end of file diff --git a/KNotes/templates/base.html b/KNotes/templates/base.html deleted file mode 100644 index 15d76b8..0000000 --- a/KNotes/templates/base.html +++ /dev/null @@ -1,126 +0,0 @@ -{% load url from future %} - - - - - - - - - - - - - - - - - - - - - - - - Karma Notes -- {% block title %}{% endblock %} - - {% block header %} - {% endblock %} - - - - - -{% block content %} -{% endblock %} - - - - - - - - - - -{% include 'modal_add_course.html' %} -{% include 'modal_share_karma.html' %} -{% include 'modal_upload.html' %} - -{% if not user.is_authenticated %} - -{% endif %} - -{% block scripts %} -{% endblock %} - - diff --git a/KNotes/templates/browse_courses.html b/KNotes/templates/browse_courses.html deleted file mode 100644 index 6bfa65f..0000000 --- a/KNotes/templates/browse_courses.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends 'navigation.html' %} - -{% block title %} - Browse {{ school.name }} Courses -{% endblock %} - -{% block navinner %} -
-

Browse {{ school.name }} Courses

Click on a course to see notes from that course. -
-
-{% if courses %} - {% for course in courses %} - {% include 'course.html' with course=course %} - {% endfor %} -{% else %} -

Sorry, there don't seem to be any courses for this school

-

Would you like to -

-

-{% endif %} -
- -{% endblock %} - diff --git a/KNotes/templates/browse_one_course.html b/KNotes/templates/browse_one_course.html deleted file mode 100644 index fa81873..0000000 --- a/KNotes/templates/browse_one_course.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends 'navigation.html' %} - -{% block title %} - Course -- {{ course.title }} -{% endblock %} - -{% block navinner %} - -{% include 'course_info.html' %} - - - - -
-
- - - {% for event in events %} - - - - - - {% endfor %} - -
- - - {% include 'karma_events/course_log.html' with event=event %}
-
- at {{ event.timestamp }} -
-
- -
-{% if files %} - {% for file in files %} - {% include 'file.html' with file=file %} - {% endfor %} -{% else %} -

There aren't any notes for this course yet. Would you like to upload some?

-

Would you like to -

-

-{% endif %} -
- -{% endblock %} - -{% block scripts %} - - -{% endblock %} - diff --git a/KNotes/templates/browse_schools.html b/KNotes/templates/browse_schools.html deleted file mode 100644 index 5635c0d..0000000 --- a/KNotes/templates/browse_schools.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'navigation.html' %} - -{% block title %} - Browse Schools -{% endblock %} - -{% block navinner %} -
-

Browse Schools

Click on a school to see courses from that school. -
-
- {% for school in schools %} - {% include 'school.html' with school=school %} - {% endfor %} -
- -{% endblock %} - diff --git a/KNotes/templates/course_info.html b/KNotes/templates/course_info.html deleted file mode 100644 index 5a3c1f1..0000000 --- a/KNotes/templates/course_info.html +++ /dev/null @@ -1,53 +0,0 @@ -
-

About this Class

-

{{ course.title }}

- - {{ course.instructor }} | {{ course.school.name }} - - - - -
- {% if course.files.count >= 1 %} - This class has {{ course.files.count }} notes. - {% else %} - This course doesn't have any notes yet. - {% endif %} -    - {% if course.url %} - Course Homepage - {% else %} - {# TODO: js inline editing and ajax save endpoint #} - add a Course Homepage - {% endif %} - - {# TODO: implement actual count here before launch or remove it #} - -
- {% include "course-actions.html" with course=course %} -
-
-
- - {# TODO: include professor email address if we have it #} - -
-

Students Enrolled

- {# TODO: find limit of horizontal space and set upper bound to forloop #} - {% for user in profiles %} - - {% endfor %} - - {% if user.get_profile not in profiles %} - add this course to my profile - {% endif %} - -
-
-
diff --git a/KNotes/templates/email_confirmed.html b/KNotes/templates/email_confirmed.html deleted file mode 100644 index 9f29939..0000000 --- a/KNotes/templates/email_confirmed.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "base.html" %} -{% load url from future %} - -{% block header %} - - -{% endblock %} - -{% block content %} -
- - - - -
-
-
-
-

Email Confirmed!

-
-

It's you after all! We're redirecting you to your profile...

-
-
-
-
-
- -{% include 'footer.html' %} -{% endblock %} - -{% block scripts %} - - - -{% endblock %} diff --git a/KNotes/templates/footer.html b/KNotes/templates/footer.html deleted file mode 100644 index ad68dc4..0000000 --- a/KNotes/templates/footer.html +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/KNotes/templates/getting-started.html b/KNotes/templates/getting-started.html deleted file mode 100644 index 19661ad..0000000 --- a/KNotes/templates/getting-started.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends 'navigation.html' %} - -{% block title %} - Getting Started -{% endblock %} - -{% block navinner %} - - {% for task in tasks %} - - - - - - - {% endfor %} - -

{{ forloop.counter }}.

- {{ task.message }} -
{{ task.karma }} points{% if task.status %} {% endif %} -
-{% endblock %} - diff --git a/KNotes/templates/home.html b/KNotes/templates/home.html deleted file mode 100644 index b4d37ea..0000000 --- a/KNotes/templates/home.html +++ /dev/null @@ -1,151 +0,0 @@ -{% extends "base.html" %} -{% load url from future %} - -{% block header %} - - -{% endblock %} - -{% block content %} -
- - - - -
-
- -
-
-

Share notes. Ace your exams.

-

Upload your notes to get instant access to over {{ file_count }} study materials. Free.

-
- UPLOAD FILE -
-
for instant access
- {#
Or drag + drop anywhere on this page
#} -
-
-
-
- - - - -

Recently uploaded notes

- -
    - {% for file in recent_files %} -
  • - {% include "file.html" with file=file %} -
  • - {% endfor %} -
-
-{% include 'footer.html' %} -{% endblock %} - -{% block scripts %} - - - - - -{% endblock %} diff --git a/KNotes/templates/jobs.html b/KNotes/templates/jobs.html deleted file mode 100644 index 4adc442..0000000 --- a/KNotes/templates/jobs.html +++ /dev/null @@ -1,151 +0,0 @@ -{% extends "base.html" %} -{% load url from future %} - -{% block header %} - - -{% endblock %} - -{% block content %} -
- - - - -
-
-
-
-

Jobs

-
-

Web Pioneer

-

The FinalsClub foundation is looking for a Django web developer who's excited about advancing education through open source software.

- -

We're building KarmaNotes.org, a community which enables students across the world to share notes and other academic materials.

- -

Our team is small, so you'll have an opportunity to leave your mark and build experience throughout our application. You'll deploy on a daily basis and build a respectable portfolio. All our code is open source and publicly available on GitHub!

- -

Our site digests user files (documents, pdfs, and images) and presents them for viewing (and eventually editing) all within the browser. The back-end logic is Python/Django with JavaScript/jQuery folded in to the front-end templates.

- -

Requirements

- -
    -
  • 1-3 years experience with an object oriented programming language (Python preferred).
  • -
  • Mastery of basic computer science concepts, such as data structures and algorithmic complexity.
  • -
  • Familiarity with relational databases.
  • -
  • You know how the web works, at least from HTTP to HTML.
  • -
  • A self-starter with the ability to work independently and meet team goals.
  • -
  • Experience shipping real products.
  • -
-

Apply!

-
-
-
-
- -{% include 'footer.html' %} -{% endblock %} - -{% block scripts %} - - - - - -{% endblock %} diff --git a/KNotes/templates/karma-events.html b/KNotes/templates/karma-events.html deleted file mode 100644 index 2dd9ab7..0000000 --- a/KNotes/templates/karma-events.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends 'navigation.html' %} - -{% block title %} - Karma Events -{% endblock %} - -{% block navinner %} -
-
-
-

Karma Events

- This is a list of activities that have added or subtracted to your karma score -
- - - - {% for event in events %} - - - - - - {% endfor %} - - - - -{% endblock %} diff --git a/KNotes/templates/karma_wall.html b/KNotes/templates/karma_wall.html deleted file mode 100644 index 5ec1f4b..0000000 --- a/KNotes/templates/karma_wall.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'index.html' %} -{% block content %} - -

Hey {{user.get_profile.getName}}!

-
-

You need a bit more Karma to {{permission}}!

-
-
-
-
-

{{request.user.get_profile.karma}} / {{ required_level.karma }}

-

Pts

- -{% endblock %} \ No newline at end of file diff --git a/KNotes/templates/modal/modal_add_course.html b/KNotes/templates/modal/modal_add_course.html deleted file mode 100644 index 3228056..0000000 --- a/KNotes/templates/modal/modal_add_course.html +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/KNotes/templates/modal/modal_login.html b/KNotes/templates/modal/modal_login.html deleted file mode 100644 index 1c92177..0000000 --- a/KNotes/templates/modal/modal_login.html +++ /dev/null @@ -1,31 +0,0 @@ -{# TODO: Is this used and does it work? the homepage has a different dropdown defined there that works #} - diff --git a/KNotes/templates/modal/modal_share_karma.html b/KNotes/templates/modal/modal_share_karma.html deleted file mode 100644 index f637698..0000000 --- a/KNotes/templates/modal/modal_share_karma.html +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/KNotes/templates/modal/modal_upload.html b/KNotes/templates/modal/modal_upload.html deleted file mode 100644 index 8a8a22a..0000000 --- a/KNotes/templates/modal/modal_upload.html +++ /dev/null @@ -1,166 +0,0 @@ -{% load url from future %} - - - - - - -{# TODO: Break this script into a js template file #} - - - - - diff --git a/KNotes/templates/models/school.html b/KNotes/templates/models/school.html deleted file mode 100644 index 540cba7..0000000 --- a/KNotes/templates/models/school.html +++ /dev/null @@ -1,18 +0,0 @@ - -
-
- -
- - -
-
diff --git a/KNotes/templates/navigation.html b/KNotes/templates/navigation.html deleted file mode 100644 index 5570b44..0000000 --- a/KNotes/templates/navigation.html +++ /dev/null @@ -1,532 +0,0 @@ -{% extends "base.html" %} -{% load url from future %} - -{% block title %} - Your Personal Profile Page {{ user.get_profile.getName }} -{% endblock %} - -{% block content %} - - -
-
-
- - -
- - - {% block navinner %} -
-

Welcome, {{ user.get_profile.getName }}!

- - {% comment %} - {# disabling messages for now #} - {# ------ Messages ------ #} - {% for message in messages %} -
- - {{ message.body}} -
- {% endfor %} - {% endcomment%} - - - -
- {% if user.get_profile.courses.all %} -

Your Courses

- {% for course in user.get_profile.courses.all %} - {% include 'course.html' with course=course %} - {% endfor %} - {% else %} -

You haven't added any courses yet

-

Would you like to

- - {% endif %} -
- - {% if request.user.get_profile.school != None %} -
- Would you like to add additional courses to your school? - - -
- {% endif %} - - {% if your_files %} -
-

Your Files

- {% for file in your_files %} - {% include 'file.html' with file=file %} - {% endfor %} -
- {% endif %} - -
-{% endblock %} -
-
-
-
- -{% endblock %} - -{% block scripts %} - - - - - - -{% endblock %} diff --git a/KNotes/templates/search_results2.html b/KNotes/templates/search_results2.html deleted file mode 100644 index 186efcc..0000000 --- a/KNotes/templates/search_results2.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends 'navigation.html' %} -{% load highlight %} - -{% block navinner %} -{% if results|length == 0 %} -

No Results

-{% endif %} -{% for result in results %} - - {% if result.object.viewCount %} - - {% include "file.html" with file=result.object %} - {% for highlight in result.highlighted.text %} - {{highlight|safe}} - {% endfor %} - - - - {% elif result.object.academic_year %} - - {% include "course.html" with course=result.object %} - - - {% elif result.object.name %} - - {% include "school.html" with school=result.object %} - {% endif %} - -{% endfor %} -{% if page.has_previous or page.has_next %} - -
- {% if page.has_previous %}{% endif %}« Previous{% if page.has_previous %}{% endif %} - | - {% if page.has_next %}{% endif %}Next »{% if page.has_next %}{% endif %} -
-{% endif %} - -{% endblock %} diff --git a/KNotes/templates/static/ToS.html b/KNotes/templates/static/ToS.html deleted file mode 100644 index 79d2bdb..0000000 --- a/KNotes/templates/static/ToS.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends "base.html" %} - -{% block scripts %} -{% endblock %} - -{% block content %} -
- -
-
-
-
-

KarmaNotes.org Terms of Service

-
-

Last updated: April 13, 2012

-

These Terms of Service ("Terms") govern your access to and use of the services, websites, and applications offered by KarmaNotes (the "Service"). Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms. By accessing or using the Service you agree to be bound by these Terms.

- -

Use of the Service

-

You may use the Service only if you can form a binding contract with KarmaNotes, and only in compliance with these Terms and all applicable local, state, national, and international laws, rules and regulations. You must provide us accurate information when you create your account on KarmaNotes. We may, without prior notice, change the Service; stop providing the Service or features of the Service, to you or to users generally; or create usage limits for the Service. You are responsible for safeguarding the password that you use to access the Service and for any activities or actions under your password. We encourage you to use "strong" passwords (that use a combination of upper and lower case letters, numbers and symbols) with your account. KarmaNotes will not be liable for any loss or damage arising from your failure to comply with these requirements.

- -

User Content

-

"Content" means any information, text, graphics, or other materials uploaded, downloaded or appearing on the Service. The original copyright holder retains all rights to any content that you (the user) submit, post, display, or otherwise make available on the Service.

- -

Your License to KarmaNotes

-

By submitting, posting or displaying Content on or through the Service, you grant KarmaNotes permission to share all content under a Creative Commons Attribution Share-Alike 3.0 Unported (CC BY-SA 3.0) license. For more information, see http://creativecommons.org/licenses/by-sa/3.0/us/ If you would like more information, please contact us directly.

- -

You agree that this license includes the right for other users of the Service to modify your Content, and for KarmaNotes to make your Content available to others for the publication, distribution, syndication, or broadcast of such Content on other media and services, subject to our terms and conditions for such Content use. Such additional uses by KarmaNotes or others may be made with no compensation paid to you with respect to the Content that you submit, post, transmit or otherwise make available through the Service. We may modify or adapt your Content in order to transmit, display or distribute it over computer networks and in various media and/or make changes to your Content as are necessary to conform and adapt that Content to any requirements or limitations of any networks, devices, services or media.

- - - -

KarmaNotes's Licenses to You

-

Subject to these Terms, KarmaNotes gives you a worldwide, royalty-free, non-assignable and non-exclusive license to re-post any of the Content on KarmaNotes anywhere on the rest of the web provided that the Content was added to the Service after June 22, 2011, and provided that the user who created the content has not explicitly marked the content as not for reproduction, and provided that you: (a) do not modify the Content; (b) attribute KarmaNotes with a human and machine-followable link (an A tag) linking back to the page displaying the original source of the content on KarmaNotes.org (c) upon request, either by KarmaNotes or a user, remove the user's name from Content which the user has subsequently made anonymous; (d) upon request, either by KarmaNotes or by a user who contributed to the Content, make a reasonable effort to update a particular piece of Content to the latest version on KarmaNotes.org; and (e) upon request, either by KarmaNotes or by a user who contributed to the Content, make a reasonable attempt to delete Content that has been deleted on KarmaNotes.org. Your Content will be viewable by other users of the Service and through third party services and websites. You should only provide Content for which you have received express consent to record and share under these Terms.

- -

All Content, whether publicly posted or privately transmitted, is the sole responsibility of the person who originated such Content. We may not monitor or control the Content posted via the Service. Any use of or reliance on any Content or materials posted via the Service or obtained by you through the Service is at your own risk. We do not endorse, support, represent or guarantee the completeness, truthfulness, accuracy, or reliability of any Content or communications posted via the Service or endorse any opinions expressed via the Service. You understand that by using the Service, you may be exposed to Content that might be offensive, harmful, inaccurate or otherwise inappropriate. Under no circumstances will KarmaNotes be liable in any way for any Content, including, but not limited to, any errors or omissions in any Content, or any loss or damage of any kind incurred as a result of the use of any Content made available via the Service or broadcast elsewhere. You are responsible for your use of the Service, for any Content you provide, and for any consequences thereof, including the use of your Content by other users and third parties partners. You understand that your Content may be republished and if you do not have the right to submit Content for such use, it may subject you to liability. KarmaNotes will not be responsible or liable for any use of your Content by KarmaNotes in accordance with these Terms. You represent and warrant that you have all the rights, power and authority necessary to grant the rights granted herein to any Content that you submit.

- -

We reserve the right at all times (but will not have an obligation) to remove or refuse to distribute any Content on the Service and to terminate users or reclaim usernames. We also reserve the right to access, read, preserve, and disclose any information as we reasonably believe is necessary to (i) satisfy any applicable law, regulation, legal process or governmental request, (ii) enforce the Terms, including investigation of potential violations hereof, (iii) detect, prevent, or otherwise address fraud, security or technical issues, (iv) respond to user support requests, or (v) protect the rights, property or safety of KarmaNotes, its users and the public.

- -

Rules

-

You must not do any of the following while accessing or using the Service: (i) use the Service for any unlawful purposes or for promotion of illegal activities; (ii) post any Content (as defined below) or use the Service in violation of any applicable law (including intellectual property laws, right of privacy or publicity laws, and any laws of a non-U.S. jurisdiction applicable to you), or any contractual or other legal obligation; (iii) post Content that is hateful, abusive, threatening, profane, or otherwise objectionable; (iv) post Content or use the Service to create an impression that you know is incorrect, misleading, or deceptive, including by impersonating others or otherwise misrepresenting your affiliation with a person or entity; (v) publish or post other people's private or personally identifiable information without their express authorization and permission; (vi) use the Service for the purpose of spamming anyone; (vii) publish or link to malicious content intended to damage or disrupt another user's browser or computer or to compromise a user's privacy; (viii) access or tamper with non-public areas of the Service, KarmaNotes's computer systems, or the technical delivery systems of KarmaNotes's providers; (ix) probe, scan, or test the vulnerability of any system or network or breach or circumvent any security or authentication measures; (x) access or search or attempt to access or search the Service by any means (automated or otherwise) other than through the currently available, published interfaces that are provided by KarmaNotes (and only pursuant to those terms and conditions), unless you have been specifically allowed to do so in a separate agreement with KarmaNotes (crawling the Service is permissible in accordance with these Terms, but scraping the Service without the prior consent of KarmaNotes except as permitted by these Terms is expressly prohibited); (xi) forge any TCP/IP packet header or any part of the header information in any email or posting, or in any way use the Service to send altered, deceptive or false source-identifying information; or (xii) interfere with or disrupt (or attempt to do so) the access of any user, host or network, including, without limitation, sending a virus, overloading, flooding, spamming, mail-bombing the Service, or by scripting the creation of Content in such a manner as to interfere with or create an undue burden on the Service.

- -

Proprietary Rights

-

All right, title, and interest in and to the Service (excluding Content provided by users) are and will remain the exclusive property of the original copyright holder.

- -

Copyright Policy

-

KarmaNotes respects the intellectual property rights of others and expects users of the Service to do the same. We will respond to notices of alleged copyright infringement that comply with applicable law and are properly provided to us. If you believe that your Content has been copied in a way that constitutes copyright infringement, please provide our copyright agent with the following information in accordance with the Digital Millennium Copyright Act: (i) a physical or electronic signature of the copyright owner or a person authorized to act on their behalf; (ii) identification of the copyrighted work claimed to have been infringed; (iii) identification of the material that is claimed to be infringing or to be the subject of infringing activity and that is to be removed or access to which is to be disabled, and information reasonably sufficient to permit us to locate the material; (iv) your contact information, including your address, telephone number, and an email address; (v) a statement by you that you have a good faith belief that use of the material in the manner complained of is not authorized by the copyright owner, its agent, or the law; and (vi) a statement that the information in the notification is accurate, and, under penalty of perjury, that you are authorized to act on behalf of the copyright owner. Our designated copyright agent for notice of alleged copyright infringement or other legal notices regarding Content appearing on the Service is: - -

KarmaNotes.org. Attn: Copyright Agent 1132 Massachusetts Avenue Cambridge, MA 02138 Email: Info [at] KarmaNotes.org

- -

Please note that in addition to being forwarded to the person who provided the allegedly illegal content, we may send a copy of your notice (with your personal information removed) to Chilling Effects (www.chillingeffects.org) for publication and/or annotation. You can see an example of such a publication at http://www.chillingeffects.org/fairuse/notice.cgi?NoticeID=16887. A link to your published notice will be displayed on KarmaNotes in place of the removed content. We reserve the right to remove Content alleged to be infringing or otherwise illegal without prior notice and at our sole discretion. In appropriate circumstances, KarmaNotes will also terminate a user's account if the user is determined to be a repeat infringer.

- -

Privacy

-

KarmaNotes values your privacy. We will never sell any personal user information stored on KarmaNotes.org with any third-party corporations or organizations without your express consent. By using the Service you consent to the transfer of your information to the United States and/or other countries for storage, processing and use by KarmaNotes. Note that user information stored with any of our affiliate services, such as docs.google.com, is subject to the terms of service of those service-providers. Please consult the respective Terms of Service for any affiliate services for specific details.

- -

Links

- -

The Service may contain links to third-party websites or resources. You acknowledge and agree that we are not responsible or liable for: (i) the availability or accuracy of such websites or resources; or (ii) the content, products, or services on or available from such websites or resources. Links to such websites or resources do not imply any endorsement by KarmaNotes of such websites or resources or the content, products, or services available from such websites or resources. You acknowledge sole responsibility for and assume all risk arising from your use of any such websites or resources. Disclaimers; Indemnity Your access to and use of the Service or any Content is at your own risk. You understand and agree that the Service is provided to you on an "AS IS" and "AS AVAILABLE" basis. Without limiting the foregoing, KARMANOTES AND ITS PARTNERS DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. We make no warranty and disclaim all responsibility and liability for the completeness, accuracy, availability, timeliness, security or reliability of the Service or any content thereon. KarmaNotes will not be responsible or liable for any harm to your computer system, loss of data, or other harm that results from your access to or use of the Service, or any Content. You also agree that KarmaNotes has no responsibility or liability for the deletion of, or the failure to store or to transmit, any Content and other communications maintained by the Service. We make no warranty that the Service will meet your requirements or be available on an uninterrupted, secure, or error-free basis. No advice or information, whether oral or written, obtained from KarmaNotes or through the Service, will create any warranty not expressly made herein. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, KARMANOTES AND ITS AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS AND LICENSORS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES, INCLUDING WITHOUT LIMITATION, LOSS OF PROFITS, DATA, USE, GOOD-WILL, OR OTHER INTANGIBLE LOSSES, RESULTING FROM (i) YOUR ACCESS TO OR USE OF OR INABILITY TO ACCESS OR USE THE SERVICE; (ii) ANY CONDUCT OR CONTENT OF ANY THIRD PARTY ON THE SERVICE, INCLUDING WITHOUT LIMITATION, ANY DEFAMATORY, OFFENSIVE OR ILLEGAL CONDUCT OF OTHER USERS OR THIRD PARTIES; (iii) ANY CONTENT OBTAINED FROM THE SERVICE; AND (iv) UNAUTHORIZED ACCESS, USE OR ALTERATION OF YOUR TRANSMISSIONS OR CONTENT, WHETHER BASED ON WARRANTY, CONTRACT, TORT (INCLUDING NEGLIGENCE) OR ANY OTHER LEGAL THEORY, WHETHER OR NOT KARMANOTES HAS BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGE, AND EVEN IF A REMEDY SET FORTH HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. Some jurisdictions do not allow the exclusion of certain warranties or the exclusion or limitation of liability for consequential or incidental damages, so the limitations above may not apply to you.

- -

If anyone brings a claim against us related to your actions or Content on the Service, or actions or Content by or from someone using your account, you will indemnify and hold us harmless from and against all damages, losses, and expenses of any kind (including reasonable legal fees and costs) related to such claim.

- -

General Terms

-

These Terms and any action related thereto will be governed by the laws of the State of Massachusetts without regard to or application of its conflict of law provisions or your state or country of residence. Unless submitted to arbitration as set forth in the following paragraph, all claims, legal proceedings or litigation arising in connection with the Service will be brought solely in Cambridge, Massachusetts, and you consent to the jurisdiction of and venue in such courts and waive any objection as to inconvenient forum. For any claim (excluding claims for injunctive or other equitable relief) under these Terms where the total amount of the award sought is less than $10,000, the party requesting relief may elect to resolve the dispute through binding non-appearance-based arbitration. The party electing such arbitration shall initiate the arbitration through an established alternative dispute resolution ("ADR") provider mutually agreed upon by the parties. The ADR provider and the parties must comply with the following rules: a) the arbitration shall be conducted by telephone, online and/or be solely based on written submissions, as selected by the party initiating the arbitration; b) the arbitration shall not involve any personal appearance by the parties or witnesses unless otherwise mutually agreed by the parties; and c) any judgment on the award rendered by the arbitrator may be entered in any court of competent jurisdiction. These Terms are the entire and exclusive agreement between KarmaNotes and you regarding the Service (excluding any services for which you have a separate agreement with KarmaNotes that is explicitly in addition or in place of these Terms), and these Terms supersede and replace any prior agreements between KarmaNotes and you regarding the Service. The failure of KarmaNotes to enforce any right or provision of these Terms will not be deemed a waiver of such right or provision. In the event that any provision of these Terms is held to be invalid or unenforceable, the remaining provisions of these Terms will remain in full force and effect. We may revise these Terms from time to time. The most current version will always be on this page (or such other page as the Service may indicate). If the revision, in our sole discretion, is material we will notify you via posting to our website or e-mail to the email associated with your account. By continuing to access or use the Service after those revisions become effective, you agree to be bound by the revised Terms.

- -

The Service is operated and provided by the FinalsClub Foundation, a 501(c)(3) non-profit corporation located at 1132 Massachusetts Avenue Cambridge, MA 02138. If you have questions about these Terms, please contact Info [at] KarmaNotes.org or Info [at] FinalsClub.org.

- -
-
-
-
-
-
-{% include 'footer.html' %} -{% endblock %} diff --git a/KNotes/templates/static/about.html b/KNotes/templates/static/about.html deleted file mode 100644 index 312353f..0000000 --- a/KNotes/templates/static/about.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "base.html" %} - -{% block scripts %} -{% endblock %} - -{% block content %} - -
- -
-
-
-
-

Welcome to KarmaNotes.org

-
- -

KarmaNotes is a community of sharing. By posting your notes, study guides, and assignments to the cloud, you will earn Karma Points and access to a growing database of academic resources. Thanks to scholars like you, we are making education more open, one lecture at a time.

-

Because KarmaNotes is a 501(c)(3) non-profit project all of our content is freely shared under Creative Commons Share-Alike license to encourage open access and remixing. We also respect the rights of copyright holders and maintain full compliance with the Digital Millenium Copyright Act. Please do not upload any copyrighted files or they may be subject to removal.

-

Share the academic wealth!

-

Want to learn more about KarmaNotes.org? Check out our free and open source code or email us: Info [at] KarmaNotes.org

-

Thanks Again,
- Team KarmaNotes

- -
-
-
-
-
-
-{% include 'footer.html' %} -{% endblock %} diff --git a/KNotes/templates/your-courses.html b/KNotes/templates/your-courses.html deleted file mode 100644 index f8626df..0000000 --- a/KNotes/templates/your-courses.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends 'navigation.html' %} - -{% block title %} - Your Courses -{% endblock %} - -{% block navinner %} -
-

Welcome, {{ user.get_profile.getName }}! Your Courses

-
- - -
-{% if request.user.get_profile.courses.all %} - {% for course in user.get_profile.courses.all %} - {% include 'course.html' with course=course %} - {% endfor %} -{% else %} -

You haven't added any courses yet

-

Would you like to -

-

-{% endif %} -
- -{% if request.user.get_profile.school != None %} -
- Would you like to add additional courses to your school? - - -
-{% endif %} - -{% endblock %} - From 82764503de9aba424c839883d95d593d085d947f Mon Sep 17 00:00:00 2001 From: Seth Woodworth Date: Wed, 19 Dec 2012 18:12:04 -0500 Subject: [PATCH 27/29] adding hashbang and encoding type for views.py --- notes/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/notes/views.py b/notes/views.py index 9a243c1..a0a571f 100644 --- a/notes/views.py +++ b/notes/views.py @@ -1,3 +1,5 @@ +#!/usr/bin/python2.7 +# -*- coding:utf8 -*- # Copyright (C) 2012 FinalsClub Foundation import datetime From a101e0d41cba2cce8d138c9610d844a476199a48 Mon Sep 17 00:00:00 2001 From: Seth Woodworth Date: Wed, 19 Dec 2012 18:35:54 -0500 Subject: [PATCH 28/29] cleanup settings.py and add formatting --- KNotes/settings.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/KNotes/settings.py b/KNotes/settings.py index 1728516..7dfeb2d 100644 --- a/KNotes/settings.py +++ b/KNotes/settings.py @@ -1,5 +1,9 @@ -# settings.py is part of Karma Notes -# Django settings for KNotes project. +#!/usr/bin/python2.7 +# -*- coding:utf8 -*- +""" Django settings for KNotes project. """ +import os + +from djcelery import setup_loader ''' Secrets ''' from notes.credentials import FACEBOOK_ID @@ -28,10 +32,7 @@ from notes.credentials import DEFAULT_FROM_EMAIL -import os -import djcelery -djcelery.setup_loader() # Is this running on the karmanotes.org box? DEPLOY = True @@ -86,16 +87,12 @@ ADMINS = ( ("Seth Woodworth", 'seth@finalsclub.org'), - ("David Brodsky", 'david@finalsclub.org'), ("Charles Holbrow", 'charles@finalsclub.org') ) MANAGERS = ADMINS -# For autocomplete -SIMPLE_AUTOCOMPLETE_MODELS = ('notes.School', 'notes.Course') - -TIME_ZONE = 'America/New_York' +IME_ZONE = 'America/New_York' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html @@ -302,7 +299,12 @@ 'notes', ) -# Django-Celery settings +###=========================== +# django-celery configuration + +setup_loader() + +# TODO: set up rabbitmq properly BROKER_URL = "django://" if DEPLOY: @@ -313,8 +315,12 @@ else: CELERY_RESULT_DBURI = "sqlite:///karmaNotes.sql" +# end django-celery conf +######################## + +###======================= +## HAYSTACK Configuration -### HAYSTACK Configuration HAYSTACK_SITECONF = 'notes.search_sites' HAYSTACK_SEARCH_ENGINE = 'solr' @@ -323,6 +329,9 @@ # ...or for multicore... #HAYSTACK_SOLR_URL = 'http://127.0.0.1:8983/solr/mysite' +## end HAYSTACK conf +###================== + # A sample logging configuration. The only tangible logging # performed by this configuration is to send an email to # the site admins on every HTTP 500 error when DEBUG=False. @@ -352,6 +361,9 @@ } } +###=================== +## AWS SES email conf + EMAIL_USE_TLS = True EMAIL_HOST = 'email-smtp.us-east-1.amazonaws.com' EMAIL_HOST_USER = SMTP_USERNAME @@ -361,6 +373,9 @@ TEMPLATED_EMAIL_TEMPLATE_DIR = 'templated_email/' # use '' for top level template dir, ensure there is a trailing slash TEMPLATED_EMAIL_FILE_EXTENSION = 'email' +## end SES email conf +###=================== + try: # For development, mv the initial file `dev_settings.py` to be named `local_settings.py` from local_settings import * From 25d5ecaaa82354d276fb96e1213b9be7d9118dd9 Mon Sep 17 00:00:00 2001 From: Charles Holbrow Date: Wed, 19 Dec 2012 18:39:38 -0500 Subject: [PATCH 29/29] removing redundant DB link between Reputation Events and UserProfile. Renaming related name of user and target in ReputationEvent. cleaning references. fixes #164 --- notes/models.py | 36 +++++++++++++++++++----------------- notes/views.py | 7 +++++-- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/notes/models.py b/notes/models.py index c8670a2..5681ce2 100644 --- a/notes/models.py +++ b/notes/models.py @@ -505,8 +505,8 @@ class ReputationEvent(models.Model): timestamp = models.DateTimeField(auto_now_add=True) # optional fkeys to related models. used for displaying activity for user/school/course - user = models.ForeignKey(User, blank=True, null=True, related_name='actor') # FIXME: rename actor_user - target = models.ForeignKey(User, blank=True, null=True, related_name='target') + user = models.ForeignKey(User, blank=True, null=True, related_name='reputation_event_actor') + target = models.ForeignKey(User, blank=True, null=True, related_name='reputation_event_target') file = models.ForeignKey(Note, blank=True, null=True) course = models.ForeignKey(Course, blank=True, null=True) school = models.ForeignKey(School, blank=True, null=True) @@ -541,7 +541,6 @@ class UserProfile(models.Model): # it is more efficient to incrementally tally the total value # vs summing all ReputationEvents every time karma is needed karma = models.IntegerField(default=0) - reputationEvents = models.ManyToManyField(ReputationEvent, blank=True, null=True) # Optional fields: # TODO: update this when User.save() is run, check if gravatar has an image for their email @@ -670,7 +669,6 @@ def get_name(self): def award_karma(self, event, target_user=None, school=None, course=None, user=None, file=None): """ Award user karma given a ReputationEventType slug title - and add a new ReputationEvent to UserProfile.reputationEvents Does not call UserProfile.save() because it is used in The UserProfile save() method @@ -699,10 +697,13 @@ def award_karma(self, event, target_user=None, school=None, course=None, user=No event.course = course if user: event.user = user + if target_user: + event.target = target_user + print 'UserProfile.award_karma: target user set' if file: event.file = file event.save() # FIXME: might be called on UserProfile.save() - self.reputationEvents.add(event) + # Don't self.save(), because this method is called # from UserProfile.save() return self @@ -711,34 +712,35 @@ def award_karma(self, event, target_user=None, school=None, course=None, user=No print e return False - def addFile(self, Note): + def addFile(self, note): """ Called by notes.views.upload after saving Note Generates the appropriate ReputationEvent, and modifies the user's karma """ - # Set Note.owner to the user - Note.owner = self.user - Note.save() + # Set note.owner to the user + note.owner = self.user + note.save() # Add this file to the user's collection - self.viewed_notes.add(Note) + self.viewed_notes.add(note) # Generate a reputation event title = "" - if Note.type == 'N': + if note.type == 'N': title = 'lecture-note' - elif Note.type == 'G': + elif note.type == 'G': title = 'mid-term-study-guide' - elif Note.type == 'S': + elif note.type == 'S': title = 'syllabus' - elif Note.type == 'A': + elif note.type == 'A': title = 'assignment' - elif Note.type == 'E': + elif note.type == 'E': title = 'exam-or-quiz' # Remember to load all ReputationEventTypes with # python manage.py loaddata ./fixtures/data.json repType = ReputationEventType.objects.get(title=title) - repEvent = ReputationEvent.objects.create(type=repType) - self.reputationEvents.add(repEvent) + repEvent = ReputationEvent.objects.create(type=repType, + user = self.user, + file = note) #FIXME: check if we need to add a school to this list # Assign user points as prescribed by ReputationEventType self.karma += repType.actor_karma diff --git a/notes/views.py b/notes/views.py index 9a243c1..8356d31 100644 --- a/notes/views.py +++ b/notes/views.py @@ -13,6 +13,7 @@ from django.contrib.auth import authenticate from django.contrib.auth import login from django.db.models import Count +from django.db.models import Q from django.http import HttpResponse from django.http import HttpResponseRedirect from django.http import Http404 @@ -35,6 +36,7 @@ from models import Instructor from models import Vote from models import ReputationEventType +from models import ReputationEvent from models import UsdeSchool @@ -65,11 +67,12 @@ def dashboard(request): response = {} - response['events'] = request.user.get_profile().reputationEvents.order_by('-id').all() + query = Q(user=request.user) | Q(target=request.user) + response['events'] = ReputationEvent.objects.filter(query).order_by('-id').all() response['upload_count'] = Note.objects.filter(owner=request.user).count() # Count the reputation events where the user was the actor and the type was 'upvote' - response['upvote_count'] = request.user.actor.filter(type__title='upvote').count() + response['upvote_count'] = request.user.reputation_event_actor.filter(type__title='upvote').count() return render(request, 'n_dashboard.html', response)
- - - {% include 'karma_events/person_log.html' with event=event %}
-
- at {{ event.timestamp }} -