diff --git a/gracedb/models.py b/gracedb/models.py index cf98c4956cc6dbdabc0c65aa6373d6b763e6ef22..dc95a9976c9b99dc94588f3dc869b71a8b46941c 100644 --- a/gracedb/models.py +++ b/gracedb/models.py @@ -14,7 +14,10 @@ import pytz, time SERVER_TZ = pytz.timezone(settings.TIME_ZONE) # Let's say we start here on schema versions -schema_version = "1.0" +# +# 1.0 -> 1.1 changed EventLog.comment from CharField(length=200) -> TextField +# +schema_version = "1.1" class User(models.Model): name = models.CharField(max_length=100) @@ -158,7 +161,7 @@ class EventLog(models.Model): created = models.DateTimeField(auto_now_add=True) issuer = models.ForeignKey(User) filename = models.CharField(max_length=100, default="") - comment = models.CharField(max_length=200, null=False, default="") + comment = models.TextField(null=False) def fileurl(self): if self.filename: @@ -176,6 +179,8 @@ class Labelling(models.Model): creator = models.ForeignKey(User) created = models.DateTimeField(auto_now_add=True) +# XXX Deprecated? Is this used *anywhere*? +# Appears to only be used in models.py. Here and Event class as approval_set class Approval(models.Model): COLLABORATION_CHOICES = ( ('L','LIGO'), ('V','Virgo'), ) approver = models.ForeignKey(User) diff --git a/gracedb/urls.py b/gracedb/urls.py index 3505e42abd1ab3b461832aa1b58123d40051fdf4..7c79a0ca409686ce4565528eca13ebc1d95a4a82 100644 --- a/gracedb/urls.py +++ b/gracedb/urls.py @@ -14,6 +14,7 @@ urlpatterns = patterns('gracedb.views', url (r'^skyalert/(?P<graceid>[\w\d]+)', 'skyalert', name="skyalert"), url (r'^(?P<graceid>[\w\d]+)$', 'view', name="view2"), url (r'^(?P<graceid>[\w\d]+)/files/(?P<filename>.+)$', download, name="file"), + url (r'^(?P<graceid>[\w\d]+)/log/(?P<num>([\d]*|preview))$', 'logentry', name="logentry"), # (r'^view/(?P<uid>[\w\d]+)', 'view'), diff --git a/gracedb/views.py b/gracedb/views.py index 1c8ee6ef0a36f2231fafbb901701ad7407d682dc..0b93146cf8aa5f5c0f113a8640623f5af6e64fd0 100644 --- a/gracedb/views.py +++ b/gracedb/views.py @@ -3,7 +3,7 @@ from django.http import HttpResponse from django.http import HttpResponseRedirect, HttpResponseNotFound, HttpResponseBadRequest, Http404 from django.template import RequestContext from django.core.urlresolvers import reverse, get_script_prefix -from django.shortcuts import render_to_response +from django.shortcuts import render_to_response, get_object_or_404 from django.contrib.sites.models import Site from django.utils.html import strip_tags, escape, urlize from django.utils.safestring import mark_safe @@ -16,6 +16,7 @@ from alert import issueAlert, issueAlertForLabel, issueAlertForUpdate from translator import handle_uploaded_data import urllib +import markdown import os import re @@ -32,6 +33,7 @@ MAX_QUERY_RESULTS = 1000 GRACEDB_DATA_DIR = settings.GRACEDB_DATA_DIR import simplejson +import json def index(request): # assert request.ligouser @@ -387,6 +389,76 @@ def cli_label(request): return response +import html5lib +def sanitize_html(data): + """ + + >>> sanitize_html5lib("foobar<p>adf<i></p>abc</i>") + u'foobar<p>adf<i></i></p><i>abc</i>' + >>> sanitize_html5lib('foobar<p style="color:red; remove:me; background-image: url(http://example.com/test.php?query_string=bad);">adf<script>alert("Uhoh!")</script><i></p>abc</i>') + u'foobar<p style="color: red;">adf<script>alert("Uhoh!")</script><i></i></p><i>abc</i>' + """ + from html5lib import treebuilders, treewalkers, serializer, sanitizer + + p = html5lib.HTMLParser(tokenizer=sanitizer.HTMLSanitizer, tree=treebuilders.getTreeBuilder("dom")) + dom_tree = p.parseFragment(data) + + walker = treewalkers.getTreeWalker("dom") + + stream = walker(dom_tree) + + s = serializer.htmlserializer.HTMLSerializer(omit_optional_tags=False) + return "".join(s.serialize(stream)) + + +def logentry(request, graceid, num=None): + try: + event = Event.getByGraceid(graceid) + except Event.DoesNotExist: + raise Http404 + if request.method == "POST": + # create a log entry + elog = EventLog(event=event, issuer=request.ligouser) + elog.comment = request.POST.get('comment') or request.GET.get('comment') + elog.save() + else: + try: + elog = event.eventlog_set.order_by('created').all()[int(num)] + except Exception, e: + raise Http404 + + rv = {} + rv['comment'] = elog.comment + rv['issuer'] = elog.issuer.name + rv['created'] = elog.created.isoformat() + rv['comment'] = elog.comment + + return HttpResponse(json.dumps(rv), content_type="application/json") + +def markdownlogentry(request, graceid, num=None): + try: + event = Event.getByGraceid(graceid) + except Event.DoesNotExist: + raise Http404 + if request.method == "POST": + # create a log entry + # XXX num can be None or 'preview' + comment = request.POST.get('comment') or request.GET.get('comment') + + elog = EventLog(event=event, issuer=request.ligouser) + elog.comment = comment + elog.save() + # XXX janky. optimize this. + elogIndex = list(event.eventlog_set.order_by('created').all()).index(elog) + return HttpResponse(reverse(logentry, args=[event.graceid(), elogIndex])) + else: + try: + text = event.eventlog_set.order_by('created').all()[int(num)].comment + text = markdown.markdown(text) + return HttpResponse(text) + except Exception, e: + raise Http404 + def log(request): message = request.POST.get('message') graceid = request.POST.get('graceid') diff --git a/settings/default.py b/settings/default.py index 140960ed14d057b8958298fc11ac6c660693d88b..72608deae48e7e069f611c52a0493f148579a071 100644 --- a/settings/default.py +++ b/settings/default.py @@ -181,6 +181,9 @@ INSTALLED_APPS = ( 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', + 'django.contrib.markup', + 'django_wysiwyg', 'gracedb', 'userprofile', + 'slink', ) diff --git a/templates/gracedb/event_detail.html b/templates/gracedb/event_detail.html index 136b972f0357ec6659f5a9b0177d5ecf54d1c8f7..712de5a31bd29ad7170b9047e190fe1a0ad11744 100644 --- a/templates/gracedb/event_detail.html +++ b/templates/gracedb/event_detail.html @@ -1,67 +1,153 @@ {% extends "base.html" %} {% load timeutil %} {% load scientific %} +{% load sanitize_html %} {% load markup %} -{% load wysiwyg %} +{# load wysiwyg #} {% block title %}View {{ object.graceid }}{% endblock %} {% block heading %}{% endblock %} +{% block bodyattrs %}class="tundra"{% endblock %} {% block jscript %} {# http/https depending on this pages' protocol #} - {% wysiwyg_setup %} + {# wysiwyg_setup #} <script type="text/javascript" - src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=AM_HTMLorMML-full"> + src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> <script src="http{% if request.is_secure %}s{% endif %}://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js" type="text/javascript"></script> <script type="text/javascript"> - dojo.require("dojox.image.LightboxNano"); - dojo.require("dojo.parser"); - dojo.ready(function(){ + + require(["dijit/Editor", + "dojox/editor/plugins/Save", + "dojox/editor/plugins/Preview", + "dojox/image/LightboxNano", + "dijit/_editor/plugins/TextColor", + "dijit/_editor/plugins/LinkDialog", + "dijit/_editor/plugins/ViewSource", + "dijit/_editor/plugins/NewPage", + "dijit/_editor/plugins/FullScreen", + "dojo/parser", + "dojo/domReady!"], function (Editor, Save, Preview) { + dojo.parser.parse(); - setupform(); - }); - function handleNewEntry(target, data) { - target.innerHTML = data.issuer + " / " + data.created + " / " + data.comment; - return; - var xhrArgs = { - url: data, - handleAs: "json", - load: function(data) { - target.innerHTML = data; - }, - error: function(error) { - target.innerHTML = "Errorz: " + error; - } - }; - dojo.byId("result").innerHTML = "Sending more..." - var deferred = dojo.xhrGet(xhrArgs); - }; - - function setupform() { + var newlogdiv = dojo.byId("newlogdiv"); + var logtitle = dojo.byId("logmessagetitle"); + var logtext = dojo.byId("newlogtext"); var form = dojo.byId("newlog"); - dojo.connect(form, "onsubmit", function(event) { - dojo.stopEvent(event); - var xhrArgs = { - form: dojo.byId("newlog"), - handleAs: "json", - load: function(data){ - //alert("load: " + data); - //dojo.byId("result").innerHTML = data; - handleNewEntry(dojo.byId("result"), data); + + dojo.style(newlogdiv, {'display':'none'}); + + logtitle.innerHTML = logtitle.innerHTML + " [click to create a new log entry]"; + var editor_div = dojo.create('div'); + var preview_div = dojo.create('div'); + dojo.place(editor_div, newlogdiv, "after"); + dojo.place(preview_div, editor_div, "before"); + dojo.style(preview_div, { 'display':'none'}); + dojo.style(editor_div, { 'display':'none'}); + + var savebutton = new Save({ + url: "{% url logentry object.graceid "" %}", + onSuccess: function (resp, ioargs) { + //this.inherited(resp, ioargs); + this.button.set("disabled", false); + location.reload(true); }, - error: function(error){ - //alert("error: " + error); - dojo.byId("result").innerHTML = error; + onError: function (error, ioargs) { + //this.inherited(error, ioargs); + this.button.set("disabled", false); + alert(error); + }, + save: function(postdata) { + var postArgs = { + url: this.url, + content: { comment: postdata }, + handleAs: "json" + }; + this.button.set("disabled", true); + var deferred = dojo.xhrPost(postArgs); + deferred.addCallback(dojo.hitch(this, this.onSuccess)); + deferred.addErrback(dojo.hitch(this, this.onError)); } - }; - dojo.byId("result").innerHTML = "Sending..." - var deferred = dojo.xhrPost(xhrArgs); }); - }; + + var previewbutton = new Preview({ + _preview: function(){ + var content = this.editor.get("value"); + preview_div.innerHTML = editor.get('value'); + dojo.style(preview_div, { + 'display':'block', + 'border': ".2em solid #900", + 'padding': '10px' + }); + MathJax.Hub.Queue(["Typeset",MathJax.Hub, preview_div]); + } + }); + + +// dojo.connect(form, "onsubmit", function(event) { +// dojo.stopEvent(event); +// var postdata = editor.get('value'); +// var xhrArgs = { +// url: "{% url logentry object.graceid "" %}", +// content: { comment: postdata}, +// handleAs: "json", +// load: function(data) { +// location.reload(true); +// }, +// error: function(data) { +// alert("Problem submitting: " + data); +// }, +// }; +// var deferred = dojo.xhrPost(xhrArgs); +// }); + + dojo.connect(logtitle, "click", function(event) { + dojo.style(editor_div, {'display':'block'}); + }); + +// var cancelButton = dojo.byId('cancel'); +// dojo.connect(cancelButton, "onclick", function(event) { +// dojo.style(editor_div, {'display':'none'}); +// dojo.style(logtext, {'display':'block'}); +// }); + +// var previewButton = dojo.byId('preview'); +// dojo.connect(previewButton, "onclick", function(event) { +// var result = dojo.byId("result"); +// result.innerHTML = editor.get('value'); +// MathJax.Hub.Queue(["Typeset",MathJax.Hub, result]); +// }); + + var editor = new Editor({ + extraPlugins : ['foreColor','hiliteColor','|','createLink', + 'insertImage','fullscreen','viewsource','newpage', '|', previewbutton, savebutton] + }, editor_div); + + dojo.connect(logtext, 'click', function(event) { + dojo.style(logtext, {'display':'none'}); + dojo.style(editor_div, {'display':'block'}); + editor.set('value', logtext.value); + editor.focus(); + }); + + }); + </script> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/PageBreak.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/ShowBlockNodes.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/Preview.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/Save.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/Breadcrumb.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/FindReplace.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/PasteFromWord.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/InsertAnchor.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/CollapsibleToolbar.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/Blockquote.css" /> + <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/editor/plugins/resources/css/Smiley.css" /> <style> + @import "https://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dijit/themes/tundra/tundra.css"; a:hover .dojoxEnlarge { display: block !important; } @@ -188,35 +274,34 @@ </table> {% endif %} + +<div id="newlogdiv"> + <h3>Create a new log entry</h3> + <form id="newlog" action="{% url logentry object.graceid "" %}" method="POST"> + <textarea id="newlogtext" name="comment" style="width:300px;display:block"></textarea> + <input type="submit" value="Submit"/> + <input type="button" value="Preview" id="preview" /> + <input type="button" value="Cancel" id="cancel" /> + </form> +</div> + {% if object.eventlog_set.count %} <p/> - <h3>Event Log Messages</h3> + <h3 id="logmessagetitle">Event Log Messages</h3> <table class="event"> <tr> - <th>{{ "logtime"|timeselect:"utc" }} Created / Creator</th> + <th>{{ "logtime"|timeselect:"utc" }} Log Entry Created</th> + <th>Submitter</th> <th>Comment</th> <th> </th> </tr> - <tr> - <td></td> - <td><div id="result">empty `x = (-b +- sqrt(b^2-4ac))/(2a) .` </div></td> - </tr> - <tr> - <td>{{ ligouser }}</td> - <td><form id="newlog" action="{% url logentry object.graceid "" %}" method="POST"> - <input type="submit"/> - <textarea id="newlogtext" name="comment"></textarea> - {% wysiwyg_editor "newlogtext" %} - </form> - </td> - <td></td> - </tr> {% for log in object.eventlog_set.iterator %} <tr class="{% cycle 'odd' 'even'%}"> - <td>{{log.created|multiTime:"logtime"}}<br/>{{log.issuer}}</td> - <td>{{log.comment|safe}} + <td>{{log.created|multiTime:"logtime"}}</td> + <td>{{log.issuer}}</td> + <td>{{log.comment|sanitize}} {% if log.fileurl %} <a href="{{log.fileurl}}">{{log.filename}}</a> {% endif %} diff --git a/templates/gracedb/histogram.html b/templates/gracedb/histogram.html index 19ab15124e84cc343162aadf5a003e0228f4e739..173a3d3bedc4690e290ee7fc9d79cc9cca56dbfa 100644 --- a/templates/gracedb/histogram.html +++ b/templates/gracedb/histogram.html @@ -17,7 +17,8 @@ function toggle(id) { } </script> -<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js" +{# http/https depending on this pages' protocol #} +<script src="http{% if request.is_secure %}s{% endif %}://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js" data-dojo-config="async: true, isDebug: false, parseOnLoad: true"> </script> diff --git a/templates/gracedb/latest.html b/templates/gracedb/latest.html index db64abc836ef3b9021873c81f4143f7548c47836..482123e4e0e5113fbf306e4166f71fd7e2167388 100644 --- a/templates/gracedb/latest.html +++ b/templates/gracedb/latest.html @@ -19,7 +19,8 @@ {% endif %} - <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js" type="text/javascript"></script> + {# http/https depending on this pages' protocol #} + <script src="http{% if request.is_secure %}s{% endif %}://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js" type="text/javascript"></script> <script type="text/javascript"> dojo.require("dojox.image.LightboxNano"); dojo.require("dojo.parser");