Added archive_date for jobs.
authorJean-Michel Nirgal Vourgère <jmv@nirgal.com>
Mon, 8 Nov 2010 23:29:39 +0000 (23:29 +0000)
committerJean-Michel Nirgal Vourgère <jmv@nirgal.com>
Mon, 8 Nov 2010 23:29:39 +0000 (23:29 +0000)
Archive jobs after first download.
Added options to view archived jobs.
Changed user.info & user.error to use html rather that text.
Added user feedback message when a job is complete.
Miscalaneous improvement to jobs page.

bin/djais/models.py
bin/djais/urls.py
bin/djais/views.py
bin/jobrunner.py
html_templates/base.html
html_templates/jobs.html
html_templates/vessel.html
structure.sql

index ac4d461ceb86e7959d393be191b85edac05eb5a1..40eff377117ada856953d9aa1f0f55b92077e015 100644 (file)
@@ -5,6 +5,7 @@ from datetime import datetime
 from random import SystemRandom
 from django.db import models
 from django.contrib.auth.models import get_hexdigest
+from django.utils import html
 
 from ais.common import Nmea, mmsi_to_strmmsi, nice_timedelta_str
 
@@ -44,7 +45,7 @@ class User(models.Model):
         salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
         hsh = get_hexdigest(algo, salt, raw_password)
         self.password_hash = '%s$%s$%s' % (algo, salt, hsh)
-        self.info('Password changed')
+        self.info('Password changed') # FIXME
 
     def check_password(self, raw_password):
         algo, salt, hsh = self.password_hash.split('$')
@@ -76,10 +77,10 @@ class User(models.Model):
         return messages_dict
     
     def info(self, message):
-        UserMessage(user_id = self.id, category_id=u'info', txt=message).save()
+        UserMessage(user_id = self.id, category_id=u'info', txt=html.escape(message)).save()
 
     def error(self, message):
-        UserMessage(user_id = self.id, category_id=u'error', txt=message).save()
+        UserMessage(user_id = self.id, category_id=u'error', txt=html.escape(message)).save()
 
     def check_sandbox_access(self, source_user=None):
         SANDBOX_FLEET = 1
@@ -215,9 +216,16 @@ class Job(models.Model):
     extension = models.CharField(max_length=8)
     pid = models.IntegerField(blank=True, null=True)
     result = models.IntegerField(blank=True, null=True)
+    archive_time = models.DateTimeField(blank=True, null=True)
+
+    def nice_command(self):
+        if self.command.startswith('python -m'):
+            return self.command[len('python -m '):]
+        else:
+            return self.command
 
     def queue_rank(self):
-        return Job.objects.filter(queue_time__lt=self.queue_time).filter(start_time__isnull=True).count()
+        return Job.objects.filter(queue_time__lt=self.queue_time).filter(start_time__isnull=True).count() + 1
     def process_time(self):
         dt = self.finish_time - self.start_time
         return nice_timedelta_str(dt)
