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.
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
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('$')
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
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)
(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'),
@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):
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__':
<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 %}
{% 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 %}
<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>
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: -
--
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:
--
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: -
--