index 92cf239470b3917b6cee2d9bb18b95f4ecad80be..3f1d1aa5c7fe0bcc10d7aa52afc60b179a9d5737 100644 (file)
@@ -30,6 +30,7 @@ urlpatterns = patterns('',
     (r'^user/(?P<login>[a-zA-Z0-9_]+)/change_password$', 'ais.djais.views.user_change_password'),
     (r'^user/(?P<login>[a-zA-Z0-9_]+)/delete$', 'ais.djais.views.user_delete'),
     (r'^job/$', 'ais.djais.views.jobs_index'),
+    (r'^job/(?P<jobid>[A-Z0-9]+)$', 'ais.djais.views.job_get'),
     (r'^source/$', 'ais.djais.views.sources_index'),
     (r'^source/stats$', 'ais.djais.views.sources_stats'),
     (r'^news/(?P<page>\d*)$', 'ais.djais.views.news'),
index 789de7bf6168dc2911a3cc652ac2b01978d5835f..20b4a7d54972c4d4dda92cd918117a93bc881801 100644 (file)
@@ -660,7 +660,22 @@ def fleet_lastpos(request, fleetname):
 
 @http_authenticate(auth, 'ais')
 def jobs_index(request):
-    return render_to_response('jobs.html', {}, RequestContext(request))
+    show_archive = request.REQUEST.has_key('archive')
+    if show_archive:
+        jobs = request.user.job_set.all()
+    else:
+        jobs = request.user.job_set.filter(archive_time__isnull=True)
+    return render_to_response('jobs.html', {'jobs': jobs, 'archive': show_archive }, RequestContext(request))
+
+@http_authenticate(auth, 'ais')
+def job_get(request, jobid):
+    job = get_object_or_404(Job, id=jobid)
+    if job.user != request.user:
+        return HttpResponseForbidden('403 Forbidden')
+    if not job.archive_time:
+        job.archive_time = datetime.utcnow()
+        job.save()
+    return HttpResponseRedirect('/job_result/%s.%s' % (job.id, job.extension))
 
 @http_authenticate(auth, 'ais')
 def users(request):
index 108a0da3793735e68519b1a2774380502e7d7b4c..0d0b13ba53f44f8c6eee545d178f12b2c9be40b8 100755 (executable)
@@ -54,7 +54,7 @@ def main():
     dbcommit()
     logging.info('Job complete: result=%s', returncode)
 
-    sqlexec(u"INSERT INTO user_message (user_id, user_message_category_id, txt) VALUES(%(user_id)s, 'info', %(msg)s)", {'user_id':user_id, 'msg':'Your job %s is complete.' % jobid})
+    sqlexec(u"INSERT INTO user_message (user_id, user_message_category_id, txt) VALUES(%(user_id)s, 'info', %(msg)s)", {'user_id':user_id, 'msg':('Your <a href="/job/%(jobid)s">job %(jobid)s</a> is complete.' % {'jobid': jobid}) })
     dbcommit()
 
 if __name__ == '__main__':
index caa6ee32f089f28e788fec8edd96dee380d4a63c..01415943fb0ccf0733e3dcc657970977949fe8b1 100644 (file)
@@ -32,7 +32,7 @@
     <div style="padding:1ex;">
         {% for message in user.get_messages %}
             <div class=message>
-            {{ message.category.id }}: {{ message.txt }}
+            {{ message.category.id }}: {{ message.txt|safe }}
             </div>
         {% endfor %}
         {% block content %}{% endblock %}
index ab031f00288bcf08ce0737c25096849e98879ae3..85835410da49e156b6dcef02e7a2b4ef10089aec 100644 (file)
@@ -9,28 +9,33 @@
 
 {% block content %}
 
-{% if not user.job_set.all %}
-You don't have any job.
+{% if not jobs %}
+<p><b>No results.</b>
 {% endif %}
 <ul>
-{% for job in user.job_set.all %}
+{% for job in jobs %}
 <li>Job {{ job.id }}<br>
-Command:<br>
-<tt>{{ job.command }}</tt><br>
+<tt>{{ job.nice_command }}</tt><br>
 {% if job.finish_time %}
-Status: <b>Complete</b> in {{ job.process_time }} <br>
-Result: {% if job.result %}Error {{ job.result }}{% else %}Success<br><a href="/job_result/{{ job.id }}.{{ job.extension }}" class=button>download</a>{% endif %}<br>
+Status: <b>Completed</b> at {{ job.finish_time|date:"Y-m-d H:i:s" }} UTC in {{ job.process_time }}<br>
+Result: {% if job.result %}<b>Error {{ job.result }}</b>{% else %}<b>Success</b><br>
+<a href="/job/{{ job.id }}" class=button>download</a>{% endif %}<br>
 {% else %}
     {% if job.start_time %}
-    Status:<b>Running</b> since {{ job.start_time }}<br>
+    Status:<b>Running</b> since {{ job.start_time }}.<br>
     Pid: {{ job.pid }}<br>
     {% else %}
-    Status: <b>Queued</b> since {{ job.queue_time }}<br>
+    Status: <b>Queued</b> since {{ job.queue_time }}.<br>
     Position in jobs queue: {{ job.queue_rank }}<br>
     {% endif %}
 {% endif %}
 
 {% endfor %}
 </ul>
+{% if archive %}
+<a href="." class=button>Hide archived jobs</a>
+{% else %}
+<a href="?archive" class=button>Show archived jobs</a>
+{% endif %}
 
 {% endblock %}
index df76d7775f05e3a22f0fe499ad2824c8bf3c64c2..6ec6454b3d3d2e9faae4ad5d9d6fb3b30830702c 100644 (file)
@@ -58,7 +58,7 @@ One position every <input name=grain size=3 value=1><select name=grain_type>
 <option value=86400>day(s)</option>
 </select>
 <br>
-<input type=checkbox name=queue>Queue request (experimental/work in progress...)
+<input type=checkbox name=queue>Queue request (experimental)
 <br>
 <span id=csvhint style="display:none;">Make sure you select "Charset: UTF-8" and "Separated by: Coma" when you <a href="/oocalc_howto.png">choose import options</a>.<br></span>
 <input type=submit value=Get>
index c517e6882486bc769e2759755ebd50ee6c413d2a..8801105c7d9f4da01c38056587059ca77ad775c7 100644 (file)
@@ -102,6 +102,24 @@ CREATE SEQUENCE fleet_vessel_id_seq
 ALTER SEQUENCE fleet_vessel_id_seq OWNED BY fleet_vessel.id;
 
 
+--
+-- Name: job; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE job (
+    id character(8) NOT NULL,
+    user_id integer,
+    queue_time timestamp without time zone DEFAULT now() NOT NULL,
+    start_time timestamp without time zone,
+    finish_time timestamp without time zone,
+    command text NOT NULL,
+    extension character varying(8) NOT NULL,
+    pid integer,
+    result integer,
+    archive_time timestamp without time zone
+);
+
+
 --
 -- Name: mi_mmsi_i_seq; Type: SEQUENCE; Schema: public; Owner: -
 --
@@ -391,6 +409,14 @@ ALTER TABLE ONLY fleet_vessel
     ADD CONSTRAINT fleet_vessel_pkey2 UNIQUE (mmsi, fleet_id);
 
 
+--
+-- Name: job_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY job
+    ADD CONSTRAINT job_pkey PRIMARY KEY (id);
+
+
 --
 -- Name: mi_source_name_key; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
 --
@@ -510,6 +536,14 @@ ALTER TABLE ONLY fleet_vessel
     ADD CONSTRAINT fleet_vessel_mmsi_fkey FOREIGN KEY (mmsi) REFERENCES vessel(mmsi) ON UPDATE CASCADE ON DELETE CASCADE;
 
 
+--
+-- Name: job_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY job
+    ADD CONSTRAINT job_user_id_fkey FOREIGN KEY (user_id) REFERENCES "user"(id) ON UPDATE CASCADE ON DELETE SET NULL;
+
+
 --
 -- Name: user_father_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
 --