From 63c646a22b4a8ba1d2ef316c56f8bc04e42b59c3 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 16:51:16 -0700 Subject: [PATCH 01/48] Fix use of debug logger. --- neoexchange/ingest/ephem_subs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/ingest/ephem_subs.py index 9965c3695..492b0305d 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/ingest/ephem_subs.py @@ -508,7 +508,7 @@ def determine_darkness_times(site_code, utc_date=datetime.utcnow(), debug=False) utc_date = datetime.combine(utc_date, time()) (start_of_darkness, end_of_darkness) = astro_darkness(site_code, utc_date) end_of_darkness = end_of_darkness+timedelta(hours=1) - logger.debug("Start,End of darkness=", start_of_darkness, end_of_darkness) + logger.debug("Start,End of darkness=%s %s", start_of_darkness, end_of_darkness) if utc_date > end_of_darkness: logger.debug("Planning for the next night") utc_date = utc_date + timedelta(days=1) @@ -517,10 +517,10 @@ def determine_darkness_times(site_code, utc_date=datetime.utcnow(), debug=False) utc_date = utc_date + timedelta(days=-1) utc_date = utc_date.replace(hour=0, minute=0, second=0, microsecond=0) - logger.debug("Planning observations for", utc_date, "for", site_code) + logger.debug("Planning observations for %s for %s", utc_date, site_code) # Get hours of darkness for site (dark_start, dark_end) = astro_darkness(site_code, utc_date) - logger.debug("Dark from ", dark_start, "to", dark_end) + logger.debug("Dark from %s to %s", dark_start, dark_end) return dark_start, dark_end From 8b83b6257eeff621807d75b10eee4cf357618ef7 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 16:54:54 -0700 Subject: [PATCH 02/48] Fix broken tests for Edwards' use of new Form and incorrect spelling... --- .../ingest/templates/ingest/ephem.html | 2 +- neoexchange/ingest/tests/test_views.py | 87 ++++++++++++------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index f3c95717b..562a7a1fd 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -9,7 +9,7 @@ {% block extramenu %}
-

Emphemeris for {{ new_target_name }} at {{ site_code }}

+

Ephemeris for {{ new_target_name }} at {{ site_code }}

{% endblock%} diff --git a/neoexchange/ingest/tests/test_views.py b/neoexchange/ingest/tests/test_views.py index 43caf867f..c7c987660 100644 --- a/neoexchange/ingest/tests/test_views.py +++ b/neoexchange/ingest/tests/test_views.py @@ -27,6 +27,7 @@ from ingest.ephem_subs import call_compute_ephem, determine_darkness_times from ingest.views import home, clean_NEOCP_object from ingest.models import Body +from ingest.forms import EphemQuery class TestClean_NEOCP_Object(TestCase): @@ -82,28 +83,33 @@ def test_missing_absmag(self): class HomePageTest(TestCase): - def test_root_url_resolves_to_home_page_view(self): - found = resolve('/') - self.assertEqual(found.func, home) - - def test_home_page_returns_correct_html(self): - request = HttpRequest() - response = home(request) - expected_html = render_to_string('ingest/home.html') - self.assertEqual(response.content.decode(), expected_html) - - def test_home_page_redirects_after_GET(self): - request = HttpRequest() - request.method = 'GET' - request.GET['target_name'] = 'New target' + def setUp(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body = Body.objects.create(**params) + self.body.save() - response = home(request) + def test_home_page_renders_home_template(self): + response = self.client.get('/') + self.assertTemplateUsed(response, 'ingest/home.html') - self.assertEqual(response.status_code, 302) - self.assertEqual(response['location'], '/ephemeris/') + def test_home_page_uses_ephemquery_form(self): + response = self.client.get('/') + self.assertIsInstance(response.context['form'], EphemQuery) - def test_home_page_ephem_form_shows_current_date(self): - pass class EphemPageTest(TestCase): maxDiff = None @@ -134,41 +140,58 @@ def test_home_page_can_save_a_GET_request(self): dark_start, dark_end = determine_darkness_times(site_code, utc_date ) response = self.client.get('/ephemeris/', - data={'target_name' : 'N999r0q', - 'site_code' : site_code, - 'utc_date' : '2015-04-21'} + data={'target' : 'N999r0q', + 'site_code' : site_code, + 'utc_date' : '2015-04-21', + 'alt_limit' : 0} ) self.assertIn('N999r0q', response.content.decode()) body_elements = model_to_dict(self.body) ephem_lines = call_compute_ephem(body_elements, dark_start, dark_end, site_code, '5m' ) expected_html = render_to_string( 'ingest/ephem.html', - {'new_target_name' : 'N999r0q', + {'new_target_name' : 'N999r0q', 'ephem_lines' : ephem_lines, 'site_code' : site_code } ) self.assertMultiLineEqual(response.content.decode(), expected_html) def test_displays_ephem(self): - response = self.client.get('/ephemeris/', data={'target_name' : 'N999r0q'}) - self.assertContains(response, 'Computing ephemeris for') + response = self.client.get('/ephemeris/', + data ={'target' : 'N999r0q', + 'utc_date' : '2015-05-11', + 'site_code' : 'V37', + 'alt_limit' : 30.0 + } + ) + self.assertContains(response, 'Ephemeris for') def test_uses_ephem_template(self): - response = self.client.get('/ephemeris/', data={'target_name' : 'N999r0q'}) + response = self.client.get('/ephemeris/', + data = {'target' : 'N999r0q', + 'site_code' : 'W86', + 'utc_date' : '2015-04-20', + 'alt_limit' : 40.0 + } + ) self.assertTemplateUsed(response, 'ingest/ephem.html') def test_form_errors_are_sent_back_to_home_page(self): - response = self.client.get('/ephemeris/', data={'target_name' : ''}) + response = self.client.get('/ephemeris/', data={'target' : ''}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'ingest/home.html') - expected_error = escape("You didn't specify a target") + expected_error = escape("Target name is required") self.assertContains(response, expected_error) def test_ephem_page_displays_site_code(self): - response = self.client.get('/ephemeris/', - data={'target_name' : 'N999r0q', 'site_code' : 'F65'}) - self.assertContains(response, - 'Computing ephemeris for: N999r0q for F65') + response = self.client.get('/ephemeris/', + data = {'target' : 'N999r0q', + 'site_code' : 'F65', + 'utc_date' : '2015-04-20', + 'alt_limit' : 30.0 + } + ) + self.assertContains(response, 'Ephemeris for N999r0q at F65') class TargetsPageTest(TestCase): From 79bd28df7c083920aaf7f4344d8d4d89e41867bb Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 18:05:15 -0700 Subject: [PATCH 03/48] Add unit tests for EphemQuery form --- neoexchange/ingest/tests/__init__.py | 1 + neoexchange/ingest/tests/test_forms.py | 68 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 neoexchange/ingest/tests/test_forms.py diff --git a/neoexchange/ingest/tests/__init__.py b/neoexchange/ingest/tests/__init__.py index d8090a0f9..5093a70d7 100644 --- a/neoexchange/ingest/tests/__init__.py +++ b/neoexchange/ingest/tests/__init__.py @@ -1,4 +1,5 @@ from test_ast_subs import * from test_ephem_subs import * +from test_forms import * from test_source_subs import * from test_views import * diff --git a/neoexchange/ingest/tests/test_forms.py b/neoexchange/ingest/tests/test_forms.py new file mode 100644 index 000000000..4d8bbd0fa --- /dev/null +++ b/neoexchange/ingest/tests/test_forms.py @@ -0,0 +1,68 @@ +''' +NEO exchange: NEO observing portal for Las Cumbres Observatory Global Telescope Network +Copyright (C) 2014-2015 LCOGT + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +''' + +from django.test import TestCase +from ingest.models import Body + +#Import module to test +from ingest.forms import EphemQuery + +class EphemQueryFormTest(TestCase): + + def setUp(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body = Body.objects.create(**params) + self.body.save() + + def test_form_has_label(self): + form = EphemQuery() + self.assertIn('Enter target name...', form.as_p()) + self.assertIn('Site code:', form.as_p()) + + def test_form_validation_for_blank_target(self): + form = EphemQuery(data = {'target' : ''}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['target'], + ['Target name is required'] + ) + + def test_form_validation_for_blank_date(self): + form = EphemQuery(data = {'utc_date' : ''}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['utc_date'], + ['UTC date is required'] + ) + + def test_form_handles_save(self): + form = EphemQuery(data = {'target' : 'N999r0q', + 'utc_date' : '2015-04-20', + 'site_code' : 'K92', + 'alt_limit' : 30.0 + }) + self.assertTrue(form.is_valid()) From 597285ad5b74ba9646988b59dfc03ab3bc809b3e Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 18:09:34 -0700 Subject: [PATCH 04/48] Add start of functional tests for scheduling observations --- neoexchange/neox/tests/__init__.py | 1 + .../neox/tests/test_schedule_observations.py | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 neoexchange/neox/tests/test_schedule_observations.py diff --git a/neoexchange/neox/tests/__init__.py b/neoexchange/neox/tests/__init__.py index 5d4332eea..138cec777 100644 --- a/neoexchange/neox/tests/__init__.py +++ b/neoexchange/neox/tests/__init__.py @@ -2,3 +2,4 @@ from test_ephemeris_validation import * from test_layout_and_styling import * from test_targets_validation import * +from test_schedule_observations import * diff --git a/neoexchange/neox/tests/test_schedule_observations.py b/neoexchange/neox/tests/test_schedule_observations.py new file mode 100644 index 000000000..a825a17e1 --- /dev/null +++ b/neoexchange/neox/tests/test_schedule_observations.py @@ -0,0 +1,66 @@ +from .base import FunctionalTest +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import Select +from datetime import datetime +from ingest.models import Body + +class ScheduleObservations(FunctionalTest): + + def insert_test_bodies(self): + params1 = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body = Body.objects.create(**params1) + self.body.save() + + params2 = { 'provisional_name' : 'WH2845B', + 'abs_mag' : 18.2, + 'slope' : 0.15, + 'epochofel' : '2015-06-27 00:00:00', + 'meananom' : 25.57309, + 'argofperih' : 314.41870, + 'longascnode' : 224.52430, + 'orbinc' : 31.31052, + 'eccentricity' : 0.5356964, + 'meandist' : 2.6132962, + 'source_type' : 'N', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body = Body.objects.create(**params2) + self.body.save() + + def test_can_schedule_observations(self): + + ## Insert test body otherwise things will fail + self.insert_test_bodies() + + # Eduardo has heard about a new website for NEOs. He goes to the + # homepage + self.browser.get(self.live_server_url) + + # She sees a link to TARGETS + link = self.browser.find_element_by_link_text('TARGETS') + target_url = self.live_server_url + '/target/' + self.assertEqual(link.get_attribute('href'), target_url) + + # She clicks the link to go to the TARGETS page + link.click() + self.browser.implicitly_wait(3) + new_url = self.browser.current_url + self.assertEqual(str(new_url), target_url) + From d3f0fbd2a08f58a88015df616272219c0aa35388 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 23:11:53 -0700 Subject: [PATCH 05/48] Add missing icon and jquery.dj.selectable.js file which were causing the Django TestServer to die horribly with 500 errors --- .../ingest/static/ingest/images/favicon.ico | Bin 0 -> 2238 bytes .../js/jquery.dj.selectable.js?v=0.8.0 | 352 ++++++++++++++++++ neoexchange/ingest/templates/ingest/base.html | 4 +- 3 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 neoexchange/ingest/static/ingest/images/favicon.ico create mode 100644 neoexchange/ingest/static/selectable/js/jquery.dj.selectable.js?v=0.8.0 diff --git a/neoexchange/ingest/static/ingest/images/favicon.ico b/neoexchange/ingest/static/ingest/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7a851a5e74ececf101594f6863276cc6896eb58b GIT binary patch literal 2238 zcmeHHYgklO6kR7T31*Z+s6d6$11gd1p=AY@f|8lq_`tx5Fdw9VMNyejsR-r>NI{_@ zaRI?bUYDz%jtK%o2*EcRf|#J7DMB;|0-?=m-vN{~|Mk_M*1h{Y_S$=$bM`&o1sFXJ z4lrS-IbcI0Co%^zcQOWY62b&icSFND2X}J=v_|6UG3Wa^h>B{4F)K3DmAXoF2L-{=u@wCHajeTNz|y5H$jNEP$&;6H+SV5K*{6}4n~Q2gHCE~k z;QRK$Cd~%5v{pn!Hn_JpTn=-L9eWnqR@%e*_1J%8Ka6x3JG)=7q<9HhTU$|9){WXu zjtjA|NKV#bbv?(3Vvf4HGvKbS;H9NVOH0F#wYA`umQbmhaPZ(k@N?&2vBLs>em^56 zr5y%mXDqL!bL`%YkdR-|*wKhKV;d$P?gS@o?&SsE(E;CTjzbj{xV&l=nwzhHFJ6q{ z!%ySVr9ZH-ful1u6%#9r(CKs-KmHts9^x=JKLsD33#h57fnKjiXGas*Hio+9I<%?P zs4DvbNl8h_Y2lQo1)nehEnPh*ZsX{nH7~}+Vf5%m?4ufSYil?=Q(b4y1h1?_U|VO3)OT?-{?>?YqY-9i_28bKsH&=hwP+2mBOHfy#aK|qVIYkPs^NhH2arhjXz$*= z$fG@_q@*A#D+?9{9QqPH_@qg&EaI3wy9vCh355j(a4q9NEMRCgAcE?xE7F16+r!1> z4BFeq~fSASDo<~C1Iz^vtnoo@~xC&AIoJ3eFg_3(z>I5Ppuab;K zf3-)Frup3>NshGssPGu#oT9Gj2CEsZsGQd ze}KLw%}$c;czLlTO}6|r=bd@;VqzXj)AVOkpB^+=IsfNNSfY8j31$Q28CyUx?|Uz& zHrCzUebapRRdW?rFQOJMTsYC@?yzO|+!+w?_M@WsTI}!(6JKh36T2HNb+YWqG=qO_5zp^i|$DKp').addClass('ui-widget selectable-deck selectable-deck-' + style); + if (style === 'bottom' || style === 'bottom-inline') { + $(this.element).after(this.deck); + } else { + $(this.element).before(this.deck); + } + $(self.hiddenMultipleSelector).each(function (i, input) { + self._addDeckItem(input); + }); + }, + + _addDeckItem: function (input) { + /* Add new deck list item from a given hidden input */ + var self = this; + var li = $('
  • ') + .text($(input).attr('title')) + .addClass('selectable-deck-item'); + var item = {element: self.element, input: input, wrapper: li, deck: self.deck}; + if (self._trigger("add", null, item) === false) { + input.remove(); + } else { + var button = $('
    ') + .addClass('selectable-deck-remove') + .append( + $('') + .attr('href', '#') + .button({ + icons: { + primary: self.options.removeIcon + }, + text: false + }) + .click(function (e) { + e.preventDefault(); + if (self._trigger("remove", e, item) !== false) { + $(input).remove(); + li.remove(); + } + }) + ); + li.append(button).appendTo(this.deck); + } + }, + + select: function (item, event) { + /* Trigger selection of a given item. + Item should contain two properties: id and value + Event is the original select event if there is one. + Event should not be passed if triggered manually. + */ + var self = this, + input = this.element; + $(input).removeClass('ui-state-error'); + if (item) { + if (self.allowMultiple) { + $(input).val(""); + $(input).data("autocomplete").term = ""; + if ($(self.hiddenMultipleSelector + '[value="' + item.id + '"]').length === 0) { + var newInput = $('', { + 'type': 'hidden', + 'name': self.hiddenName, + 'value': item.id, + 'title': item.value, + 'data-selectable-type': 'hidden-multiple' + }); + $(input).after(newInput); + self._addDeckItem(newInput); + } + return false; + } else { + $(input).val(item.value); + var ui = {item: item}; + if (typeof(event) === 'undefined' || event.type !== "autocompleteselect") { + $(input).trigger('autocompleteselect', [ui ]); + } + } + } + }, + + _create: function () { + /* Initialize a new selectable widget */ + var self = this, + input = this.element, + data = $(input).data(); + self.allowNew = data.selectableAllowNew || data['selectable-allow-new']; + self.allowMultiple = data.selectableMultiple || data['selectable-multiple']; + self.textName = $(input).attr('name'); + self.hiddenName = self.textName.replace('_0', '_1'); + self.hiddenSelector = ':input[data-selectable-type=hidden][name=' + self.hiddenName + ']'; + self.hiddenMultipleSelector = ':input[data-selectable-type=hidden-multiple][name=' + self.hiddenName + ']'; + if (self.allowMultiple) { + self.allowNew = false; + $(input).val(""); + this._initDeck(); + } + + function dataSource(request, response) { + /* Custom data source to uses the lookup url with pagination + Adds hook for adjusting query parameters. + Includes timestamp to prevent browser caching the lookup. */ + var url = data.selectableUrl || data['selectable-url']; + var now = new Date().getTime(); + var query = {term: request.term, timestamp: now}; + if (self.options.prepareQuery) { + self.options.prepareQuery(query); + } + var page = $(input).data("page"); + if (page) { + query.page = page; + } + function unwrapResponse(data) { + var results = data.data; + var meta = data.meta; + if (meta.next_page && meta.more) { + results.push({ + id: '', + value: '', + label: meta.more, + page: meta.next_page + }); + } + return response(results); + } + $.getJSON(url, query, unwrapResponse); + } + // Create base auto-complete lookup + $(input).autocomplete({ + source: dataSource, + change: function (event, ui) { + $(input).removeClass('ui-state-error'); + if ($(input).val() && !ui.item) { + if (!self.allowNew) { + $(input).addClass('ui-state-error'); + } + } + if (self.allowMultiple && !$(input).hasClass('ui-state-error')) { + $(input).val(""); + $(input).data("autocomplete").term = ""; + } + }, + select: function (event, ui) { + $(input).removeClass('ui-state-error'); + if (ui.item && ui.item.page) { + // Set current page value + $(input).data("page", ui.item.page); + $('.selectable-paginator', self.menu).remove(); + // Search for next page of results + $(input).autocomplete("search"); + return false; + } + return self.select(ui.item, event); + } + }).addClass("ui-widget ui-widget-content ui-corner-all"); + // Override the default auto-complete render. + $(input).data("autocomplete")._renderItem = function (ul, item) { + /* Adds hook for additional formatting, allows HTML in the label, + highlights term matches and handles pagination. */ + var label = item.label; + if (self.options.formatLabel) { + label = self.options.formatLabel(label, item); + } + if (self.options.highlightMatch && this.term) { + var re = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + + $.ui.autocomplete.escapeRegex(this.term) + + ")(?![^<>]*>)(?![^&;]+;)", "gi"); + label = label.replace(re, "$1"); + } + var li = $("
  • ") + .data("item.autocomplete", item) + .append($("").append(label)) + .appendTo(ul); + if (item.page) { + li.addClass('selectable-paginator'); + } + return li; + }; + // Override the default auto-complete suggest. + $(input).data("autocomplete")._suggest = function (items) { + /* Needed for handling pagination links */ + var page = $(input).data('page'); + var ul = this.menu.element; + if (!page) { + ul.empty(); + } + $(input).data('page', null); + ul.zIndex(this.element.zIndex() + 1); + this._renderMenu(ul, items); + // jQuery UI menu does not define deactivate + if (this.menu.deactivate) this.menu.deactivate(); + this.menu.refresh(); + // size and position menu + ul.show(); + this._resizeMenu(); + ul.position($.extend({of: this.element}, this.options.position)); + if (this.options.autoFocus) { + this.menu.next(new $.Event("mouseover")); + } + }; + // Additional work for combobox widgets + var selectableType = data.selectableType || data['selectable-type']; + if (selectableType === 'combobox') { + // Change auto-complete options + $(input).autocomplete("option", { + delay: 0, + minLength: 0 + }) + .removeClass("ui-corner-all") + .addClass("ui-corner-left ui-combo-input"); + // Add show all items button + $("").attr("tabIndex", -1).attr("title", "Show All Items") + .insertAfter($(input)) + .button({ + icons: { + primary: self.options.comboboxIcon + }, + text: false + }) + .removeClass("ui-corner-all") + .addClass("ui-corner-right ui-button-icon ui-combo-button") + .click(function () { + // close if already visible + if ($(input).autocomplete("widget").is(":visible")) { + $(input).autocomplete("close"); + return false; + } + // pass empty string as value to search for, displaying all results + $(input).autocomplete("search", ""); + $(input).focus(); + return false; + }); + } + } + }); + + window.bindSelectables = function (context) { + /* Bind all selectable widgets in a given context. + Automatically called on document.ready. + Additional calls can be made for dynamically added widgets. + */ + $(":input[data-selectable-type=text]", context).djselectable(); + $(":input[data-selectable-type=combobox]", context).djselectable(); + $(":input[data-selectable-type=hidden]", context).each(function (i, elem) { + var hiddenName = $(elem).attr('name'); + var textName = hiddenName.replace('_1', '_0'); + $(":input[name=" + textName + "][data-selectable-url]").bind( + "autocompletechange autocompleteselect", + function (event, ui) { + if (ui.item && ui.item.id) { + $(elem).val(ui.item.id); + } else { + $(elem).val(""); + } + } + ); + }); + }; + + /* Monkey-patch Django's dynamic formset, if defined */ + if (typeof(django) !== "undefined" && typeof(django.jQuery) !== "undefined") { + if (django.jQuery.fn.formset) { + var oldformset = django.jQuery.fn.formset; + django.jQuery.fn.formset = function (opts) { + var options = $.extend({}, opts); + var addedevent = function (row) { + bindSelectables($(row)); + }; + var added = null; + if (options.added) { + // Wrap previous added function and include call to bindSelectables + var oldadded = options.added; + added = function (row) { oldadded(row); addedevent(row); }; + } + options.added = added || addedevent; + return oldformset.call(this, options); + }; + } + } + + /* Monkey-patch Django's dismissAddAnotherPopup(), if defined */ + if (typeof(dismissAddAnotherPopup) !== "undefined" && + typeof(windowname_to_id) !== "undefined" && + typeof(html_unescape) !== "undefined") { + var django_dismissAddAnotherPopup = dismissAddAnotherPopup; + dismissAddAnotherPopup = function (win, newId, newRepr) { + /* See if the popup came from a selectable field. + If not, pass control to Django's code. + If so, handle it. */ + var fieldName = windowname_to_id(win.name); /* e.g. "id_fieldname" */ + var field = $('#' + fieldName); + var multiField = $('#' + fieldName + '_0'); + /* Check for bound selectable */ + var singleWidget = field.data('djselectable'); + var multiWidget = multiField.data('djselectable'); + if (singleWidget || multiWidget) { + // newId and newRepr are expected to have previously been escaped by + // django.utils.html.escape. + var item = { + id: html_unescape(newId), + value: html_unescape(newRepr) + }; + if (singleWidget) { + field.djselectable('select', item); + } + if (multiWidget) { + multiField.djselectable('select', item); + } + win.close(); + } else { + /* Not ours, pass on to original function. */ + return django_dismissAddAnotherPopup(win, newId, newRepr); + } + }; + } + + $(document).ready(function () { + // Bind existing widgets on document ready + if (typeof(djselectableAutoLoad) === "undefined" || djselectableAutoLoad) { + bindSelectables('body'); + } + }); +})(jQuery); diff --git a/neoexchange/ingest/templates/ingest/base.html b/neoexchange/ingest/templates/ingest/base.html index 8264f4e00..887cbcebf 100644 --- a/neoexchange/ingest/templates/ingest/base.html +++ b/neoexchange/ingest/templates/ingest/base.html @@ -11,7 +11,7 @@ {% block meta %}{% endblock %} {% block favicon %} - + {% endblock %} {% block css-content %}{% endblock %} @@ -22,7 +22,7 @@ - + {% block script-content %}{% endblock %} From 53919ce296d910345a817b2996e3673c300826a4 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 23:13:08 -0700 Subject: [PATCH 06/48] Fix functional tests following re-styling/new form breakages: ingest/templates/ingest/ephem.html: Add Schedule and return buttons to bottom of output (needs proper CSS, super ugly) ingest/templates/ingest/home.html: Add id field back into target input field. neox/tests/base.py: Move the creation of a test object into the base setup as without at least 1 object the home page won't even render (due to use of "Body.objects.latest('ingest')" in views.home) --- neoexchange/ingest/forms.py | 2 +- .../ingest/templates/ingest/ephem.html | 6 ++ neoexchange/ingest/templates/ingest/home.html | 4 +- neoexchange/neox/tests/base.py | 26 ++++++++ .../neox/tests/test_ephemeris_creation.py | 66 ++++++++++--------- .../neox/tests/test_ephemeris_validation.py | 2 +- .../neox/tests/test_layout_and_styling.py | 6 +- 7 files changed, 75 insertions(+), 37 deletions(-) diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index d0377de80..dda7366d7 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -19,4 +19,4 @@ def clean_target(self): elif body.count() == 0: raise forms.ValidationError("Object not found.") elif body.count() > 1: - raise forms.ValidationError("Multiple objects found.") \ No newline at end of file + raise forms.ValidationError("Multiple objects found.") diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index 562a7a1fd..2aeb29612 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -76,4 +76,10 @@

    Ephemeris for {{ new_target_name }} at {{ site_code }}

    + {% endblock %} diff --git a/neoexchange/ingest/templates/ingest/home.html b/neoexchange/ingest/templates/ingest/home.html index e4315878c..c41885384 100644 --- a/neoexchange/ingest/templates/ingest/home.html +++ b/neoexchange/ingest/templates/ingest/home.html @@ -17,7 +17,7 @@
    - +
    {{ form.utc_date }} @@ -81,4 +81,4 @@
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/neoexchange/neox/tests/base.py b/neoexchange/neox/tests/base.py index d716aa55e..247373cad 100644 --- a/neoexchange/neox/tests/base.py +++ b/neoexchange/neox/tests/base.py @@ -1,11 +1,32 @@ from django.test import LiveServerTestCase from selenium import webdriver +from ingest.models import Body class FunctionalTest(LiveServerTestCase): + + def insert_test_body(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body, created = Body.objects.get_or_create(**params) + def setUp(self): self.browser = webdriver.Firefox() self.browser.implicitly_wait(5) + self.insert_test_body() def tearDown(self): self.browser.refresh() @@ -25,3 +46,8 @@ def check_for_header_in_table(self, table_id, header_text): def get_item_input_box(self, element_id='id_target'): return self.browser.find_element_by_id(element_id) + + def get_item_input_box_and_clear(self, element_id='id_target'): + inputbox = self.browser.find_element_by_id(element_id) + inputbox.clear() + return inputbox diff --git a/neoexchange/neox/tests/test_ephemeris_creation.py b/neoexchange/neox/tests/test_ephemeris_creation.py index 7b651ab9f..cf81bd944 100644 --- a/neoexchange/neox/tests/test_ephemeris_creation.py +++ b/neoexchange/neox/tests/test_ephemeris_creation.py @@ -28,8 +28,6 @@ def insert_test_body(self): def test_can_compute_ephemeris(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -37,9 +35,9 @@ def test_can_compute_ephemeris(self): # He notices the page title has the name of the site and the header # mentions current targets - self.assertIn('NEOexchange', self.browser.title) - header_text = self.browser.find_element_by_tag_name('h1').text - self.assertIn('Current Targets', header_text) + self.assertIn('NEOx home | LCOGT', self.browser.title) + header_text = self.browser.find_element_by_class_name('masthead').text + self.assertIn('active targets', header_text) # He notices there are several targets that could be followed up self.check_for_header_in_table('id_neo_targets', @@ -53,13 +51,13 @@ def test_can_compute_ephemeris(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) inputbox.send_keys('N999r0q') - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box_and_clear('id_utc_date') datebox.send_keys('2015-04-21') # When he hits Enter, he is taken to a new page and now the page shows an ephemeris @@ -69,7 +67,8 @@ def test_can_compute_ephemeris(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for V37') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at V37', menu) self.check_for_header_in_table('id_ephemeris_table', 'Date/Time (UTC) RA Dec Mag "/min Alt Moon Phase Moon Dist. Moon Alt. Score H.A.' @@ -79,16 +78,16 @@ def test_can_compute_ephemeris(self): ) # There is a button asking whether to schedule the target + link = self.browser.find_element_by_link_text('No') # He clicks 'No' and is returned to the front page - self.assertIn('NEOexchange', self.browser.title) + link.click() + self.assertIn('NEOx home | LCOGT', self.browser.title) # Satisfied, he goes back to sleep def test_can_compute_ephemeris_for_specific_site(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -98,7 +97,7 @@ def test_can_compute_ephemeris_for_specific_site(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -106,14 +105,19 @@ def test_can_compute_ephemeris_for_specific_site(self): # He notices a new selection for the site code and chooses FTN (F65) # XXX Code smell: Too many static text constants - site_choices = Select(self.browser.find_element_by_id('id_sitecode')) + site_choices = Select(self.browser.find_element_by_id('id_site_code')) self.assertIn('FTN (F65)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('FTN (F65)') - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') + datebox.clear() datebox.send_keys('2015-04-21') + altlimitbox = self.get_item_input_box('id_alt_limit') + altlimitbox.clear() + altlimitbox.send_keys('20') + # When he hits Enter, he is taken to a new page and now the page shows an ephemeris # for the target with a column header and a series of rows for the position # as a function of time. @@ -122,7 +126,8 @@ def test_can_compute_ephemeris_for_specific_site(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for F65') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at F65', menu) # Check the results for V37 are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') @@ -140,8 +145,6 @@ def test_can_compute_ephemeris_for_specific_site(self): def test_can_compute_ephemeris_for_specific_date(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -151,7 +154,7 @@ def test_can_compute_ephemeris_for_specific_date(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -159,22 +162,24 @@ def test_can_compute_ephemeris_for_specific_date(self): # He notices a new selection for the site code and chooses ELP (V37) # XXX Code smell: Too many static text constants - site_choices = Select(self.get_item_input_box('id_sitecode')) + site_choices = Select(self.get_item_input_box('id_site_code')) self.assertIn('ELP (V37)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('ELP (V37)') # He notices a new textbox for the date that is wanted which is filled # in with the current date - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') current_date = datetime.utcnow().date() current_date_str = current_date.strftime('%Y-%m-%d') self.assertEqual( - datebox.get_attribute('placeholder'), + datebox.get_attribute('value'), current_date_str ) # He decides to see where it will be on a specific date in a future + # so clears the box and put his new date in + datebox.clear() datebox.send_keys('2015-04-28') # When he hits Enter, he is taken to a new page and now the page shows an ephemeris @@ -185,7 +190,8 @@ def test_can_compute_ephemeris_for_specific_date(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for V37') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at V37', menu) # Check the results for default date are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') @@ -203,8 +209,6 @@ def test_can_compute_ephemeris_for_specific_date(self): def test_can_compute_ephemeris_for_specific_alt_limit(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -214,7 +218,7 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -222,28 +226,29 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): # He notices a new selection for the site code and chooses CPT (K91) # XXX Code smell: Too many static text constants - site_choices = Select(self.get_item_input_box('id_sitecode')) + site_choices = Select(self.get_item_input_box('id_site_code')) self.assertIn('CPT (K91-93)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('CPT (K91-93)') # He notices a new textbox for the date that is wanted which is filled # in with the current date - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') current_date = datetime.utcnow().date() current_date_str = current_date.strftime('%Y-%m-%d') self.assertEqual( - datebox.get_attribute('placeholder'), + datebox.get_attribute('value'), current_date_str ) # He decides to see where it will be on a specific date in a future + datebox.clear() datebox.send_keys('2015-09-04') # He notices a new textbox for the altitude limit that is wanted, below # which he doesn't want to see ephemeris output. It is filled in with # the default value of 30.0 degrees - datebox = self.get_item_input_box('id_altlimit') + datebox = self.get_item_input_box('id_alt_limit') self.assertEqual(datebox.get_attribute('value'), str(30.0)) @@ -255,7 +260,8 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for K92') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at K92', menu) # Check the results for default date are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') diff --git a/neoexchange/neox/tests/test_ephemeris_validation.py b/neoexchange/neox/tests/test_ephemeris_validation.py index 425600925..eb38fb00d 100644 --- a/neoexchange/neox/tests/test_ephemeris_validation.py +++ b/neoexchange/neox/tests/test_ephemeris_validation.py @@ -15,4 +15,4 @@ def test_cannot_get_ephem_for_bad_objects(self): # The page refreshes and there is an error message saying that targets' # can't be blank error = self.browser.find_element_by_css_selector('.error') - self.assertEqual(error.text, "You didn't specify a target") + self.assertEqual(error.text, "Target name is required") diff --git a/neoexchange/neox/tests/test_layout_and_styling.py b/neoexchange/neox/tests/test_layout_and_styling.py index d114f79b7..516cd63e0 100644 --- a/neoexchange/neox/tests/test_layout_and_styling.py +++ b/neoexchange/neox/tests/test_layout_and_styling.py @@ -9,8 +9,8 @@ def test_layout_and_styling(self): self.browser.set_window_size(1280, 1024) # He notices the input box is nicely centered - inputbox = self.get_item_input_box('id_date') + link = self.browser.find_element_by_partial_link_text('active targets') self.assertAlmostEqual( - inputbox.location['x'] + inputbox.size['width'] / 2, - 640, delta=7 + link.location['x'] + link.size['width'] , + 640, delta=27 ) From 903ef5553f90fd97d72b900124ea086cadba48ed Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 16:51:16 -0700 Subject: [PATCH 07/48] Fix use of debug logger. --- neoexchange/ingest/ephem_subs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/ingest/ephem_subs.py index 9965c3695..492b0305d 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/ingest/ephem_subs.py @@ -508,7 +508,7 @@ def determine_darkness_times(site_code, utc_date=datetime.utcnow(), debug=False) utc_date = datetime.combine(utc_date, time()) (start_of_darkness, end_of_darkness) = astro_darkness(site_code, utc_date) end_of_darkness = end_of_darkness+timedelta(hours=1) - logger.debug("Start,End of darkness=", start_of_darkness, end_of_darkness) + logger.debug("Start,End of darkness=%s %s", start_of_darkness, end_of_darkness) if utc_date > end_of_darkness: logger.debug("Planning for the next night") utc_date = utc_date + timedelta(days=1) @@ -517,10 +517,10 @@ def determine_darkness_times(site_code, utc_date=datetime.utcnow(), debug=False) utc_date = utc_date + timedelta(days=-1) utc_date = utc_date.replace(hour=0, minute=0, second=0, microsecond=0) - logger.debug("Planning observations for", utc_date, "for", site_code) + logger.debug("Planning observations for %s for %s", utc_date, site_code) # Get hours of darkness for site (dark_start, dark_end) = astro_darkness(site_code, utc_date) - logger.debug("Dark from ", dark_start, "to", dark_end) + logger.debug("Dark from %s to %s", dark_start, dark_end) return dark_start, dark_end From 311abba0a56318acd5363d9f3295a8a9b223217a Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 16:54:54 -0700 Subject: [PATCH 08/48] Fix broken tests for Edwards' use of new Form and incorrect spelling... --- .../ingest/templates/ingest/ephem.html | 2 +- neoexchange/ingest/tests/test_views.py | 87 ++++++++++++------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index f3c95717b..562a7a1fd 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -9,7 +9,7 @@ {% block extramenu %}
    -

    Emphemeris for {{ new_target_name }} at {{ site_code }}

    +

    Ephemeris for {{ new_target_name }} at {{ site_code }}

    {% endblock%} diff --git a/neoexchange/ingest/tests/test_views.py b/neoexchange/ingest/tests/test_views.py index 43caf867f..c7c987660 100644 --- a/neoexchange/ingest/tests/test_views.py +++ b/neoexchange/ingest/tests/test_views.py @@ -27,6 +27,7 @@ from ingest.ephem_subs import call_compute_ephem, determine_darkness_times from ingest.views import home, clean_NEOCP_object from ingest.models import Body +from ingest.forms import EphemQuery class TestClean_NEOCP_Object(TestCase): @@ -82,28 +83,33 @@ def test_missing_absmag(self): class HomePageTest(TestCase): - def test_root_url_resolves_to_home_page_view(self): - found = resolve('/') - self.assertEqual(found.func, home) - - def test_home_page_returns_correct_html(self): - request = HttpRequest() - response = home(request) - expected_html = render_to_string('ingest/home.html') - self.assertEqual(response.content.decode(), expected_html) - - def test_home_page_redirects_after_GET(self): - request = HttpRequest() - request.method = 'GET' - request.GET['target_name'] = 'New target' + def setUp(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body = Body.objects.create(**params) + self.body.save() - response = home(request) + def test_home_page_renders_home_template(self): + response = self.client.get('/') + self.assertTemplateUsed(response, 'ingest/home.html') - self.assertEqual(response.status_code, 302) - self.assertEqual(response['location'], '/ephemeris/') + def test_home_page_uses_ephemquery_form(self): + response = self.client.get('/') + self.assertIsInstance(response.context['form'], EphemQuery) - def test_home_page_ephem_form_shows_current_date(self): - pass class EphemPageTest(TestCase): maxDiff = None @@ -134,41 +140,58 @@ def test_home_page_can_save_a_GET_request(self): dark_start, dark_end = determine_darkness_times(site_code, utc_date ) response = self.client.get('/ephemeris/', - data={'target_name' : 'N999r0q', - 'site_code' : site_code, - 'utc_date' : '2015-04-21'} + data={'target' : 'N999r0q', + 'site_code' : site_code, + 'utc_date' : '2015-04-21', + 'alt_limit' : 0} ) self.assertIn('N999r0q', response.content.decode()) body_elements = model_to_dict(self.body) ephem_lines = call_compute_ephem(body_elements, dark_start, dark_end, site_code, '5m' ) expected_html = render_to_string( 'ingest/ephem.html', - {'new_target_name' : 'N999r0q', + {'new_target_name' : 'N999r0q', 'ephem_lines' : ephem_lines, 'site_code' : site_code } ) self.assertMultiLineEqual(response.content.decode(), expected_html) def test_displays_ephem(self): - response = self.client.get('/ephemeris/', data={'target_name' : 'N999r0q'}) - self.assertContains(response, 'Computing ephemeris for') + response = self.client.get('/ephemeris/', + data ={'target' : 'N999r0q', + 'utc_date' : '2015-05-11', + 'site_code' : 'V37', + 'alt_limit' : 30.0 + } + ) + self.assertContains(response, 'Ephemeris for') def test_uses_ephem_template(self): - response = self.client.get('/ephemeris/', data={'target_name' : 'N999r0q'}) + response = self.client.get('/ephemeris/', + data = {'target' : 'N999r0q', + 'site_code' : 'W86', + 'utc_date' : '2015-04-20', + 'alt_limit' : 40.0 + } + ) self.assertTemplateUsed(response, 'ingest/ephem.html') def test_form_errors_are_sent_back_to_home_page(self): - response = self.client.get('/ephemeris/', data={'target_name' : ''}) + response = self.client.get('/ephemeris/', data={'target' : ''}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'ingest/home.html') - expected_error = escape("You didn't specify a target") + expected_error = escape("Target name is required") self.assertContains(response, expected_error) def test_ephem_page_displays_site_code(self): - response = self.client.get('/ephemeris/', - data={'target_name' : 'N999r0q', 'site_code' : 'F65'}) - self.assertContains(response, - 'Computing ephemeris for: N999r0q for F65') + response = self.client.get('/ephemeris/', + data = {'target' : 'N999r0q', + 'site_code' : 'F65', + 'utc_date' : '2015-04-20', + 'alt_limit' : 30.0 + } + ) + self.assertContains(response, 'Ephemeris for N999r0q at F65') class TargetsPageTest(TestCase): From d7dbbe5982f3612fb6cd599ffa3b3848a564ed31 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 18:05:15 -0700 Subject: [PATCH 09/48] Add unit tests for EphemQuery form --- neoexchange/ingest/tests/__init__.py | 1 + neoexchange/ingest/tests/test_forms.py | 68 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 neoexchange/ingest/tests/test_forms.py diff --git a/neoexchange/ingest/tests/__init__.py b/neoexchange/ingest/tests/__init__.py index d8090a0f9..5093a70d7 100644 --- a/neoexchange/ingest/tests/__init__.py +++ b/neoexchange/ingest/tests/__init__.py @@ -1,4 +1,5 @@ from test_ast_subs import * from test_ephem_subs import * +from test_forms import * from test_source_subs import * from test_views import * diff --git a/neoexchange/ingest/tests/test_forms.py b/neoexchange/ingest/tests/test_forms.py new file mode 100644 index 000000000..4d8bbd0fa --- /dev/null +++ b/neoexchange/ingest/tests/test_forms.py @@ -0,0 +1,68 @@ +''' +NEO exchange: NEO observing portal for Las Cumbres Observatory Global Telescope Network +Copyright (C) 2014-2015 LCOGT + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +''' + +from django.test import TestCase +from ingest.models import Body + +#Import module to test +from ingest.forms import EphemQuery + +class EphemQueryFormTest(TestCase): + + def setUp(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body = Body.objects.create(**params) + self.body.save() + + def test_form_has_label(self): + form = EphemQuery() + self.assertIn('Enter target name...', form.as_p()) + self.assertIn('Site code:', form.as_p()) + + def test_form_validation_for_blank_target(self): + form = EphemQuery(data = {'target' : ''}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['target'], + ['Target name is required'] + ) + + def test_form_validation_for_blank_date(self): + form = EphemQuery(data = {'utc_date' : ''}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['utc_date'], + ['UTC date is required'] + ) + + def test_form_handles_save(self): + form = EphemQuery(data = {'target' : 'N999r0q', + 'utc_date' : '2015-04-20', + 'site_code' : 'K92', + 'alt_limit' : 30.0 + }) + self.assertTrue(form.is_valid()) From b67a40a95af5b685debe572def858e4a838831f4 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 23:11:53 -0700 Subject: [PATCH 10/48] Add missing icon and jquery.dj.selectable.js file which were causing the Django TestServer to die horribly with 500 errors --- .../ingest/static/ingest/images/favicon.ico | Bin 0 -> 2238 bytes .../js/jquery.dj.selectable.js?v=0.8.0 | 352 ++++++++++++++++++ neoexchange/ingest/templates/ingest/base.html | 4 +- 3 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 neoexchange/ingest/static/ingest/images/favicon.ico create mode 100644 neoexchange/ingest/static/selectable/js/jquery.dj.selectable.js?v=0.8.0 diff --git a/neoexchange/ingest/static/ingest/images/favicon.ico b/neoexchange/ingest/static/ingest/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7a851a5e74ececf101594f6863276cc6896eb58b GIT binary patch literal 2238 zcmeHHYgklO6kR7T31*Z+s6d6$11gd1p=AY@f|8lq_`tx5Fdw9VMNyejsR-r>NI{_@ zaRI?bUYDz%jtK%o2*EcRf|#J7DMB;|0-?=m-vN{~|Mk_M*1h{Y_S$=$bM`&o1sFXJ z4lrS-IbcI0Co%^zcQOWY62b&icSFND2X}J=v_|6UG3Wa^h>B{4F)K3DmAXoF2L-{=u@wCHajeTNz|y5H$jNEP$&;6H+SV5K*{6}4n~Q2gHCE~k z;QRK$Cd~%5v{pn!Hn_JpTn=-L9eWnqR@%e*_1J%8Ka6x3JG)=7q<9HhTU$|9){WXu zjtjA|NKV#bbv?(3Vvf4HGvKbS;H9NVOH0F#wYA`umQbmhaPZ(k@N?&2vBLs>em^56 zr5y%mXDqL!bL`%YkdR-|*wKhKV;d$P?gS@o?&SsE(E;CTjzbj{xV&l=nwzhHFJ6q{ z!%ySVr9ZH-ful1u6%#9r(CKs-KmHts9^x=JKLsD33#h57fnKjiXGas*Hio+9I<%?P zs4DvbNl8h_Y2lQo1)nehEnPh*ZsX{nH7~}+Vf5%m?4ufSYil?=Q(b4y1h1?_U|VO3)OT?-{?>?YqY-9i_28bKsH&=hwP+2mBOHfy#aK|qVIYkPs^NhH2arhjXz$*= z$fG@_q@*A#D+?9{9QqPH_@qg&EaI3wy9vCh355j(a4q9NEMRCgAcE?xE7F16+r!1> z4BFeq~fSASDo<~C1Iz^vtnoo@~xC&AIoJ3eFg_3(z>I5Ppuab;K zf3-)Frup3>NshGssPGu#oT9Gj2CEsZsGQd ze}KLw%}$c;czLlTO}6|r=bd@;VqzXj)AVOkpB^+=IsfNNSfY8j31$Q28CyUx?|Uz& zHrCzUebapRRdW?rFQOJMTsYC@?yzO|+!+w?_M@WsTI}!(6JKh36T2HNb+YWqG=qO_5zp^i|$DKp').addClass('ui-widget selectable-deck selectable-deck-' + style); + if (style === 'bottom' || style === 'bottom-inline') { + $(this.element).after(this.deck); + } else { + $(this.element).before(this.deck); + } + $(self.hiddenMultipleSelector).each(function (i, input) { + self._addDeckItem(input); + }); + }, + + _addDeckItem: function (input) { + /* Add new deck list item from a given hidden input */ + var self = this; + var li = $('
  • ') + .text($(input).attr('title')) + .addClass('selectable-deck-item'); + var item = {element: self.element, input: input, wrapper: li, deck: self.deck}; + if (self._trigger("add", null, item) === false) { + input.remove(); + } else { + var button = $('
    ') + .addClass('selectable-deck-remove') + .append( + $('') + .attr('href', '#') + .button({ + icons: { + primary: self.options.removeIcon + }, + text: false + }) + .click(function (e) { + e.preventDefault(); + if (self._trigger("remove", e, item) !== false) { + $(input).remove(); + li.remove(); + } + }) + ); + li.append(button).appendTo(this.deck); + } + }, + + select: function (item, event) { + /* Trigger selection of a given item. + Item should contain two properties: id and value + Event is the original select event if there is one. + Event should not be passed if triggered manually. + */ + var self = this, + input = this.element; + $(input).removeClass('ui-state-error'); + if (item) { + if (self.allowMultiple) { + $(input).val(""); + $(input).data("autocomplete").term = ""; + if ($(self.hiddenMultipleSelector + '[value="' + item.id + '"]').length === 0) { + var newInput = $('', { + 'type': 'hidden', + 'name': self.hiddenName, + 'value': item.id, + 'title': item.value, + 'data-selectable-type': 'hidden-multiple' + }); + $(input).after(newInput); + self._addDeckItem(newInput); + } + return false; + } else { + $(input).val(item.value); + var ui = {item: item}; + if (typeof(event) === 'undefined' || event.type !== "autocompleteselect") { + $(input).trigger('autocompleteselect', [ui ]); + } + } + } + }, + + _create: function () { + /* Initialize a new selectable widget */ + var self = this, + input = this.element, + data = $(input).data(); + self.allowNew = data.selectableAllowNew || data['selectable-allow-new']; + self.allowMultiple = data.selectableMultiple || data['selectable-multiple']; + self.textName = $(input).attr('name'); + self.hiddenName = self.textName.replace('_0', '_1'); + self.hiddenSelector = ':input[data-selectable-type=hidden][name=' + self.hiddenName + ']'; + self.hiddenMultipleSelector = ':input[data-selectable-type=hidden-multiple][name=' + self.hiddenName + ']'; + if (self.allowMultiple) { + self.allowNew = false; + $(input).val(""); + this._initDeck(); + } + + function dataSource(request, response) { + /* Custom data source to uses the lookup url with pagination + Adds hook for adjusting query parameters. + Includes timestamp to prevent browser caching the lookup. */ + var url = data.selectableUrl || data['selectable-url']; + var now = new Date().getTime(); + var query = {term: request.term, timestamp: now}; + if (self.options.prepareQuery) { + self.options.prepareQuery(query); + } + var page = $(input).data("page"); + if (page) { + query.page = page; + } + function unwrapResponse(data) { + var results = data.data; + var meta = data.meta; + if (meta.next_page && meta.more) { + results.push({ + id: '', + value: '', + label: meta.more, + page: meta.next_page + }); + } + return response(results); + } + $.getJSON(url, query, unwrapResponse); + } + // Create base auto-complete lookup + $(input).autocomplete({ + source: dataSource, + change: function (event, ui) { + $(input).removeClass('ui-state-error'); + if ($(input).val() && !ui.item) { + if (!self.allowNew) { + $(input).addClass('ui-state-error'); + } + } + if (self.allowMultiple && !$(input).hasClass('ui-state-error')) { + $(input).val(""); + $(input).data("autocomplete").term = ""; + } + }, + select: function (event, ui) { + $(input).removeClass('ui-state-error'); + if (ui.item && ui.item.page) { + // Set current page value + $(input).data("page", ui.item.page); + $('.selectable-paginator', self.menu).remove(); + // Search for next page of results + $(input).autocomplete("search"); + return false; + } + return self.select(ui.item, event); + } + }).addClass("ui-widget ui-widget-content ui-corner-all"); + // Override the default auto-complete render. + $(input).data("autocomplete")._renderItem = function (ul, item) { + /* Adds hook for additional formatting, allows HTML in the label, + highlights term matches and handles pagination. */ + var label = item.label; + if (self.options.formatLabel) { + label = self.options.formatLabel(label, item); + } + if (self.options.highlightMatch && this.term) { + var re = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + + $.ui.autocomplete.escapeRegex(this.term) + + ")(?![^<>]*>)(?![^&;]+;)", "gi"); + label = label.replace(re, "$1"); + } + var li = $("
  • ") + .data("item.autocomplete", item) + .append($("").append(label)) + .appendTo(ul); + if (item.page) { + li.addClass('selectable-paginator'); + } + return li; + }; + // Override the default auto-complete suggest. + $(input).data("autocomplete")._suggest = function (items) { + /* Needed for handling pagination links */ + var page = $(input).data('page'); + var ul = this.menu.element; + if (!page) { + ul.empty(); + } + $(input).data('page', null); + ul.zIndex(this.element.zIndex() + 1); + this._renderMenu(ul, items); + // jQuery UI menu does not define deactivate + if (this.menu.deactivate) this.menu.deactivate(); + this.menu.refresh(); + // size and position menu + ul.show(); + this._resizeMenu(); + ul.position($.extend({of: this.element}, this.options.position)); + if (this.options.autoFocus) { + this.menu.next(new $.Event("mouseover")); + } + }; + // Additional work for combobox widgets + var selectableType = data.selectableType || data['selectable-type']; + if (selectableType === 'combobox') { + // Change auto-complete options + $(input).autocomplete("option", { + delay: 0, + minLength: 0 + }) + .removeClass("ui-corner-all") + .addClass("ui-corner-left ui-combo-input"); + // Add show all items button + $("").attr("tabIndex", -1).attr("title", "Show All Items") + .insertAfter($(input)) + .button({ + icons: { + primary: self.options.comboboxIcon + }, + text: false + }) + .removeClass("ui-corner-all") + .addClass("ui-corner-right ui-button-icon ui-combo-button") + .click(function () { + // close if already visible + if ($(input).autocomplete("widget").is(":visible")) { + $(input).autocomplete("close"); + return false; + } + // pass empty string as value to search for, displaying all results + $(input).autocomplete("search", ""); + $(input).focus(); + return false; + }); + } + } + }); + + window.bindSelectables = function (context) { + /* Bind all selectable widgets in a given context. + Automatically called on document.ready. + Additional calls can be made for dynamically added widgets. + */ + $(":input[data-selectable-type=text]", context).djselectable(); + $(":input[data-selectable-type=combobox]", context).djselectable(); + $(":input[data-selectable-type=hidden]", context).each(function (i, elem) { + var hiddenName = $(elem).attr('name'); + var textName = hiddenName.replace('_1', '_0'); + $(":input[name=" + textName + "][data-selectable-url]").bind( + "autocompletechange autocompleteselect", + function (event, ui) { + if (ui.item && ui.item.id) { + $(elem).val(ui.item.id); + } else { + $(elem).val(""); + } + } + ); + }); + }; + + /* Monkey-patch Django's dynamic formset, if defined */ + if (typeof(django) !== "undefined" && typeof(django.jQuery) !== "undefined") { + if (django.jQuery.fn.formset) { + var oldformset = django.jQuery.fn.formset; + django.jQuery.fn.formset = function (opts) { + var options = $.extend({}, opts); + var addedevent = function (row) { + bindSelectables($(row)); + }; + var added = null; + if (options.added) { + // Wrap previous added function and include call to bindSelectables + var oldadded = options.added; + added = function (row) { oldadded(row); addedevent(row); }; + } + options.added = added || addedevent; + return oldformset.call(this, options); + }; + } + } + + /* Monkey-patch Django's dismissAddAnotherPopup(), if defined */ + if (typeof(dismissAddAnotherPopup) !== "undefined" && + typeof(windowname_to_id) !== "undefined" && + typeof(html_unescape) !== "undefined") { + var django_dismissAddAnotherPopup = dismissAddAnotherPopup; + dismissAddAnotherPopup = function (win, newId, newRepr) { + /* See if the popup came from a selectable field. + If not, pass control to Django's code. + If so, handle it. */ + var fieldName = windowname_to_id(win.name); /* e.g. "id_fieldname" */ + var field = $('#' + fieldName); + var multiField = $('#' + fieldName + '_0'); + /* Check for bound selectable */ + var singleWidget = field.data('djselectable'); + var multiWidget = multiField.data('djselectable'); + if (singleWidget || multiWidget) { + // newId and newRepr are expected to have previously been escaped by + // django.utils.html.escape. + var item = { + id: html_unescape(newId), + value: html_unescape(newRepr) + }; + if (singleWidget) { + field.djselectable('select', item); + } + if (multiWidget) { + multiField.djselectable('select', item); + } + win.close(); + } else { + /* Not ours, pass on to original function. */ + return django_dismissAddAnotherPopup(win, newId, newRepr); + } + }; + } + + $(document).ready(function () { + // Bind existing widgets on document ready + if (typeof(djselectableAutoLoad) === "undefined" || djselectableAutoLoad) { + bindSelectables('body'); + } + }); +})(jQuery); diff --git a/neoexchange/ingest/templates/ingest/base.html b/neoexchange/ingest/templates/ingest/base.html index 8264f4e00..887cbcebf 100644 --- a/neoexchange/ingest/templates/ingest/base.html +++ b/neoexchange/ingest/templates/ingest/base.html @@ -11,7 +11,7 @@ {% block meta %}{% endblock %} {% block favicon %} - + {% endblock %} {% block css-content %}{% endblock %} @@ -22,7 +22,7 @@ - + {% block script-content %}{% endblock %} From dab1848df50a4560d18f2f98ba00b9a258d13a90 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Sun, 3 May 2015 23:13:08 -0700 Subject: [PATCH 11/48] Fix functional tests following re-styling/new form breakages: ingest/templates/ingest/ephem.html: Add Schedule and return buttons to bottom of output (needs proper CSS, super ugly) ingest/templates/ingest/home.html: Add id field back into target input field. neox/tests/base.py: Move the creation of a test object into the base setup as without at least 1 object the home page won't even render (due to use of "Body.objects.latest('ingest')" in views.home) --- neoexchange/ingest/forms.py | 2 +- .../ingest/templates/ingest/ephem.html | 6 ++ neoexchange/ingest/templates/ingest/home.html | 4 +- neoexchange/neox/tests/base.py | 26 ++++++++ .../neox/tests/test_ephemeris_creation.py | 66 ++++++++++--------- .../neox/tests/test_ephemeris_validation.py | 2 +- .../neox/tests/test_layout_and_styling.py | 6 +- 7 files changed, 75 insertions(+), 37 deletions(-) diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index d0377de80..dda7366d7 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -19,4 +19,4 @@ def clean_target(self): elif body.count() == 0: raise forms.ValidationError("Object not found.") elif body.count() > 1: - raise forms.ValidationError("Multiple objects found.") \ No newline at end of file + raise forms.ValidationError("Multiple objects found.") diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index 562a7a1fd..2aeb29612 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -76,4 +76,10 @@

    Ephemeris for {{ new_target_name }} at {{ site_code }}

    + {% endblock %} diff --git a/neoexchange/ingest/templates/ingest/home.html b/neoexchange/ingest/templates/ingest/home.html index e4315878c..c41885384 100644 --- a/neoexchange/ingest/templates/ingest/home.html +++ b/neoexchange/ingest/templates/ingest/home.html @@ -17,7 +17,7 @@
    - +
    {{ form.utc_date }} @@ -81,4 +81,4 @@
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/neoexchange/neox/tests/base.py b/neoexchange/neox/tests/base.py index d716aa55e..247373cad 100644 --- a/neoexchange/neox/tests/base.py +++ b/neoexchange/neox/tests/base.py @@ -1,11 +1,32 @@ from django.test import LiveServerTestCase from selenium import webdriver +from ingest.models import Body class FunctionalTest(LiveServerTestCase): + + def insert_test_body(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body, created = Body.objects.get_or_create(**params) + def setUp(self): self.browser = webdriver.Firefox() self.browser.implicitly_wait(5) + self.insert_test_body() def tearDown(self): self.browser.refresh() @@ -25,3 +46,8 @@ def check_for_header_in_table(self, table_id, header_text): def get_item_input_box(self, element_id='id_target'): return self.browser.find_element_by_id(element_id) + + def get_item_input_box_and_clear(self, element_id='id_target'): + inputbox = self.browser.find_element_by_id(element_id) + inputbox.clear() + return inputbox diff --git a/neoexchange/neox/tests/test_ephemeris_creation.py b/neoexchange/neox/tests/test_ephemeris_creation.py index 7b651ab9f..cf81bd944 100644 --- a/neoexchange/neox/tests/test_ephemeris_creation.py +++ b/neoexchange/neox/tests/test_ephemeris_creation.py @@ -28,8 +28,6 @@ def insert_test_body(self): def test_can_compute_ephemeris(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -37,9 +35,9 @@ def test_can_compute_ephemeris(self): # He notices the page title has the name of the site and the header # mentions current targets - self.assertIn('NEOexchange', self.browser.title) - header_text = self.browser.find_element_by_tag_name('h1').text - self.assertIn('Current Targets', header_text) + self.assertIn('NEOx home | LCOGT', self.browser.title) + header_text = self.browser.find_element_by_class_name('masthead').text + self.assertIn('active targets', header_text) # He notices there are several targets that could be followed up self.check_for_header_in_table('id_neo_targets', @@ -53,13 +51,13 @@ def test_can_compute_ephemeris(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) inputbox.send_keys('N999r0q') - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box_and_clear('id_utc_date') datebox.send_keys('2015-04-21') # When he hits Enter, he is taken to a new page and now the page shows an ephemeris @@ -69,7 +67,8 @@ def test_can_compute_ephemeris(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for V37') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at V37', menu) self.check_for_header_in_table('id_ephemeris_table', 'Date/Time (UTC) RA Dec Mag "/min Alt Moon Phase Moon Dist. Moon Alt. Score H.A.' @@ -79,16 +78,16 @@ def test_can_compute_ephemeris(self): ) # There is a button asking whether to schedule the target + link = self.browser.find_element_by_link_text('No') # He clicks 'No' and is returned to the front page - self.assertIn('NEOexchange', self.browser.title) + link.click() + self.assertIn('NEOx home | LCOGT', self.browser.title) # Satisfied, he goes back to sleep def test_can_compute_ephemeris_for_specific_site(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -98,7 +97,7 @@ def test_can_compute_ephemeris_for_specific_site(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -106,14 +105,19 @@ def test_can_compute_ephemeris_for_specific_site(self): # He notices a new selection for the site code and chooses FTN (F65) # XXX Code smell: Too many static text constants - site_choices = Select(self.browser.find_element_by_id('id_sitecode')) + site_choices = Select(self.browser.find_element_by_id('id_site_code')) self.assertIn('FTN (F65)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('FTN (F65)') - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') + datebox.clear() datebox.send_keys('2015-04-21') + altlimitbox = self.get_item_input_box('id_alt_limit') + altlimitbox.clear() + altlimitbox.send_keys('20') + # When he hits Enter, he is taken to a new page and now the page shows an ephemeris # for the target with a column header and a series of rows for the position # as a function of time. @@ -122,7 +126,8 @@ def test_can_compute_ephemeris_for_specific_site(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for F65') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at F65', menu) # Check the results for V37 are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') @@ -140,8 +145,6 @@ def test_can_compute_ephemeris_for_specific_site(self): def test_can_compute_ephemeris_for_specific_date(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -151,7 +154,7 @@ def test_can_compute_ephemeris_for_specific_date(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -159,22 +162,24 @@ def test_can_compute_ephemeris_for_specific_date(self): # He notices a new selection for the site code and chooses ELP (V37) # XXX Code smell: Too many static text constants - site_choices = Select(self.get_item_input_box('id_sitecode')) + site_choices = Select(self.get_item_input_box('id_site_code')) self.assertIn('ELP (V37)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('ELP (V37)') # He notices a new textbox for the date that is wanted which is filled # in with the current date - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') current_date = datetime.utcnow().date() current_date_str = current_date.strftime('%Y-%m-%d') self.assertEqual( - datebox.get_attribute('placeholder'), + datebox.get_attribute('value'), current_date_str ) # He decides to see where it will be on a specific date in a future + # so clears the box and put his new date in + datebox.clear() datebox.send_keys('2015-04-28') # When he hits Enter, he is taken to a new page and now the page shows an ephemeris @@ -185,7 +190,8 @@ def test_can_compute_ephemeris_for_specific_date(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for V37') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at V37', menu) # Check the results for default date are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') @@ -203,8 +209,6 @@ def test_can_compute_ephemeris_for_specific_date(self): def test_can_compute_ephemeris_for_specific_alt_limit(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -214,7 +218,7 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -222,28 +226,29 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): # He notices a new selection for the site code and chooses CPT (K91) # XXX Code smell: Too many static text constants - site_choices = Select(self.get_item_input_box('id_sitecode')) + site_choices = Select(self.get_item_input_box('id_site_code')) self.assertIn('CPT (K91-93)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('CPT (K91-93)') # He notices a new textbox for the date that is wanted which is filled # in with the current date - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') current_date = datetime.utcnow().date() current_date_str = current_date.strftime('%Y-%m-%d') self.assertEqual( - datebox.get_attribute('placeholder'), + datebox.get_attribute('value'), current_date_str ) # He decides to see where it will be on a specific date in a future + datebox.clear() datebox.send_keys('2015-09-04') # He notices a new textbox for the altitude limit that is wanted, below # which he doesn't want to see ephemeris output. It is filled in with # the default value of 30.0 degrees - datebox = self.get_item_input_box('id_altlimit') + datebox = self.get_item_input_box('id_alt_limit') self.assertEqual(datebox.get_attribute('value'), str(30.0)) @@ -255,7 +260,8 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for K92') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at K92', menu) # Check the results for default date are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') diff --git a/neoexchange/neox/tests/test_ephemeris_validation.py b/neoexchange/neox/tests/test_ephemeris_validation.py index 425600925..eb38fb00d 100644 --- a/neoexchange/neox/tests/test_ephemeris_validation.py +++ b/neoexchange/neox/tests/test_ephemeris_validation.py @@ -15,4 +15,4 @@ def test_cannot_get_ephem_for_bad_objects(self): # The page refreshes and there is an error message saying that targets' # can't be blank error = self.browser.find_element_by_css_selector('.error') - self.assertEqual(error.text, "You didn't specify a target") + self.assertEqual(error.text, "Target name is required") diff --git a/neoexchange/neox/tests/test_layout_and_styling.py b/neoexchange/neox/tests/test_layout_and_styling.py index d114f79b7..516cd63e0 100644 --- a/neoexchange/neox/tests/test_layout_and_styling.py +++ b/neoexchange/neox/tests/test_layout_and_styling.py @@ -9,8 +9,8 @@ def test_layout_and_styling(self): self.browser.set_window_size(1280, 1024) # He notices the input box is nicely centered - inputbox = self.get_item_input_box('id_date') + link = self.browser.find_element_by_partial_link_text('active targets') self.assertAlmostEqual( - inputbox.location['x'] + inputbox.size['width'] / 2, - 640, delta=7 + link.location['x'] + link.size['width'] , + 640, delta=27 ) From ab552f2ef88c58c089af5ad6e21a5baebaf61b39 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Mon, 4 May 2015 10:08:40 -0700 Subject: [PATCH 12/48] Update README with version number and local setup details. Fix LayoutAndStylingTest. --- README.md | 42 ++++++++++++++++++- .../neox/tests/test_layout_and_styling.py | 4 +- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 82b519e43..4733084a5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ NEO Exchange ============ -Portal for scheduling observations of NEOs using LCOGT (Version 0.1.0) +Portal for scheduling observations of NEOs using LCOGT (Version 0.1.1) Setup ----- @@ -16,4 +16,42 @@ or: `source /bin/activate.csh # for (t)csh-shells` `pip install -r pip-requirements.txt` - +Local Testing +------------- + +For local testing you will probably want to create a +`neoexchange/neox/local_settings.py` file to point at a local test database and +to switch on `DEBUG` for easier testing. An example file would look like: +``` +import sys, os + +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +BASE_DIR = os.path.dirname(CURRENT_PATH) + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'neox.db', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +DEBUG = True + +# Use a different database file when testing or exploring in the shell. +if 'test' in sys.argv or 'test_coverage' in sys.argv or 'shell' in sys.argv: + DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3' + DATABASES['default']['NAME'] = 'test.db' + DATABASES['default']['USER'] = '' + DATABASES['default']['PASSWORD'] = '' + +STATIC_ROOT = os.path.abspath(os.path.join(BASE_DIR, '../../static/')) +``` + +To prepare the local SQLite DB for use, you should follow these steps: +1. `cd neoexchange\neoexchange` +2. Run `python manage.py syncdb` +3. Migrate the apps with `python manage.py migrate` diff --git a/neoexchange/neox/tests/test_layout_and_styling.py b/neoexchange/neox/tests/test_layout_and_styling.py index 516cd63e0..6ebfd865d 100644 --- a/neoexchange/neox/tests/test_layout_and_styling.py +++ b/neoexchange/neox/tests/test_layout_and_styling.py @@ -11,6 +11,6 @@ def test_layout_and_styling(self): # He notices the input box is nicely centered link = self.browser.find_element_by_partial_link_text('active targets') self.assertAlmostEqual( - link.location['x'] + link.size['width'] , - 640, delta=27 + link.location['x'] + link.size['width'] /2 , + 640, delta=50 ) From 814e6c31ab5a84f27c3c7bbe315292b0cf6d0100 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Mon, 4 May 2015 15:50:18 -0700 Subject: [PATCH 13/48] Add new form for scheduling and associated unit test. Switch to using Body.objects.get_or_create --- neoexchange/ingest/forms.py | 13 +++++++++- neoexchange/ingest/tests/test_forms.py | 33 ++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index dda7366d7..85f3bff90 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -1,7 +1,7 @@ from datetime import datetime from django import forms from django.db.models import Q -from ingest.models import Body +from ingest.models import Body, Proposal from django.utils.translation import ugettext as _ class EphemQuery(forms.Form): @@ -20,3 +20,14 @@ def clean_target(self): raise forms.ValidationError("Object not found.") elif body.count() > 1: raise forms.ValidationError("Multiple objects found.") + +class ScheduleForm(forms.Form): + SITES = (('V37','ELP (V37)'),('F65','FTN (F65)'),('E10', 'FTS (E10)'),('W86','LSC (W85-87)'),('K92','CPT (K91-93)'),('Q63','COJ (Q63-64)')) + proposals = Proposal.objects.all() + proposal_choices = [] + for proposal in proposals.values(): + proposal_choices.append((str(proposal['code']), str(proposal['title']))) + proposal_code = forms.ChoiceField(required=True, choices=proposal_choices) + site_code = forms.ChoiceField(required=True, choices=SITES) + utc_date = forms.DateField(input_formats=['%Y-%m-%d',], initial=datetime.utcnow().date(), required=True, widget=forms.TextInput(attrs={'size':'10'}), error_messages={'required': _(u'UTC date is required')}) + body_id = forms.IntegerField(widget=forms.HiddenInput()) diff --git a/neoexchange/ingest/tests/test_forms.py b/neoexchange/ingest/tests/test_forms.py index 4d8bbd0fa..9b0d89588 100644 --- a/neoexchange/ingest/tests/test_forms.py +++ b/neoexchange/ingest/tests/test_forms.py @@ -17,9 +17,9 @@ from ingest.models import Body #Import module to test -from ingest.forms import EphemQuery +from ingest.forms import EphemQuery, ScheduleForm -class EphemQueryFormTest(TestCase): +class TestEphemQueryForm(TestCase): def setUp(self): params = { 'provisional_name' : 'N999r0q', @@ -37,8 +37,7 @@ def setUp(self): 'active' : True, 'origin' : 'M', } - self.body = Body.objects.create(**params) - self.body.save() + self.body, created = Body.objects.get_or_create(**params) def test_form_has_label(self): form = EphemQuery() @@ -66,3 +65,29 @@ def test_form_handles_save(self): 'alt_limit' : 30.0 }) self.assertTrue(form.is_valid()) + +class TestScheduleForm(TestCase): + + def setUp(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body, created = Body.objects.get_or_create(**params) + + def test_form_has_fields(self): + form = ScheduleForm() + self.assertIsInstance(form, ScheduleForm) + self.assertIn('Proposal', form.as_p()) + self.assertIn('Site code:', form.as_p()) From d58ca03f36b3e4f25bfefbd4a0f176074df362ef Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Mon, 4 May 2015 15:54:13 -0700 Subject: [PATCH 14/48] Add first, fairly hardwired version of object scheduler: templates/ingest/body_detail.html: Make 'Scheduler Observations' a link. templates/ingest/schedule.html" New template for scheduling view. ingest/tests/test_views.py: New test class for scheduling page. Switch to using get_or_create(). ingest/views.py: New 'schedule' view skeleton. Needs several more methods filling out. neox/tests/test_schedule_observations.py: Functional Test for scheduling (doesn't currently pass due to missing methods as above). neox/urls.py: Point to new schedule view. --- .../ingest/templates/ingest/body_detail.html | 2 +- .../ingest/templates/ingest/schedule.html | 45 ++++++++++++ neoexchange/ingest/tests/test_views.py | 59 +++++++++++++-- neoexchange/ingest/views.py | 50 ++++++++++++- .../neox/tests/test_schedule_observations.py | 73 +++++++++++++++---- neoexchange/neox/urls.py | 1 + 6 files changed, 207 insertions(+), 23 deletions(-) create mode 100644 neoexchange/ingest/templates/ingest/schedule.html diff --git a/neoexchange/ingest/templates/ingest/body_detail.html b/neoexchange/ingest/templates/ingest/body_detail.html index 93206206f..cfb39cbfc 100644 --- a/neoexchange/ingest/templates/ingest/body_detail.html +++ b/neoexchange/ingest/templates/ingest/body_detail.html @@ -19,7 +19,7 @@

    Object: {{body.current_name}}

    Type{{body.get_source_type_display}}

    Status {% if body.active %}Actively Following{% else %}Not Following{% endif %}

    Source {{body.get_origin_display}}

    - Schedule Observations + Schedule Observations

    Recent Observations

    diff --git a/neoexchange/ingest/templates/ingest/schedule.html b/neoexchange/ingest/templates/ingest/schedule.html new file mode 100644 index 000000000..ed1fc8acf --- /dev/null +++ b/neoexchange/ingest/templates/ingest/schedule.html @@ -0,0 +1,45 @@ +{% extends 'ingest/base.html' %} +{% load url from future %} +{% block header %}NEOx scheduling{% endblock %} + +{% block bodyclass %}page{% endblock %} + +{% block extramenu %} +
    +

    Scheduling for: {{target_name}}

    +
    +{% endblock%} + +{% block main-content %} +
    +
    +
    + +
    + {{ form.proposal_code }} + {{ form.site_code }} +
    +
    + {{ form.body_id }} + {{ form.utc_date }} + +
    + {% for key, error in form.errors.items %} +
    + {{ error }} +
    + {% endfor %} + +
    +
    +
    + +{% endblock %} diff --git a/neoexchange/ingest/tests/test_views.py b/neoexchange/ingest/tests/test_views.py index c7c987660..abac6e067 100644 --- a/neoexchange/ingest/tests/test_views.py +++ b/neoexchange/ingest/tests/test_views.py @@ -26,7 +26,7 @@ #Import module to test from ingest.ephem_subs import call_compute_ephem, determine_darkness_times from ingest.views import home, clean_NEOCP_object -from ingest.models import Body +from ingest.models import Body, Proposal from ingest.forms import EphemQuery @@ -99,8 +99,7 @@ def setUp(self): 'active' : True, 'origin' : 'M', } - self.body = Body.objects.create(**params) - self.body.save() + self.body, created = Body.objects.get_or_create(**params) def test_home_page_renders_home_template(self): response = self.client.get('/') @@ -130,8 +129,7 @@ def setUp(self): 'active' : True, 'origin' : 'M', } - self.body = Body.objects.create(**params) - self.body.save() + self.body, created = Body.objects.get_or_create(**params) def test_home_page_can_save_a_GET_request(self): @@ -206,3 +204,54 @@ def test_target_page_returns_correct_html(self): response = targetlist.render_to_response(targetlist) expected_html = render_to_string('ingest/body_list.html') self.assertEqual(response, expected_html) + +class ScheduleTargetsPageTest(TestCase): + maxDiff = None + + def setUp(self): + # Initialise with a test body and two test proposals + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body, created = Body.objects.get_or_create(**params) + + neo_proposal_params = { 'code' : 'LCO2015A-009', + 'title' : 'LCOGT NEO Follow-up Network' + } + self.neo_proposal, created = Proposal.objects.get_or_create(**neo_proposal_params) + + test_proposal_params = { 'code' : 'LCOEngineering', + 'title' : 'Test Proposal' + } + self.test_proposal, created = Proposal.objects.get_or_create(**test_proposal_params) + + def test_uses_schedule_template(self): + response = self.client.get('/schedule/', + data = {'body_id' : self.body.pk, + 'site_code' : 'F65', + 'utc_date' : '2015-04-20', + } + ) + self.assertTemplateUsed(response, 'ingest/schedule.html') + + def test_schedule_page_contains_object_name(self): + response = self.client.get('/schedule/', + data = {'body_id' : self.body.pk, + 'site_code' : 'F65', + 'utc_date' : '2015-04-20', + 'proposal_code' : self.neo_proposal.code + } + ) + self.assertContains(response, 'Scheduling for: ' + self.body.current_name()) diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index af2e12759..931f21d51 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -18,8 +18,8 @@ from django.forms.models import model_to_dict from django.shortcuts import render from django.views.generic import DetailView, ListView -from ingest.ephem_subs import call_compute_ephem, determine_darkness_times -from ingest.forms import EphemQuery +from ingest.ephem_subs import call_compute_ephem, compute_ephem, determine_darkness_times +from ingest.forms import EphemQuery, ScheduleForm from ingest.models import * from ingest.sources_subs import fetchpage_and_make_soup, packed_to_normal, fetch_mpcorbit from ingest.time_subs import extract_mpc_epoch, parse_neocp_date @@ -79,12 +79,56 @@ def ephemeris(request): return render(request, 'ingest/home.html', {'form' : form}) return render(request, 'ingest/ephem.html', - {'new_target_name' : form['target'].value, + {'new_target_name' : form['target'].value, 'ephem_lines' : ephem_lines, 'site_code' : form['site_code'].value(), } ) +def schedule(request): + + body_id = request.GET.get('body_id', 1) + form = ScheduleForm(request.GET, initial={'body_id' : body_id}) + body = Body.objects.get(id=form.initial['body_id']) + if form.is_valid(): + data = form.cleaned_data + body_elements = model_to_dict(body) + #proposal = Proposal.objects.get(title__icontains = 'NEO Follow-up Network') + # Check for valid proposal + + # Determine magnitude + dark_start, dark_end = determine_darkness_times(data['site_code'], data['utc_date']) + dark_midpoint = dark_start + (dark_end-dark_start)/2 + emp = compute_ephem(dark_midpoint, body_elements, data['site_code'], False, False, False) + magnitude = emp[3] + speed = emp[4] + + # Determine slot length + slot_length = 30 + # slot_length = determine_slot_length(body_elements.provisional_name, magnitude, site_code) + + # Determine exposure length and count + # exp_length, exp_count = determine_exptime_count(speed, site_code, slot_length) + + # Assemble request + # make_request(body_elements, params) + # Record block and submit to scheduler +# if check_block_exists == 0: +# submit_block_to_scheduler() +# record_block() + return render(request, 'ingest/schedule.html', + {'target_name' : body.current_name(), + 'magnitude' : magnitude, + 'speed' : speed, + 'slot_length' : slot_length} + ) + else: + return render(request, 'ingest/schedule.html', {'form' : form}) + + return render(request, 'ingest/schedule.html', + {'target_name' : body.current_name()} + ) + def save_and_make_revision(body,kwargs): ''' Make a revision if any of the parameters have changed, but only do it once per ingest not for each parameter ''' diff --git a/neoexchange/neox/tests/test_schedule_observations.py b/neoexchange/neox/tests/test_schedule_observations.py index a825a17e1..7906ad79d 100644 --- a/neoexchange/neox/tests/test_schedule_observations.py +++ b/neoexchange/neox/tests/test_schedule_observations.py @@ -3,7 +3,7 @@ from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from datetime import datetime -from ingest.models import Body +from ingest.models import Body, Proposal class ScheduleObservations(FunctionalTest): @@ -23,8 +23,7 @@ def insert_test_bodies(self): 'active' : True, 'origin' : 'M', } - self.body = Body.objects.create(**params1) - self.body.save() + self.body1, created = Body.objects.get_or_create(**params1) params2 = { 'provisional_name' : 'WH2845B', 'abs_mag' : 18.2, @@ -41,26 +40,72 @@ def insert_test_bodies(self): 'active' : True, 'origin' : 'M', } - self.body = Body.objects.create(**params2) - self.body.save() + self.body2, created = Body.objects.get_or_create(**params2) + + def insert_test_proposals(self): + + neo_proposal_params = { 'code' : 'LCO2015A-009', + 'title' : 'LCOGT NEO Follow-up Network' + } + self.neo_proposal, created = Proposal.objects.get_or_create(**neo_proposal_params) + + test_proposal_params = { 'code' : 'LCOEngineering', + 'title' : 'Test Proposal' + } + self.test_proposal, created = Proposal.objects.get_or_create(**test_proposal_params) def test_can_schedule_observations(self): - ## Insert test body otherwise things will fail + ## Insert test bodies and proposals otherwise things will fail self.insert_test_bodies() + self.insert_test_proposals() - # Eduardo has heard about a new website for NEOs. He goes to the - # homepage - self.browser.get(self.live_server_url) + # Sharon has heard about a new website for NEOs. She goes to the + # page of the first target + # (XXX semi-hardwired but the targets link should be being tested in + # test_targets_validation.TargetsValidationTest + self.browser.get(self.live_server_url + '/target/1/') - # She sees a link to TARGETS - link = self.browser.find_element_by_link_text('TARGETS') - target_url = self.live_server_url + '/target/' + # She sees a Schedule Observations button + link = self.browser.find_element_by_link_text('Schedule Observations') + target_url = self.live_server_url + '/schedule/?body_id=1' self.assertEqual(link.get_attribute('href'), target_url) - # She clicks the link to go to the TARGETS page + # She clicks the link to go to the Schedule Observations page link.click() self.browser.implicitly_wait(3) new_url = self.browser.current_url self.assertEqual(str(new_url), target_url) - + + + # She notices a new selection for the proposal and site code and + # chooses the NEO Follow-up Network and ELP (V37) + proposal_choices = Select(self.browser.find_element_by_id('id_proposal_code')) + self.assertIn(self.neo_proposal.title, [option.text for option in proposal_choices.options]) + + proposal_choices.select_by_visible_text(self.neo_proposal.title) + + site_choices = Select(self.browser.find_element_by_id('id_site_code')) + self.assertIn('ELP (V37)', [option.text for option in site_choices.options]) + + site_choices.select_by_visible_text('ELP (V37)') + + datebox = self.get_item_input_box('id_utc_date') + datebox.clear() + datebox.send_keys('2015-04-21') + datebox.send_keys(Keys.ENTER) + + # The page refreshes and a series of values for magnitude, speed, slot + # length, number and length of exposures appear + magnitude = self.browser.find_element_by_id('id_magnitude').text + self.assertIn('Magnitude: 20.39', magnitude) + speed = self.browser.find_element_by_id('id_speed').text + self.assertIn("Speed: 2.52 '/min", speed) + slot_length = self.browser.find_element_by_id('id_slot_length').text + self.assertIn('Slot length: 20 mins', slot_length) + num_exp = self.browser.find_element_by_id('id_no_of_exps').text + self.assertIn('No. of exp.: 16', num_exp) + exp_length = self.browser.find_element_by_id('id_exp_length').text + self.assertIn('Exp length: 50 secs', speed) + + self.fail("Finish the test!") diff --git a/neoexchange/neox/urls.py b/neoexchange/neox/urls.py index cf4e6056c..8510612e4 100644 --- a/neoexchange/neox/urls.py +++ b/neoexchange/neox/urls.py @@ -29,6 +29,7 @@ url(r'^target/(?P\d+)/$',DetailView.as_view(model=Body, context_object_name='body'), name='target'), url(r'^search/$', BodySearchView.as_view(context_object_name="target_list"), name='search'), url(r'^ephemeris/$', 'ephemeris', name='ephemeris'), + url(r'^schedule/$', 'schedule', name='schedule'), url(r'^grappelli/', include('grappelli.urls')), url(r'^admin/', include(admin.site.urls)), )+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) From 58fe1b2f614bf2d9fb1b9beee153fb006d7b087b Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Mon, 4 May 2015 18:18:43 -0700 Subject: [PATCH 15/48] Implement methods for finding slot length, exposure time and no. of exposures with associated unit tests. Functional Test now passes (as far as implemented) --- neoexchange/ingest/ephem_subs.py | 215 +++++------- neoexchange/ingest/tests/test_ephem_subs.py | 330 +++++++++++++++++- neoexchange/ingest/views.py | 12 +- .../neox/tests/test_schedule_observations.py | 6 +- 4 files changed, 434 insertions(+), 129 deletions(-) diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/ingest/ephem_subs.py index 492b0305d..a8777dc51 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/ingest/ephem_subs.py @@ -659,44 +659,52 @@ def __init__(self, value): def __str__(self): return self.value -BRIGHTEST_ALLOWABLE_MAG = 5 +BRIGHTEST_ALLOWABLE_MAG = 10 -def get_mag_mapping(site): +def get_mag_mapping(site_code): '''Defines the site-specific mappings from target magnitude to desired - exposure time. A null dictionary is returned if the site name isn't - recognized''' + slot length (in minutes). A null dictionary is returned if the site name + isn't recognized''' + + twom_site_codes = ['F65', 'E10'] + good_onem_site_codes = ['V37', 'K91', 'K92', 'K93', 'W85', 'W86', 'W87'] + # COJ normally has bad seeing, allow more time + bad_onem_site_codes = ['Q63', 'Q64'] # Magnitudes represent upper bin limits - site = site.upper() - if site == 'FTX' or site == 'FTS': + site_code = site_code.upper() + if site_code in twom_site_codes: # Mappings for FTN/FTS. Assumes Spectral+Solar filter mag_mapping = { - 19 : 45, - 19.5 : 60, - 20 : 100, - 20.5 : 120, - 21 : 150, - 21.5 : 215, - 22 : 240, - 23.3 : 300 + 19 : 15, + 20 : 20, + 20.5 : 22.5, + 21 : 25, + 21.5 : 27.5, + 22 : 30, + 23.3 : 35 } - elif site == 'SQA': -# Mappings for Sedgwick. Assumes kb18+parfocal clear - mag_mapping = { - 18 : 45, - 19 : 100, - 20 : 150, - 21 : 240, - 21.6 : 300 - } - elif site == 'ELP' or site == 'LSC' or site == 'CPT': + elif site_code in good_onem_site_codes: # Mappings for McDonald. Assumes kb74+w mag_mapping = { - 18 : 45, - 19 : 100, - 20 : 150, - 21 : 240, - 22.0 : 300 + 18 : 15, + 20 : 20, + 20.5 : 22.5, + 21 : 25, + 21.5 : 30, + 22.0 : 40, + 22.5 : 45 + } + elif site_code in bad_onem_site_codes: +# COJ normally has bad seeing, allow more time + mag_mapping = { + 18 : 17.5, + 19.5 : 20, + 20 : 22.5, + 20.5 : 25, + 21 : 27.5, + 21.5 : 32.5, + 22.0 : 35 } else: mag_mapping = {} @@ -704,17 +712,17 @@ def get_mag_mapping(site): return mag_mapping -def mag_to_exptime(site, mag, debug=False): +def determine_slot_length(target_name, mag, site_code, debug=False): if mag < BRIGHTEST_ALLOWABLE_MAG: raise MagRangeError("Target too bright") -# Obtain magnitude->exp. time mapping dictionary - mag_mapping = get_mag_mapping(site) +# Obtain magnitude->slot length mapping dictionary + mag_mapping = get_mag_mapping(site_code) if debug: print mag_mapping - if mag_mapping == {}: return 9999 + if mag_mapping == {}: return 0 - # Derive your tuple from the magnitude-exposure mapping data structure + # Derive your tuple from the magnitude->slot length mapping data structure upper_mags = tuple(sorted(mag_mapping.keys())) for upper_mag in upper_mags: @@ -731,6 +739,41 @@ def estimate_exptime(rate, pixscale=0.304, roundtime=10.0): round_exptime = max(int(exptime/roundtime)*roundtime, 1.0) return (round_exptime, exptime) +def determine_exptime(speed, pixel_scale, max_exp_time=300.0): + (round_exptime, full_exptime) = estimate_exptime(speed, pixel_scale, 5.0) + + if ( round_exptime > max_exp_time ): + logger.debug("Capping exposure time at %.1f seconds (Was %1.f seconds" % \ + (round_exptime, max_exp_time)) + round_exptime = full_exptime = max_exp_time + if ( round_exptime < 10.0 ): +# If under 10 seconds, re-round to nearest half second + (round_exptime, full_exptime) = estimate_exptime(speed, pixel_scale, 0.5) + logger.debug("Estimated exptime=%.1f seconds (%.1f)" % (round_exptime ,full_exptime)) + + return round_exptime + +def determine_exp_time_count(speed, site_code, slot_length_in_mins): + exp_time = None + exp_count = None + + (chk_site_code, setup_overhead, exp_overhead, pixel_scale, ccd_fov, max_exp_time, alt_limit) = get_sitecam_params(site_code) + + exp_time = determine_exptime(speed, pixel_scale, max_exp_time) + + slot_length = slot_length_in_mins * 60.0 + exp_count = int((slot_length - setup_overhead)/(exp_time + exp_overhead)) + if exp_count < 4: + exp_count = 4 + exp_time = (slot_length - setup_overhead - (exp_overhead * float(exp_count))) / exp_count + logger.debug("Reducing exposure time to %.1f secs to allow %d exposures in group" % ( exp_time, exp_count )) + logger.debug("Slot length of %.1f mins (%.1f secs) allows %d x %.1f second exposures" % \ + ( slot_length/60.0, slot_length, exp_count, exp_time)) + if exp_time == None or exp_time <= 0.0 or exp_count < 1: + logger.debug("Invalid exposure count") + return False + return exp_time, exp_count + def compute_score(obj_alt, moon_alt, moon_sep, alt_limit=25.0): '''Simple noddy scoring calculation for choosing best slot''' @@ -1043,9 +1086,9 @@ def get_mountlimits(site_code_or_name): return (ha_neg_limit, ha_pos_limit, alt_limit) -def get_siteparams(site): +def get_sitecam_params(site): '''Translates (e.g. 'FTN') to MPC site code, pixel scale, maximum - exposure time and slot length. + exposure time, setup and exposure overheads. site_code is set to 'XXX' and the others are set to -1 in the event of an unrecognized site.''' @@ -1058,115 +1101,47 @@ def get_siteparams(site): normal_alt_limit = 30.0 twom_alt_limit = 20.0 + onem_exp_overhead = 15.5 + sinistro_exp_overhead = 48.0 + onem_setup_overhead = 120.0 + twom_setup_overhead = 180.0 + twom_exp_overhead = 22.5 + point4m_exp_overhead = 7.5 # for BPL + valid_site_codes = [ 'V37', 'W85', 'W86', 'W87', 'K91', 'K92', 'K93', 'Q63', 'Q64' ] site = site.upper() if site == 'FTN' or 'OGG-CLMA' in site or site == 'F65': site_code = 'F65' - slot_length = 30 + setup_overhead = twom_setup_overhead + exp_overhead = twom_exp_overhead pixel_scale = 0.304 fov = arcmins_to_radians(10.0) max_exp_length = 300.0 alt_limit = twom_alt_limit elif site == 'FTS' or 'COJ-CLMA' in site or site == 'E10': site_code = 'E10' - slot_length = 30 + setup_overhead = twom_setup_overhead + exp_overhead = twom_exp_overhead pixel_scale = 0.304 fov = arcmins_to_radians(10.0) max_exp_length = 300.0 alt_limit = twom_alt_limit - elif 'SQA' in site: - site_code = 'G51' - slot_length = 30 - pixel_scale = 0.57 - fov = arcmins_to_radians(12.0) - max_exp_length = 300.0 - alt_limit = normal_alt_limit - elif 'ELP-DOM' in site: - site_code = 'V37' - slot_length = 35 - pixel_scale = onem_pixscale - fov = arcmins_to_radians(onem_fov) - max_exp_length = 300.0 - alt_limit = normal_alt_limit - elif 'BPL-DOM' in site: - site_code = 'G51' - slot_length = 30 - pixel_scale = onem_sinistro_pixscale - fov = arcmins_to_radians(onem_sinistro_fov) - max_exp_length = 300.0 - alt_limit = normal_alt_limit - elif 'ELP-SITE' in site or 'LSC-SITE' in site or 'CPT-SITE' in site or 'COJ-SITE' in site: -# Overall metasite for Planck observations - pond_site_codes = { 'ELP-SITE' : 'V37', - 'LSC-SITE' : 'W85', - 'CPT-SITE' : 'K91', - 'ELP-SITE-1M0A' : 'V37', - 'LSC-SITE-1M0A' : 'W85', - 'CPT-SITE-1M0A' : 'K91', - 'COJ-SITE-1M0A' : 'Q63', - } - site_code = pond_site_codes.get(site, 'XXX') -# Lookup failed, set to unrecognized - if site_code == 'XXX': - slot_length = pixel_scale = fov = max_exp_length = alt_limit = -1 - else: -# Site code found, set parameters - slot_length = 35.0 - pixel_scale = onem_pixscale - fov = arcmins_to_radians(onem_fov) - if 'LSC-SITE' in site: - pixel_scale = onem_sinistro_pixscale - fov = arcmins_to_radians(onem_sinistro_fov) - max_exp_length = 300.0 - alt_limit = normal_alt_limit -# Used for GAIA -# max_exp_length = 90.0 -# alt_limit = 40.0 - elif 'LSC-DOM' in site or 'CPT-DOM' in site or 'COJ-DOM' in site: - pond_site_codes = { 'LSC-DOMA-1M0A' : 'W85', - 'LSC-DOMB-1M0A' : 'W86', - 'LSC-DOMC-1M0A' : 'W87', - 'CPT-DOMA-1M0A' : 'K91', - 'CPT-DOMB-1M0A' : 'K92', - 'CPT-DOMC-1M0A' : 'K93', - 'COJ-DOMA-1M0A' : 'Q63', - 'COJ-DOMB-1M0A' : 'Q64', - } - site_code = pond_site_codes.get(site, 'XXX') -# Lookup failed, set to unrecognized - if site_code == 'XXX': - slot_length = pixel_scale = fov = max_exp_length = alt_limit = -1 - else: -# Site code found, set parameters - slot_length = 35.0 - pixel_scale = onem_pixscale - fov = arcmins_to_radians(onem_fov) - if 'LSC-DOMB' in site or 'LSC-DOMC' in site: - pixel_scale = onem_sinistro_pixscale - fov = arcmins_to_radians(onem_sinistro_fov) - max_exp_length = 300.0 - alt_limit = normal_alt_limit - elif 'LSC-AQWA-0M4A' in site: - site_code = 'W85' # XXX Wrong - slot_length = 30 - pixel_scale = point4m_pixscale - fov = arcmins_to_radians(point4m_fov) - max_exp_length = 300.0 - alt_limit = 17.0 elif site in valid_site_codes: - slot_length = 25 + setup_overhead = onem_setup_overhead + exp_overhead = onem_exp_overhead pixel_scale = onem_pixscale fov = arcmins_to_radians(onem_fov) if 'W86' in site or 'W87' in site: pixel_scale = onem_sinistro_pixscale fov = arcmins_to_radians(onem_sinistro_fov) + exp_overhead = sinistro_exp_overhead max_exp_length = 300.0 alt_limit = normal_alt_limit site_code = site else: # Unrecognized site site_code = 'XXX' - slot_length = pixel_scale = fov = max_exp_length = alt_limit = -1 + setup_overhead = exp_overhead = pixel_scale = fov = max_exp_length = alt_limit = -1 - return (site_code, slot_length, pixel_scale, fov, max_exp_length, alt_limit) + return (site_code, setup_overhead, exp_overhead, pixel_scale, fov, max_exp_length, alt_limit) diff --git a/neoexchange/ingest/tests/test_ephem_subs.py b/neoexchange/ingest/tests/test_ephem_subs.py index 642804a1f..63aa6135d 100644 --- a/neoexchange/ingest/tests/test_ephem_subs.py +++ b/neoexchange/ingest/tests/test_ephem_subs.py @@ -17,9 +17,10 @@ from django.test import TestCase from django.forms.models import model_to_dict from rise_set.angle import Angle +from math import radians #Import module to test -from ingest.ephem_subs import compute_ephem, call_compute_ephem, get_mountlimits, determine_darkness_times +from ingest.ephem_subs import * from ingest.models import Body @@ -224,3 +225,330 @@ def test_call_compute_ephem_with_altlimit(self): while line < len(expected_ephem_lines): self.assertEqual(expected_ephem_lines[line], ephem_lines[line]) line += 1 + +class TestDetermineSlotLength(TestCase): + + def test_bad_site_code(self): + site_code = 'foo' + name = 'WH2845B' + mag = 17.58 + expected_length = 0 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_very_bright_nonNEOWISE_good1m_lc(self): + site_code = 'k91' + name = 'WH2845B' + mag = 17.58 + expected_length = 15 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_very_bright_nonNEOWISE_good1m(self): + site_code = 'K91' + name = 'WH2845B' + mag = 17.58 + expected_length = 15 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_bright_nonNEOWISE_good1m(self): + site_code = 'K92' + name = 'WH2845B' + mag = 19.9 + expected_length = 20 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_medium_nonNEOWISE_good1m(self): + site_code = 'K93' + name = 'WH2845B' + mag = 20.1 + expected_length = 22.5 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_mediumfaint_nonNEOWISE_good1m(self): + site_code = 'V37' + name = 'WH2845B' + mag = 20.6 + expected_length = 25 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_faint_nonNEOWISE_good1m(self): + site_code = 'W85' + name = 'WH2845B' + mag = 21.0 + expected_length = 30 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_veryfaint_nonNEOWISE_good1m(self): + site_code = 'W86' + name = 'WH2845B' + mag = 21.51 + expected_length = 40 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_reallyfaint_nonNEOWISE_good1m(self): + site_code = 'W87' + name = 'WH2845B' + mag = 22.1 + expected_length = 45 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_toofaint_nonNEOWISE_good1m(self): + site_code = 'W87' + name = 'WH2845B' + mag = 23.1 + with self.assertRaises(MagRangeError): + slot_length = determine_slot_length(name, mag, site_code) + + def test_slot_length_toobright_nonNEOWISE_good1m(self): + site_code = 'W87' + name = 'WH2845B' + mag = 3.1 + with self.assertRaises(MagRangeError): + slot_length = determine_slot_length(name, mag, site_code) + + def test_slot_length_very_bright_nonNEOWISE_bad1m(self): + site_code = 'Q63' + name = 'WH2845B' + mag = 17.58 + expected_length = 17.5 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_bright_nonNEOWISE_bad1m(self): + site_code = 'Q63' + name = 'WH2845B' + mag = 19.9 + expected_length = 22.5 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_medium_nonNEOWISE_bad1m(self): + site_code = 'Q64' + name = 'WH2845B' + mag = 20.1 + expected_length = 25 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_mediumfaint_nonNEOWISE_bad1m(self): + site_code = 'Q63' + name = 'WH2845B' + mag = 20.6 + expected_length = 27.5 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_faint_nonNEOWISE_bad1m(self): + site_code = 'Q63' + name = 'WH2845B' + mag = 21.0 + expected_length = 32.5 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_veryfaint_nonNEOWISE_bad1m(self): + site_code = 'Q64' + name = 'WH2845B' + mag = 21.51 + expected_length = 35 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_toofaint_for_coj_nonNEOWISE_bad1m(self): + site_code = 'Q64' + name = 'WH2845B' + mag = 22.1 + with self.assertRaises(MagRangeError): + slot_length = determine_slot_length(name, mag, site_code) + + def test_slot_length_toofaint_nonNEOWISE_bad1m(self): + site_code = 'Q64' + name = 'WH2845B' + mag = 23.1 + with self.assertRaises(MagRangeError): + slot_length = determine_slot_length(name, mag, site_code) + + def test_slot_length_toobright_nonNEOWISE_bad1m(self): + site_code = 'Q64' + name = 'WH2845B' + mag = 3.1 + with self.assertRaises(MagRangeError): + slot_length = determine_slot_length(name, mag, site_code) + + def test_slot_length_very_bright_nonNEOWISE_2m_lc(self): + site_code = 'f65' + name = 'WH2845B' + mag = 17.58 + expected_length = 15 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_very_bright_nonNEOWISE_2m(self): + site_code = 'E10' + name = 'WH2845B' + mag = 17.58 + expected_length = 15 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_bright_nonNEOWISE_2m(self): + site_code = 'E10' + name = 'WH2845B' + mag = 19.9 + expected_length = 20 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_medium_nonNEOWISE_2m(self): + site_code = 'E10' + name = 'WH2845B' + mag = 20.1 + expected_length = 22.5 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_mediumfaint_nonNEOWISE_2m(self): + site_code = 'F65' + name = 'WH2845B' + mag = 20.6 + expected_length = 25 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_faint_nonNEOWISE_2m(self): + site_code = 'F65' + name = 'WH2845B' + mag = 21.0 + expected_length = 27.5 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_veryfaint_nonNEOWISE_2m(self): + site_code = 'F65' + name = 'WH2845B' + mag = 21.51 + expected_length = 30 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_reallyfaint_nonNEOWISE_2m(self): + site_code = 'F65' + name = 'WH2845B' + mag = 23.2 + expected_length = 35 + slot_length = determine_slot_length(name, mag, site_code) + self.assertEqual(expected_length, slot_length) + + def test_slot_length_toofaint_nonNEOWISE_2m(self): + site_code = 'F65' + name = 'WH2845B' + mag = 23.4 + with self.assertRaises(MagRangeError): + slot_length = determine_slot_length(name, mag, site_code) + + def test_slot_length_toobright_nonNEOWISE_2m(self): + site_code = 'F65' + name = 'WH2845B' + mag = 3.1 + with self.assertRaises(MagRangeError): + slot_length = determine_slot_length(name, mag, site_code) + +class TestGetSiteCamParams(TestCase): + + twom_setup_overhead = 180.0 + twom_exp_overhead = 22.5 + twom_fov = radians(10.0/60.0) + onem_sbig_fov = radians(15.5/60.0) + onem_setup_overhead = 120.0 + onem_exp_overhead = 15.5 + sinistro_exp_overhead = 48.0 + onem_sinistro_fov = radians(26.4/60.0) + max_exp = 300.0 + + def test_bad_site(self): + site_code = 'wibble' + chk_site_code, setup_overhead, exp_overhead, pixel_scale, ccd_fov, max_exp_time, alt_limit = get_sitecam_params(site_code) + self.assertEqual('XXX', chk_site_code) + self.assertEqual(-1, pixel_scale) + self.assertEqual(-1, max_exp_time) + self.assertEqual(-1, setup_overhead) + self.assertEqual(-1, exp_overhead) + + def test_2m_site(self): + site_code = 'f65' + chk_site_code, setup_overhead, exp_overhead, pixel_scale, ccd_fov, max_exp_time, alt_limit = get_sitecam_params(site_code) + self.assertEqual(site_code.upper(), chk_site_code) + self.assertEqual(0.304, pixel_scale) + self.assertEqual(self.twom_fov, ccd_fov) + self.assertEqual(self.max_exp, max_exp_time) + self.assertEqual(self.twom_setup_overhead, setup_overhead) + self.assertEqual(self.twom_exp_overhead, exp_overhead) + + def test_1m_site_sbig(self): + site_code = 'V37' + chk_site_code, setup_overhead, exp_overhead, pixel_scale, ccd_fov, max_exp_time, alt_limit = get_sitecam_params(site_code) + self.assertEqual(site_code.upper(), chk_site_code) + self.assertEqual(0.464, pixel_scale) + self.assertEqual(self.onem_sbig_fov, ccd_fov) + self.assertEqual(self.onem_setup_overhead, setup_overhead) + self.assertEqual(self.onem_exp_overhead, exp_overhead) + self.assertEqual(self.max_exp, max_exp_time) + + def test_1m_site_sinistro(self): + site_code = 'W86' + chk_site_code, setup_overhead, exp_overhead, pixel_scale, ccd_fov, max_exp_time, alt_limit = get_sitecam_params(site_code) + self.assertEqual(site_code.upper(), chk_site_code) + self.assertEqual(0.389, pixel_scale) + self.assertEqual(self.onem_sinistro_fov, ccd_fov) + self.assertEqual(self.max_exp, max_exp_time) + self.assertEqual(self.onem_setup_overhead, setup_overhead) + self.assertEqual(self.sinistro_exp_overhead, exp_overhead) + +class TestDetermineExpTimeCount(TestCase): + + def test_slow_1m(self): + speed = 2.52 + site_code = 'V37' + slot_len = 22.5 + + expected_exptime = 50.0 + expected_expcount = 18 + + exp_time, exp_count = determine_exp_time_count(speed, site_code, slot_len) + + self.assertEqual(expected_exptime, exp_time) + self.assertEqual(expected_expcount, exp_count) + + def test_fast_1m(self): + speed = 23.5 + site_code = 'K91' + slot_len = 20 + + expected_exptime = 5.5 + expected_expcount = 51 + + exp_time, exp_count = determine_exp_time_count(speed, site_code, slot_len) + + self.assertEqual(expected_exptime, exp_time) + self.assertEqual(expected_expcount, exp_count) + + def test_superslow_1m(self): + speed = 0.235 + site_code = 'W85' + slot_len = 20 + + expected_exptime = 254.5 + expected_expcount = 4 + + exp_time, exp_count = determine_exp_time_count(speed, site_code, slot_len) + + self.assertEqual(expected_exptime, exp_time) + self.assertEqual(expected_expcount, exp_count) diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index 931f21d51..ed4a24424 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -18,7 +18,8 @@ from django.forms.models import model_to_dict from django.shortcuts import render from django.views.generic import DetailView, ListView -from ingest.ephem_subs import call_compute_ephem, compute_ephem, determine_darkness_times +from ingest.ephem_subs import call_compute_ephem, compute_ephem, \ + determine_darkness_times, determine_slot_length, determine_exp_time_count from ingest.forms import EphemQuery, ScheduleForm from ingest.models import * from ingest.sources_subs import fetchpage_and_make_soup, packed_to_normal, fetch_mpcorbit @@ -104,11 +105,10 @@ def schedule(request): speed = emp[4] # Determine slot length - slot_length = 30 - # slot_length = determine_slot_length(body_elements.provisional_name, magnitude, site_code) + slot_length = determine_slot_length(body_elements['provisional_name'], magnitude, data['site_code']) # Determine exposure length and count - # exp_length, exp_count = determine_exptime_count(speed, site_code, slot_length) + exp_length, exp_count = determine_exp_time_count(speed, data['site_code'], slot_length) # Assemble request # make_request(body_elements, params) @@ -120,7 +120,9 @@ def schedule(request): {'target_name' : body.current_name(), 'magnitude' : magnitude, 'speed' : speed, - 'slot_length' : slot_length} + 'slot_length' : slot_length, + 'exp_count' : exp_count, + 'exp_length' : exp_length} ) else: return render(request, 'ingest/schedule.html', {'form' : form}) diff --git a/neoexchange/neox/tests/test_schedule_observations.py b/neoexchange/neox/tests/test_schedule_observations.py index 7906ad79d..97abad0ef 100644 --- a/neoexchange/neox/tests/test_schedule_observations.py +++ b/neoexchange/neox/tests/test_schedule_observations.py @@ -102,10 +102,10 @@ def test_can_schedule_observations(self): speed = self.browser.find_element_by_id('id_speed').text self.assertIn("Speed: 2.52 '/min", speed) slot_length = self.browser.find_element_by_id('id_slot_length').text - self.assertIn('Slot length: 20 mins', slot_length) + self.assertIn('Slot length: 22.5 mins', slot_length) num_exp = self.browser.find_element_by_id('id_no_of_exps').text - self.assertIn('No. of exp.: 16', num_exp) + self.assertIn('No. of exp: 18', num_exp) exp_length = self.browser.find_element_by_id('id_exp_length').text - self.assertIn('Exp length: 50 secs', speed) + self.assertIn('Exp length: 50.0 secs', exp_length) self.fail("Finish the test!") From b49595e25f38bfa44540617d880cecbbb524d23d Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Tue, 5 May 2015 00:00:49 -0700 Subject: [PATCH 16/48] Can now schedule objects on the network --- neoexchange/ingest/ephem_subs.py | 4 +- neoexchange/ingest/forms.py | 1 + neoexchange/ingest/sources_subs.py | 174 ++++++++++++++++++ .../ingest/templates/ingest/schedule.html | 33 +++- neoexchange/ingest/tests/test_ephem_subs.py | 29 ++- neoexchange/ingest/tests/test_source_subs.py | 46 ++++- neoexchange/ingest/views.py | 47 +++-- .../neox/tests/test_schedule_observations.py | 2 + neoexchange/pip_requirements.txt | 1 + 9 files changed, 311 insertions(+), 26 deletions(-) diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/ingest/ephem_subs.py index a8777dc51..ae5c83b73 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/ingest/ephem_subs.py @@ -771,7 +771,9 @@ def determine_exp_time_count(speed, site_code, slot_length_in_mins): ( slot_length/60.0, slot_length, exp_count, exp_time)) if exp_time == None or exp_time <= 0.0 or exp_count < 1: logger.debug("Invalid exposure count") - return False + exp_time = None + exp_count = None + return exp_time, exp_count def compute_score(obj_alt, moon_alt, moon_sep, alt_limit=25.0): diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index 85f3bff90..e0cab01fd 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -31,3 +31,4 @@ class ScheduleForm(forms.Form): site_code = forms.ChoiceField(required=True, choices=SITES) utc_date = forms.DateField(input_formats=['%Y-%m-%d',], initial=datetime.utcnow().date(), required=True, widget=forms.TextInput(attrs={'size':'10'}), error_messages={'required': _(u'UTC date is required')}) body_id = forms.IntegerField(widget=forms.HiddenInput()) + ok_to_schedule = forms.BooleanField(initial=False, required=False, widget=forms.HiddenInput()) diff --git a/neoexchange/ingest/sources_subs.py b/neoexchange/ingest/sources_subs.py index b7af1dc9d..d906b9b97 100644 --- a/neoexchange/ingest/sources_subs.py +++ b/neoexchange/ingest/sources_subs.py @@ -17,6 +17,9 @@ import urllib2, os from bs4 import BeautifulSoup from datetime import datetime +from reqdb.requests import Request, UserRequest +from reqdb.client import SchedulerClient + from re import sub import logging logger = logging.getLogger(__name__) @@ -487,3 +490,174 @@ def fetch_goldstone_targets(dbg=False): radar_objects.append(obj_id) last_year_seen = year return radar_objects + +def make_location(params): + location = { + 'telescope_class' : params['pondtelescope'][0:3], + 'site' : params['site'], + 'observatory' : params['observatory'], + 'telescope' : '', + } + +# Check if the 'pondtelescope' is length 4 (1m0a) rather than length 3, and if +# so, update the null string set above with a proper telescope + if len(params['pondtelescope']) == 4: + location['telescope'] = params['pondtelescope'] + + return location + +def make_target(params): + '''Make a target dictionary for the request. RA and Dec need to be + decimal degrees''' + + ra_degs = math.degrees(params['ra_rad']) + dec_degs = math.degrees(params['dec_rad']) + target = { + 'name' : params['source_id'], + 'ra' : ra_degs, + 'dec' : dec_degs + } + return target + +def make_moving_target(elements): + '''Make a target dictionary for the request from an element set''' + + print elements + # Generate initial dictionary of things in common + target = { + 'name' : elements['provisional_name'], + 'type' : 'NON_SIDEREAL', + 'scheme' : elements['elements_type'], + # Moving object param + 'epochofel' : elements['epochofel_mjd'], + 'orbinc' : elements['orbinc'], + 'longascnode' : elements['longascnode'], + 'argofperih' : elements['argofperih'], + 'eccentricity' : elements['eccentricity'], + } + + if elements['elements_type'].upper() == 'MPC_COMET': + target['epochofperih'] = elements['epochofperih'] + target['perihdist'] = elements['perihdist'] + else: + target['meandist'] = elements['meandist'] + target['meananom'] = elements['meananom'] + + return target + +def make_window(params): + '''Make a window. This is simply set to the start and end time from + params (i.e. the picked time with the best score plus the block length), + formatted into a string. + Hopefully this will prevent rescheduling at a different time as the + co-ords will be wrong in that case...''' + window = { + 'start' : params['start_time'].strftime('%Y-%m-%dT%H:%M:%S'), + 'end' : params['end_time'].strftime('%Y-%m-%dT%H:%M:%S'), + } + + return window + +def make_molecule(params): + molecule = { + 'exposure_count' : params['exp_count'], + 'exposure_time' : params['exp_time'], + 'bin_x' : params['binning'], + 'bin_y' : params['binning'], + 'instrument_name' : params['instrument'], + 'filter' : params['filter'], + 'ag_mode' : 'Optional', # 0=On, 1=Off, 2=Optional. Default is 2. + 'ag_name' : '' + + } + return molecule + +def make_proposal(params): + '''Construct needed proposal info''' + + proposal = { + 'proposal_id' : params['proposal_code'], + 'user_id' : params['user_id'], + 'tag_id' : params['tag_id'], + 'priority' : params['priority'], + } + return proposal + +def make_constraints(params): + constraints = { +# 'max_airmass' : 2.0, # 30 deg altitude (The maximum airmass you are willing to accept) + 'max_airmass' : 1.74, # 35 deg altitude (The maximum airmass you are willing to accept) +# 'max_airmass' : 1.55, # 40 deg altitude (The maximum airmass you are willing to accept) +# 'max_airmass' : 2.37, # 25 deg altitude (The maximum airmass you are willing to accept) + } + return constraints + +def configure_defaults(params): + + site_list = { 'V37' : 'ELP' , 'K92' : 'CPT', 'COJ' : 'Q63', 'W86' : 'LSC', 'F65' : 'OGG', 'E10' : 'COJ' } + params['pondtelescope'] = '1m0' + params['observatory'] = '' + params['site'] = site_list[params['site_code']] + params['binning'] = 2 + params['instrument'] = '1M0-SCICAM-SBIG' + params['filter'] = 'w' + + if params['site_code'] == 'W86' or params['site_code'] == 'W87': + params['binning'] = 1 + params['instrument'] = '1M0-SCICAM-SINISTRO' + elif params['site_code'] == 'F65' or params['site_code'] == 'E10': + params['instrument'] = '2M0-SCICAM-SPECTRAL' + params['filter'] = 'solar' + params['user_id'] = 'tlister@lcogt.net' + params['tag_id'] = 'LCOGT' + params['priority'] = 15 + + return params + +def submit_block_to_scheduler(elements, params): + request = Request() + + params = configure_defaults(params) +# Create Location (site, observatory etc) and add to Request + location = make_location(params) + logger.debug("Location=",location) + request.set_location(location) +# Create Target (pointing) and add to Request + if len(elements) > 0: + logger.debug("Making a moving object") + target = make_moving_target(elements) + else: + logger.debug("Making a static object") + target = make_target(params) + logger.debug("Target=",target) + request.set_target(target) +# Create Window and add to Request + window = make_window(params) + logger.debug("Window=",window) + request.add_window(window) +# Create Molecule and add to Request + molecule = make_molecule(params) + request.add_molecule(molecule) # add exposure to the request + request.set_note('Submitted by NEOexchange') + logger.debug("Request=",request) + + constraints = make_constraints(params) + request.set_constraints(constraints) + +# Add the Request to the outer User Request + user_request = UserRequest(group_id=params['group_id']) + user_request.add_request(request) + user_request.operator = 'single' + proposal = make_proposal(params) + user_request.set_proposal(proposal) + +# Make an endpoint and submit the thing + client = SchedulerClient('http://scheduler1.lco.gtn/requestdb/') + response_data = client.submit(user_request) + client.print_submit_response() + request_numbers = response_data.get('request_numbers', '') +# request_numbers = (-42,) + request_number = request_numbers[0] + logger.debug("Req number=", request_number) + + return request_number diff --git a/neoexchange/ingest/templates/ingest/schedule.html b/neoexchange/ingest/templates/ingest/schedule.html index ed1fc8acf..55666cddb 100644 --- a/neoexchange/ingest/templates/ingest/schedule.html +++ b/neoexchange/ingest/templates/ingest/schedule.html @@ -22,7 +22,13 @@

    Scheduling for: {{target_name}}

    {{ form.body_id }} {{ form.utc_date }} - + {% if schedule_ok %} + + + {% else %} + + + {% endif %}
    {% for key, error in form.errors.items %}
    @@ -33,13 +39,22 @@

    Scheduling for: {{target_name}}

    -
    - + {% endif %} + {% if request_number != '' %} + + {% endif %} {% endblock %} diff --git a/neoexchange/ingest/tests/test_ephem_subs.py b/neoexchange/ingest/tests/test_ephem_subs.py index 63aa6135d..5ee946185 100644 --- a/neoexchange/ingest/tests/test_ephem_subs.py +++ b/neoexchange/ingest/tests/test_ephem_subs.py @@ -84,8 +84,7 @@ def setUp(self): 'active' : True, 'origin' : 'M', } - self.body = Body.objects.create(**params) - self.body.save() + self.body, created = Body.objects.get_or_create(**params) self.elements = {'G': 0.15, 'H': 21.0, @@ -552,3 +551,29 @@ def test_superslow_1m(self): self.assertEqual(expected_exptime, exp_time) self.assertEqual(expected_expcount, exp_count) + + def test_superfast_2m(self): + speed = 1800.0 + site_code = 'E10' + slot_len = 15 + + expected_exptime = 1.0 + expected_expcount = 30 + + exp_time, exp_count = determine_exp_time_count(speed, site_code, slot_len) + + self.assertEqual(expected_exptime, exp_time) + self.assertEqual(expected_expcount, exp_count) + + def test_block_too_short(self): + speed = 0.18 + site_code = 'F65' + slot_len = 2 + + expected_exptime = None + expected_expcount = None + + exp_time, exp_count = determine_exp_time_count(speed, site_code, slot_len) + + self.assertEqual(expected_exptime, exp_time) + self.assertEqual(expected_expcount, exp_count) diff --git a/neoexchange/ingest/tests/test_source_subs.py b/neoexchange/ingest/tests/test_source_subs.py index 1297dfc47..345f8c817 100644 --- a/neoexchange/ingest/tests/test_source_subs.py +++ b/neoexchange/ingest/tests/test_source_subs.py @@ -14,9 +14,13 @@ ''' from django.test import TestCase +from django.forms.models import model_to_dict +from ingest.models import Body +from datetime import datetime +from ingest.ephem_subs import determine_darkness_times #Import module to test -from ingest.sources_subs import parse_goldstone_chunks +from ingest.sources_subs import parse_goldstone_chunks, submit_block_to_scheduler class TestGoldstoneChunkParser(TestCase): @@ -57,3 +61,43 @@ def test_unspecficdate_named_ast(self): chunks = ['2016', 'Jan', '1685', 'Toro', 'No', 'No', 'R'] obj_id = parse_goldstone_chunks(chunks) self.assertEqual(expected_objid, obj_id) + +class TestSubmitBlockToScheduler(TestCase): + + def setUp(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : datetime(2015,03,19,00,00,00), + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body, created = Body.objects.get_or_create(**params) + + def test_submit_body_for_cpt(self): + + body_elements = model_to_dict(self.body) + body_elements['epochofel_mjd'] = self.body.epochofel_mjd() + body_elements['current_name'] = self.body.current_name() + site_code = 'K92' + utc_date = datetime(2015, 4, 21, 0, 0, 0) + dark_start, dark_end = determine_darkness_times(site_code, utc_date) + params = { 'proposal_code' : 'LCO2015A-009', + 'exp_count' : 18, + 'exp_time' : 50.0, + 'site_code' : site_code, + 'start_time' : dark_start, + 'end_time' : dark_end, + 'group_id' : body_elements['current_name'] + '_' + 'CPT' + '-' + datetime.strftime(utc_date, '%Y%m%d') + + } + + request_number = submit_block_to_scheduler(body_elements, params) diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index ed4a24424..1514c4c17 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -22,7 +22,8 @@ determine_darkness_times, determine_slot_length, determine_exp_time_count from ingest.forms import EphemQuery, ScheduleForm from ingest.models import * -from ingest.sources_subs import fetchpage_and_make_soup, packed_to_normal, fetch_mpcorbit +from ingest.sources_subs import fetchpage_and_make_soup, packed_to_normal, \ + fetch_mpcorbit, submit_block_to_scheduler from ingest.time_subs import extract_mpc_epoch, parse_neocp_date import logging import reversion @@ -89,13 +90,16 @@ def ephemeris(request): def schedule(request): body_id = request.GET.get('body_id', 1) - form = ScheduleForm(request.GET, initial={'body_id' : body_id}) + ok_to_schedule = request.GET.get('ok_to_schedule', False) + print "top of form", ok_to_schedule, body_id + form = ScheduleForm(request.GET, initial={'body_id' : body_id, 'ok_to_schedule' : ok_to_schedule}) body = Body.objects.get(id=form.initial['body_id']) if form.is_valid(): data = form.cleaned_data + print data body_elements = model_to_dict(body) - #proposal = Proposal.objects.get(title__icontains = 'NEO Follow-up Network') # Check for valid proposal + # validate_proposal_time(data['proposal_code']) # Determine magnitude dark_start, dark_end = determine_darkness_times(data['site_code'], data['utc_date']) @@ -105,27 +109,44 @@ def schedule(request): speed = emp[4] # Determine slot length - slot_length = determine_slot_length(body_elements['provisional_name'], magnitude, data['site_code']) - + try: + slot_length = determine_slot_length(body_elements['provisional_name'], magnitude, data['site_code']) + except MagRangeError: + ok_to_schedule = False # Determine exposure length and count exp_length, exp_count = determine_exp_time_count(speed, data['site_code'], slot_length) - - # Assemble request - # make_request(body_elements, params) - # Record block and submit to scheduler + if exp_length == None or exp_count == None: + ok_to_schedule = False + + if data['ok_to_schedule'] == True: + print body_elements + # Assemble request + body_elements['epochofel_mjd'] = body.epochofel_mjd() + body_elements['current_name'] = body.current_name() + params = { 'proposal_code' : data['proposal_code'], + 'exp_count' : exp_count, + 'exp_time' : exp_length, + 'site_code' : data['site_code'], + 'start_time' : dark_start, + 'end_time' : dark_end, + 'group_id' : body_elements['current_name'] + '_' + data['site_code'].upper() + '-' + datetime.strftime(data['utc_date'], '%Y%m%d') + } + # Record block and submit to scheduler # if check_block_exists == 0: -# submit_block_to_scheduler() + request_number = submit_block_to_scheduler(body_elements, params) # record_block() return render(request, 'ingest/schedule.html', - {'target_name' : body.current_name(), + {'form' : form, 'target_name' : body.current_name(), 'magnitude' : magnitude, 'speed' : speed, 'slot_length' : slot_length, 'exp_count' : exp_count, - 'exp_length' : exp_length} + 'exp_length' : exp_length, + 'schedule_ok' : ok_to_schedule, + 'request_number' : request_number} ) else: - return render(request, 'ingest/schedule.html', {'form' : form}) + return render(request, 'ingest/schedule.html', {'form' : form, 'target_name' : body.current_name()}) return render(request, 'ingest/schedule.html', {'target_name' : body.current_name()} diff --git a/neoexchange/neox/tests/test_schedule_observations.py b/neoexchange/neox/tests/test_schedule_observations.py index 97abad0ef..dab5043d1 100644 --- a/neoexchange/neox/tests/test_schedule_observations.py +++ b/neoexchange/neox/tests/test_schedule_observations.py @@ -108,4 +108,6 @@ def test_can_schedule_observations(self): exp_length = self.browser.find_element_by_id('id_exp_length').text self.assertIn('Exp length: 50.0 secs', exp_length) + # At this point, a 'Schedule this object' button appears + link = self.browser.find_element_by_link_text('Schedule this Object') self.fail("Finish the test!") diff --git a/neoexchange/pip_requirements.txt b/neoexchange/pip_requirements.txt index fb60fb785..aacdf50b2 100644 --- a/neoexchange/pip_requirements.txt +++ b/neoexchange/pip_requirements.txt @@ -14,3 +14,4 @@ pytz==2015.2 six==1.9.0 wsgiref==0.1.2 selenium +reqdbclient From 57b2d35ef83247d3ced7be080348701fab7267dd Mon Sep 17 00:00:00 2001 From: zemogle Date: Tue, 5 May 2015 16:56:19 +0100 Subject: [PATCH 17/48] Merge branch 'master' into devel-eg --- README.md | 42 ++- config/crontab.root | 4 + config/neoexchange.ini | 7 + config/nginx/nginx.conf | 2 +- docker/neoexchange.dockerfile | 3 +- initial_data.json | 0 neoexchange/fetch_neocp_populate_db.py | 98 ----- neoexchange/ingest/ephem_subs.py | 22 +- neoexchange/ingest/forms.py | 22 ++ neoexchange/ingest/models.py | 3 +- neoexchange/ingest/sources_subs.py | 7 +- .../ingest/static/ingest/css/forms.css | 37 ++ .../ingest/static/ingest/images/favicon.ico | Bin 0 -> 2238 bytes .../js/jquery.dj.selectable.js?v=0.8.0 | 352 ++++++++++++++++++ neoexchange/ingest/templates/ingest/base.html | 4 +- .../ingest/templates/ingest/body_list.html | 2 +- .../ingest/templates/ingest/ephem.html | 36 +- neoexchange/ingest/templates/ingest/home.html | 48 +-- neoexchange/ingest/tests.py | 272 -------------- neoexchange/ingest/tests/__init__.py | 1 + neoexchange/ingest/tests/test_forms.py | 68 ++++ neoexchange/ingest/tests/test_views.py | 87 +++-- neoexchange/ingest/views.py | 76 ++-- neoexchange/neox/settings.py | 8 +- neoexchange/neox/tests/base.py | 26 ++ .../neox/tests/test_ephemeris_creation.py | 66 ++-- .../neox/tests/test_ephemeris_validation.py | 2 +- .../neox/tests/test_layout_and_styling.py | 6 +- neoexchange/pip_requirements.txt | 4 +- neoexchange/update_crossids.py | 84 ----- 30 files changed, 762 insertions(+), 627 deletions(-) create mode 100644 config/crontab.root delete mode 100644 initial_data.json delete mode 100644 neoexchange/fetch_neocp_populate_db.py create mode 100644 neoexchange/ingest/forms.py create mode 100644 neoexchange/ingest/static/ingest/images/favicon.ico create mode 100644 neoexchange/ingest/static/selectable/js/jquery.dj.selectable.js?v=0.8.0 delete mode 100644 neoexchange/ingest/tests.py create mode 100644 neoexchange/ingest/tests/test_forms.py delete mode 100644 neoexchange/update_crossids.py diff --git a/README.md b/README.md index ec925abb7..4733084a5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ NEO Exchange ============ -Portal for scheduling observations of NEOs using LCOGT (Version 0.0.5) +Portal for scheduling observations of NEOs using LCOGT (Version 0.1.1) Setup ----- @@ -16,4 +16,42 @@ or: `source /bin/activate.csh # for (t)csh-shells` `pip install -r pip-requirements.txt` - +Local Testing +------------- + +For local testing you will probably want to create a +`neoexchange/neox/local_settings.py` file to point at a local test database and +to switch on `DEBUG` for easier testing. An example file would look like: +``` +import sys, os + +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +BASE_DIR = os.path.dirname(CURRENT_PATH) + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'neox.db', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +DEBUG = True + +# Use a different database file when testing or exploring in the shell. +if 'test' in sys.argv or 'test_coverage' in sys.argv or 'shell' in sys.argv: + DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3' + DATABASES['default']['NAME'] = 'test.db' + DATABASES['default']['USER'] = '' + DATABASES['default']['PASSWORD'] = '' + +STATIC_ROOT = os.path.abspath(os.path.join(BASE_DIR, '../../static/')) +``` + +To prepare the local SQLite DB for use, you should follow these steps: +1. `cd neoexchange\neoexchange` +2. Run `python manage.py syncdb` +3. Migrate the apps with `python manage.py migrate` diff --git a/config/crontab.root b/config/crontab.root new file mode 100644 index 000000000..3bcfc0f26 --- /dev/null +++ b/config/crontab.root @@ -0,0 +1,4 @@ +# Put your cron jobs here +10 5 * * * python /var/www/apps/neoexchange/manage.py fetch_goldstone_targets +10 5 * * * python /var/www/apps/neoexchange/manage.py update_neocp_data +10 5 * * * python /var/www/apps/neoexchange/manage.py update_crossids \ No newline at end of file diff --git a/config/neoexchange.ini b/config/neoexchange.ini index 12f15293b..44f4c828b 100644 --- a/config/neoexchange.ini +++ b/config/neoexchange.ini @@ -11,3 +11,10 @@ autorestart=unexpected redirect_stderr=true stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 + +[program:crond] +command=/usr/sbin/crond -n +autorestart=unexpected +redirect_stderr=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index f6c0bb530..92bd49ce4 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -30,7 +30,7 @@ http { # uWSGI over uwsgi protocol upstream django { - server 127.0.0.1:8201; # for a web port socket (we'll use this first) + server 127.0.0.1:8201; } server { diff --git a/docker/neoexchange.dockerfile b/docker/neoexchange.dockerfile index 9420caace..0f78292ad 100644 --- a/docker/neoexchange.dockerfile +++ b/docker/neoexchange.dockerfile @@ -14,7 +14,7 @@ # docker run -d -p 8200:8200 --name=neoexchange lcogtwebmaster/lcogt:neoexchange_$BRANCH # # To run with nginx + uwsgi both exposed: -# docker run -d -p 8200:8200 -p 8201:8201 --name=neoexchange lcogtwebmaster/lcogt:neoexchange_$BRANCH +# docker run -d -p 8200:8200 -p 8201:8201 --name=neox lcogtwebmaster/lcogt:neoexchange_$BRANCH # # See the notes in the code below about NFS mounts. # @@ -57,6 +57,7 @@ RUN python /var/www/apps/neoexchange/manage.py migrate --noinput COPY config/uwsgi.ini /etc/uwsgi.ini COPY config/nginx/* /etc/nginx/ COPY config/neoexchange.ini /etc/supervisord.d/neoexchange.ini +COPY config/crontab.root /var/spool/cron/root # nginx runs on port 8200, uwsgi runs on port 8201 EXPOSE 8200 8201 diff --git a/initial_data.json b/initial_data.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/neoexchange/fetch_neocp_populate_db.py b/neoexchange/fetch_neocp_populate_db.py deleted file mode 100644 index e1a463e6c..000000000 --- a/neoexchange/fetch_neocp_populate_db.py +++ /dev/null @@ -1,98 +0,0 @@ -''' -NEO exchange: NEO observing portal for Las Cumbres Observatory Global Telescope Network -Copyright (C) 2014-2015 LCOGT - -fetch_neocp_populate_db.py -- Fetch the MPC NEO Confirmation Page and populate -DB. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -''' - -import os -from ingest.sources_subs import fetch_NEOCP, fetch_NEOCP_orbit -from datetime import datetime, tzinfo -from pytz import timezone -from rise_set.moving_objects import read_neocp_orbit - -# Need to set this so Django can find its settings -os.environ['DJANGO_SETTINGS_MODULE'] = 'neox.settings' -from ingest.models import Body, check_object_exists - -def insert_new_object(elements): - '''Creates new Body entries in the DB from the passed element dictionary if - the object doesn't already exist. - - Returns -1 if a Bad Thing happened or 0 if the new Body was inserted.''' - - status = -1 - try: - Body.objects.get(provisional_name__exact=elements['name']) - except Body.MultipleObjectsReturned: - print "Multiple objects found, shouldn't happen..." - except Body.DoesNotExist: -# Insert new body into DB - - new_object = Body(provisional_name = elements['name'], - origin = elements['origin'], - source_type = elements['source_type'], - elements_type = elements['type'], - active = False, # Actively following? - fast_moving = False, # Is this object fast? - urgency = 1, # how urgent is this? - epochofel = elements['epoch'], - orbinc = elements['inclination'].in_degrees(), - longascnode = elements['long_node'].in_degrees(), - argofperih = elements['arg_perihelion'].in_degrees(), - eccentricity = elements['eccentricity'], - meandist = elements['semi_axis'], - meananom = elements['mean_anomaly'].in_degrees(), - ingest = datetime.utcnow().replace(tzinfo=timezone('UTC')) - ) - new_object.save() - status = 0 - - return status - -if __name__ == '__main__': - - dbg = True - savedir = os.path.join('/tmp', 'neoexchange') - - # Fetch down a list of objects from the MPC's NEO Confirmation Page - object_list = fetch_NEOCP() - - if dbg: print object_list - # Loop over objects, fetch the orbit from the MPC's NEO Confirmation Page - # links and insert into DB - for astobj in object_list: - # If the object doesn't already exist, try to get and insert - # XXX TODO find out if it has changed since last time and update - # if it has - if check_object_exists(astobj) == 0: - # Fetch the orbit file for the candidate, which returns 0 if no - # lines of orbit data were read (object is no longer on the NEOCP) - num_read = fetch_NEOCP_orbit(astobj, savedir, delete=True) - - if num_read != 0: - neocp_orbit_file = os.path.join(savedir, astobj+ '.neocp') - # Read the orbit file from disk (should bypass and read direct) - # and get back an element dictionary - elements = read_neocp_orbit(neocp_orbit_file) - - # Set the source type to be 'U'nknown (NEO candidate) and the - # origin to be 'M'PC - elements['source_type'] = 'U' # NEO candidate - elements['origin'] = 'M' # MPC - if dbg: print elements - insert_status = insert_new_object(elements) - else: - if dbg: print "Object", astobj, "already exists in the DB" - diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/ingest/ephem_subs.py index c57f65276..492b0305d 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/ingest/ephem_subs.py @@ -14,7 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ''' -from datetime import datetime, timedelta +from datetime import datetime, timedelta, time import slalib as S from math import sin, cos, tan, asin, acos, atan2, degrees, radians, pi, sqrt, fabs, exp, log10 from numpy import array, concatenate, zeros @@ -23,6 +23,10 @@ from ingest.time_subs import datetime2mjd_utc, datetime2mjd_tdb, mjd_utc2mjd_tt, ut1_minus_utc, round_datetime #from astsubs import mpc_8lineformat +import logging + +logger = logging.getLogger(__name__) + def compute_phase_angle(r, delta, es_Rsq, dbg=False): '''Method to compute the phase angle (beta), trapping bad values''' @@ -498,21 +502,25 @@ def determine_darkness_times(site_code, utc_date=datetime.utcnow(), debug=False) # Check if current date is greater than the end of the last night's astro darkness # Add 1 hour to this to give a bit of slack at the end and not suddenly jump # into the next day - (start_of_darkness, end_of_darkness) = astro_darkness(site_code, utc_date.replace(hour=0, minute=0, second=0, microsecond=0)) + try: + utc_date = utc_date.replace(hour=0, minute=0, second=0, microsecond=0) + except TypeError: + utc_date = datetime.combine(utc_date, time()) + (start_of_darkness, end_of_darkness) = astro_darkness(site_code, utc_date) end_of_darkness = end_of_darkness+timedelta(hours=1) - if debug: print "Start,End of darkness=", start_of_darkness, end_of_darkness + logger.debug("Start,End of darkness=%s %s", start_of_darkness, end_of_darkness) if utc_date > end_of_darkness: - if debug: print "Planning for the next night" + logger.debug("Planning for the next night") utc_date = utc_date + timedelta(days=1) elif start_of_darkness.hour > end_of_darkness.hour and utc_date.hour < end_of_darkness.hour: - if debug: print "Planning for the previous night" + logger.debug("Planning for the previous night") utc_date = utc_date + timedelta(days=-1) utc_date = utc_date.replace(hour=0, minute=0, second=0, microsecond=0) - if debug: print "Planning observations for", utc_date, "for", site_code + logger.debug("Planning observations for %s for %s", utc_date, site_code) # Get hours of darkness for site (dark_start, dark_end) = astro_darkness(site_code, utc_date) - if debug: print "Dark from ", dark_start, "to", dark_end + logger.debug("Dark from %s to %s", dark_start, dark_end) return dark_start, dark_end diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py new file mode 100644 index 000000000..dda7366d7 --- /dev/null +++ b/neoexchange/ingest/forms.py @@ -0,0 +1,22 @@ +from datetime import datetime +from django import forms +from django.db.models import Q +from ingest.models import Body +from django.utils.translation import ugettext as _ + +class EphemQuery(forms.Form): + SITES = (('V37','ELP (V37)'),('F65','FTN (F65)'),('E10', 'FTS (E10)'),('W86','LSC (W85-87)'),('K92','CPT (K91-93)'),('Q63','COJ (Q63-64)')) + target = forms.CharField(label="Enter target name...", max_length=10, required=True, widget=forms.TextInput(attrs={'size':'10'}), error_messages={'required': _(u'Target name is required')}) + site_code = forms.ChoiceField(required=True, choices=SITES) + utc_date = forms.DateField(input_formats=['%Y-%m-%d',], initial=datetime.utcnow().date(), required=True, widget=forms.TextInput(attrs={'size':'10'}), error_messages={'required': _(u'UTC date is required')}) + alt_limit = forms.FloatField(initial=30.0, required=True, widget=forms.TextInput(attrs={'size':'4'})) + + def clean_target(self): + name = self.cleaned_data['target'] + body = Body.objects.filter(Q(provisional_name__icontains = name )|Q(provisional_packed__icontains = name)|Q(name__icontains = name)) + if body.count() == 1 : + return body[0] + elif body.count() == 0: + raise forms.ValidationError("Object not found.") + elif body.count() > 1: + raise forms.ValidationError("Multiple objects found.") diff --git a/neoexchange/ingest/models.py b/neoexchange/ingest/models.py index 003d17607..0fcbcf070 100644 --- a/neoexchange/ingest/models.py +++ b/neoexchange/ingest/models.py @@ -26,7 +26,8 @@ ('E','Centaur'), ('T','Trojan'), ('U','Unknown/NEO Candidate'), - ('X','Did not exist') + ('X','Did not exist'), + ('W','Was not interesting') ) ELEMENTS_TYPES = (('MPC_MINOR_PLANET','MPC Minor Planet'),('MPC_COMET','MPC Comet')) diff --git a/neoexchange/ingest/sources_subs.py b/neoexchange/ingest/sources_subs.py index 64c8a4aaa..b7af1dc9d 100644 --- a/neoexchange/ingest/sources_subs.py +++ b/neoexchange/ingest/sources_subs.py @@ -93,7 +93,10 @@ def fetch_previous_NEOCP_desigs(dbg=False): crossids = [] for row in divs[0].find_all('li'): items = row.contents - if dbg: print items, len(items) + if dbg: print items,len(items) +# Skip the first "Processing" list item + if items[0].strip() == 'Processing': + continue crossmatch = ['', '', '', ''] if len(items) == 1: # Is of the form " does not exist" or " was not confirmed" @@ -134,7 +137,7 @@ def fetch_previous_NEOCP_desigs(dbg=False): mpec = items[3].contents[0].string + items[3].contents[1].string crossmatch = [provid, newid, mpec, date] else: - print "Unknown number of fields" + logger.warn("Unknown number of fields. items=%s", items) # Append to list if crossmatch != ['', '', '', '']: crossids.append(crossmatch) diff --git a/neoexchange/ingest/static/ingest/css/forms.css b/neoexchange/ingest/static/ingest/css/forms.css index 1b784c923..6592f4afc 100644 --- a/neoexchange/ingest/static/ingest/css/forms.css +++ b/neoexchange/ingest/static/ingest/css/forms.css @@ -138,6 +138,43 @@ form.proposalsubmit_admin .proposalsubmit_credit label { min-width:50px; } + +.home-form .inline-fields input { + display:inline-block; +} + +.home-form label { + font-size: 70%; +} + +.home-form input[type="text"], +.home-form input[type="date"], +.home-form input[type="datetime"], +.home-form input[type="email"], +.home-form input[type="number"], +.home-form input[type="search"], +.home-form input[type="time"], +.home-form input[type="url"], +.home-form input[type="password"], +.home-form textarea, +.home-form select +{ + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + outline: none; + padding: 7px; + border: none; + border-bottom: 1px solid #ddd; + background: transparent; + margin-bottom: 10px; + height: 45px; +} + +.home-form input[type="submit"] { + color:#555; +} + @media (max-width: 800px), (max-device-width: 800px) { .pform { padding: 10px; margin:0px 20px 20px 20px; width:auto; } } diff --git a/neoexchange/ingest/static/ingest/images/favicon.ico b/neoexchange/ingest/static/ingest/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7a851a5e74ececf101594f6863276cc6896eb58b GIT binary patch literal 2238 zcmeHHYgklO6kR7T31*Z+s6d6$11gd1p=AY@f|8lq_`tx5Fdw9VMNyejsR-r>NI{_@ zaRI?bUYDz%jtK%o2*EcRf|#J7DMB;|0-?=m-vN{~|Mk_M*1h{Y_S$=$bM`&o1sFXJ z4lrS-IbcI0Co%^zcQOWY62b&icSFND2X}J=v_|6UG3Wa^h>B{4F)K3DmAXoF2L-{=u@wCHajeTNz|y5H$jNEP$&;6H+SV5K*{6}4n~Q2gHCE~k z;QRK$Cd~%5v{pn!Hn_JpTn=-L9eWnqR@%e*_1J%8Ka6x3JG)=7q<9HhTU$|9){WXu zjtjA|NKV#bbv?(3Vvf4HGvKbS;H9NVOH0F#wYA`umQbmhaPZ(k@N?&2vBLs>em^56 zr5y%mXDqL!bL`%YkdR-|*wKhKV;d$P?gS@o?&SsE(E;CTjzbj{xV&l=nwzhHFJ6q{ z!%ySVr9ZH-ful1u6%#9r(CKs-KmHts9^x=JKLsD33#h57fnKjiXGas*Hio+9I<%?P zs4DvbNl8h_Y2lQo1)nehEnPh*ZsX{nH7~}+Vf5%m?4ufSYil?=Q(b4y1h1?_U|VO3)OT?-{?>?YqY-9i_28bKsH&=hwP+2mBOHfy#aK|qVIYkPs^NhH2arhjXz$*= z$fG@_q@*A#D+?9{9QqPH_@qg&EaI3wy9vCh355j(a4q9NEMRCgAcE?xE7F16+r!1> z4BFeq~fSASDo<~C1Iz^vtnoo@~xC&AIoJ3eFg_3(z>I5Ppuab;K zf3-)Frup3>NshGssPGu#oT9Gj2CEsZsGQd ze}KLw%}$c;czLlTO}6|r=bd@;VqzXj)AVOkpB^+=IsfNNSfY8j31$Q28CyUx?|Uz& zHrCzUebapRRdW?rFQOJMTsYC@?yzO|+!+w?_M@WsTI}!(6JKh36T2HNb+YWqG=qO_5zp^i|$DKp').addClass('ui-widget selectable-deck selectable-deck-' + style); + if (style === 'bottom' || style === 'bottom-inline') { + $(this.element).after(this.deck); + } else { + $(this.element).before(this.deck); + } + $(self.hiddenMultipleSelector).each(function (i, input) { + self._addDeckItem(input); + }); + }, + + _addDeckItem: function (input) { + /* Add new deck list item from a given hidden input */ + var self = this; + var li = $('
  • ') + .text($(input).attr('title')) + .addClass('selectable-deck-item'); + var item = {element: self.element, input: input, wrapper: li, deck: self.deck}; + if (self._trigger("add", null, item) === false) { + input.remove(); + } else { + var button = $('
    ') + .addClass('selectable-deck-remove') + .append( + $('') + .attr('href', '#') + .button({ + icons: { + primary: self.options.removeIcon + }, + text: false + }) + .click(function (e) { + e.preventDefault(); + if (self._trigger("remove", e, item) !== false) { + $(input).remove(); + li.remove(); + } + }) + ); + li.append(button).appendTo(this.deck); + } + }, + + select: function (item, event) { + /* Trigger selection of a given item. + Item should contain two properties: id and value + Event is the original select event if there is one. + Event should not be passed if triggered manually. + */ + var self = this, + input = this.element; + $(input).removeClass('ui-state-error'); + if (item) { + if (self.allowMultiple) { + $(input).val(""); + $(input).data("autocomplete").term = ""; + if ($(self.hiddenMultipleSelector + '[value="' + item.id + '"]').length === 0) { + var newInput = $('', { + 'type': 'hidden', + 'name': self.hiddenName, + 'value': item.id, + 'title': item.value, + 'data-selectable-type': 'hidden-multiple' + }); + $(input).after(newInput); + self._addDeckItem(newInput); + } + return false; + } else { + $(input).val(item.value); + var ui = {item: item}; + if (typeof(event) === 'undefined' || event.type !== "autocompleteselect") { + $(input).trigger('autocompleteselect', [ui ]); + } + } + } + }, + + _create: function () { + /* Initialize a new selectable widget */ + var self = this, + input = this.element, + data = $(input).data(); + self.allowNew = data.selectableAllowNew || data['selectable-allow-new']; + self.allowMultiple = data.selectableMultiple || data['selectable-multiple']; + self.textName = $(input).attr('name'); + self.hiddenName = self.textName.replace('_0', '_1'); + self.hiddenSelector = ':input[data-selectable-type=hidden][name=' + self.hiddenName + ']'; + self.hiddenMultipleSelector = ':input[data-selectable-type=hidden-multiple][name=' + self.hiddenName + ']'; + if (self.allowMultiple) { + self.allowNew = false; + $(input).val(""); + this._initDeck(); + } + + function dataSource(request, response) { + /* Custom data source to uses the lookup url with pagination + Adds hook for adjusting query parameters. + Includes timestamp to prevent browser caching the lookup. */ + var url = data.selectableUrl || data['selectable-url']; + var now = new Date().getTime(); + var query = {term: request.term, timestamp: now}; + if (self.options.prepareQuery) { + self.options.prepareQuery(query); + } + var page = $(input).data("page"); + if (page) { + query.page = page; + } + function unwrapResponse(data) { + var results = data.data; + var meta = data.meta; + if (meta.next_page && meta.more) { + results.push({ + id: '', + value: '', + label: meta.more, + page: meta.next_page + }); + } + return response(results); + } + $.getJSON(url, query, unwrapResponse); + } + // Create base auto-complete lookup + $(input).autocomplete({ + source: dataSource, + change: function (event, ui) { + $(input).removeClass('ui-state-error'); + if ($(input).val() && !ui.item) { + if (!self.allowNew) { + $(input).addClass('ui-state-error'); + } + } + if (self.allowMultiple && !$(input).hasClass('ui-state-error')) { + $(input).val(""); + $(input).data("autocomplete").term = ""; + } + }, + select: function (event, ui) { + $(input).removeClass('ui-state-error'); + if (ui.item && ui.item.page) { + // Set current page value + $(input).data("page", ui.item.page); + $('.selectable-paginator', self.menu).remove(); + // Search for next page of results + $(input).autocomplete("search"); + return false; + } + return self.select(ui.item, event); + } + }).addClass("ui-widget ui-widget-content ui-corner-all"); + // Override the default auto-complete render. + $(input).data("autocomplete")._renderItem = function (ul, item) { + /* Adds hook for additional formatting, allows HTML in the label, + highlights term matches and handles pagination. */ + var label = item.label; + if (self.options.formatLabel) { + label = self.options.formatLabel(label, item); + } + if (self.options.highlightMatch && this.term) { + var re = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + + $.ui.autocomplete.escapeRegex(this.term) + + ")(?![^<>]*>)(?![^&;]+;)", "gi"); + label = label.replace(re, "$1"); + } + var li = $("
  • ") + .data("item.autocomplete", item) + .append($("").append(label)) + .appendTo(ul); + if (item.page) { + li.addClass('selectable-paginator'); + } + return li; + }; + // Override the default auto-complete suggest. + $(input).data("autocomplete")._suggest = function (items) { + /* Needed for handling pagination links */ + var page = $(input).data('page'); + var ul = this.menu.element; + if (!page) { + ul.empty(); + } + $(input).data('page', null); + ul.zIndex(this.element.zIndex() + 1); + this._renderMenu(ul, items); + // jQuery UI menu does not define deactivate + if (this.menu.deactivate) this.menu.deactivate(); + this.menu.refresh(); + // size and position menu + ul.show(); + this._resizeMenu(); + ul.position($.extend({of: this.element}, this.options.position)); + if (this.options.autoFocus) { + this.menu.next(new $.Event("mouseover")); + } + }; + // Additional work for combobox widgets + var selectableType = data.selectableType || data['selectable-type']; + if (selectableType === 'combobox') { + // Change auto-complete options + $(input).autocomplete("option", { + delay: 0, + minLength: 0 + }) + .removeClass("ui-corner-all") + .addClass("ui-corner-left ui-combo-input"); + // Add show all items button + $("").attr("tabIndex", -1).attr("title", "Show All Items") + .insertAfter($(input)) + .button({ + icons: { + primary: self.options.comboboxIcon + }, + text: false + }) + .removeClass("ui-corner-all") + .addClass("ui-corner-right ui-button-icon ui-combo-button") + .click(function () { + // close if already visible + if ($(input).autocomplete("widget").is(":visible")) { + $(input).autocomplete("close"); + return false; + } + // pass empty string as value to search for, displaying all results + $(input).autocomplete("search", ""); + $(input).focus(); + return false; + }); + } + } + }); + + window.bindSelectables = function (context) { + /* Bind all selectable widgets in a given context. + Automatically called on document.ready. + Additional calls can be made for dynamically added widgets. + */ + $(":input[data-selectable-type=text]", context).djselectable(); + $(":input[data-selectable-type=combobox]", context).djselectable(); + $(":input[data-selectable-type=hidden]", context).each(function (i, elem) { + var hiddenName = $(elem).attr('name'); + var textName = hiddenName.replace('_1', '_0'); + $(":input[name=" + textName + "][data-selectable-url]").bind( + "autocompletechange autocompleteselect", + function (event, ui) { + if (ui.item && ui.item.id) { + $(elem).val(ui.item.id); + } else { + $(elem).val(""); + } + } + ); + }); + }; + + /* Monkey-patch Django's dynamic formset, if defined */ + if (typeof(django) !== "undefined" && typeof(django.jQuery) !== "undefined") { + if (django.jQuery.fn.formset) { + var oldformset = django.jQuery.fn.formset; + django.jQuery.fn.formset = function (opts) { + var options = $.extend({}, opts); + var addedevent = function (row) { + bindSelectables($(row)); + }; + var added = null; + if (options.added) { + // Wrap previous added function and include call to bindSelectables + var oldadded = options.added; + added = function (row) { oldadded(row); addedevent(row); }; + } + options.added = added || addedevent; + return oldformset.call(this, options); + }; + } + } + + /* Monkey-patch Django's dismissAddAnotherPopup(), if defined */ + if (typeof(dismissAddAnotherPopup) !== "undefined" && + typeof(windowname_to_id) !== "undefined" && + typeof(html_unescape) !== "undefined") { + var django_dismissAddAnotherPopup = dismissAddAnotherPopup; + dismissAddAnotherPopup = function (win, newId, newRepr) { + /* See if the popup came from a selectable field. + If not, pass control to Django's code. + If so, handle it. */ + var fieldName = windowname_to_id(win.name); /* e.g. "id_fieldname" */ + var field = $('#' + fieldName); + var multiField = $('#' + fieldName + '_0'); + /* Check for bound selectable */ + var singleWidget = field.data('djselectable'); + var multiWidget = multiField.data('djselectable'); + if (singleWidget || multiWidget) { + // newId and newRepr are expected to have previously been escaped by + // django.utils.html.escape. + var item = { + id: html_unescape(newId), + value: html_unescape(newRepr) + }; + if (singleWidget) { + field.djselectable('select', item); + } + if (multiWidget) { + multiField.djselectable('select', item); + } + win.close(); + } else { + /* Not ours, pass on to original function. */ + return django_dismissAddAnotherPopup(win, newId, newRepr); + } + }; + } + + $(document).ready(function () { + // Bind existing widgets on document ready + if (typeof(djselectableAutoLoad) === "undefined" || djselectableAutoLoad) { + bindSelectables('body'); + } + }); +})(jQuery); diff --git a/neoexchange/ingest/templates/ingest/base.html b/neoexchange/ingest/templates/ingest/base.html index 8264f4e00..887cbcebf 100644 --- a/neoexchange/ingest/templates/ingest/base.html +++ b/neoexchange/ingest/templates/ingest/base.html @@ -11,7 +11,7 @@ {% block meta %}{% endblock %} {% block favicon %} - + {% endblock %} {% block css-content %}{% endblock %} @@ -22,7 +22,7 @@ - + {% block script-content %}{% endblock %} diff --git a/neoexchange/ingest/templates/ingest/body_list.html b/neoexchange/ingest/templates/ingest/body_list.html index 3eda0919a..7606c2fff 100644 --- a/neoexchange/ingest/templates/ingest/body_list.html +++ b/neoexchange/ingest/templates/ingest/body_list.html @@ -1,6 +1,6 @@ {% extends 'ingest/base.html' %} {% load url from future %} -{% block header %}NEOx home{% endblock %} +{% block header %}NEOx{% endblock %} {% block bodyclass %}page{% endblock %} {% block extramenu %} diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index edc285c07..2aeb29612 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -1,34 +1,22 @@ {% extends 'ingest/base.html' %} {% load url from future %} -{% block header %}NEOexchange{% endblock %} +{% block header %}NEOx{% endblock %} -{% block title %}Current Targets{% endblock %} {% block bodyclass %}page{% endblock %} -{% block extramenucss %}extramenu-home{% endblock %} +{% block extramenucss %}extramenu{% endblock %} {% block extramenu %} -{% endblock %} +
    +

    Ephemeris for {{ new_target_name }} at {{ site_code }}

    +
    +{% endblock%} {% block main-content %} -
    -
    -
    - - - - - -
    Computing ephemeris for: {{ new_target_name }} for {{ site_code }}
    - {% if error %} -
    - {{ error }} -
    - {% endif %} -
    +
    +
    -
    @@ -69,7 +57,7 @@ {% for line in ephem_lines %} - + @@ -88,4 +76,10 @@ + {% endblock %} diff --git a/neoexchange/ingest/templates/ingest/home.html b/neoexchange/ingest/templates/ingest/home.html index 9ced2f377..c41885384 100644 --- a/neoexchange/ingest/templates/ingest/home.html +++ b/neoexchange/ingest/templates/ingest/home.html @@ -1,5 +1,9 @@ {% extends 'ingest/base.html' %} {% load url from future %} +{% load staticfiles %} + +{% block css-content %}{% endblock %} + {% block header %}NEOx home{% endblock %} {% block bodyclass %}page{% endblock %} @@ -11,6 +15,25 @@ {% block main-content %}
    +
    +
    + + +
    + {{ form.utc_date }} + {{ form.site_code }} +
    +
    + {{ form.alt_limit }} + +
    + {% for key, error in form.errors.items %} +
    + {{ error }} +
    + {% endfor %} + +
    {{targets}}
    active targets
    @@ -19,10 +42,6 @@
    {{blocks}}
    active blocks
    -
    -
    {{latest.ingest|date:"d M Y"}}
    -
    latest ingest
    -
    @@ -61,24 +80,5 @@
    {{ line.0 }} {{ line.1 }} {{ line.2 }}
    -
    - - - - - -
    - {% if error %} -
    - {{ error }} -
    - {% endif %}
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/neoexchange/ingest/tests.py b/neoexchange/ingest/tests.py deleted file mode 100644 index 1b20df460..000000000 --- a/neoexchange/ingest/tests.py +++ /dev/null @@ -1,272 +0,0 @@ -''' -NEO exchange: NEO observing portal for Las Cumbres Observatory Global Telescope Network -Copyright (C) 2014-2015 LCOGT - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -''' - -from nose.tools import eq_, assert_equal, assert_almost_equal, raises, nottest - -#Import module to test -from ast_subs import * -from sources_subs import parse_goldstone_chunks - -class TestIntToMutantHexChar(object): - '''Unit tests for the int_to_mutant_hex_char() method''' - -# EricS says to run without the exception handler in place first to check it works as expected -# This is the simple test version where we only care if we get an exception (any exception) -# and are not bothered what the specific message/failure is -# '@raises' is a "decorator" - -# @raises(MutantError) -# def test_bad_mutantcode_length(self): -# int_to_mutant_hex_char(-1) - - def test_bad_mutant_too_small(self): - try: - int_to_mutant_hex_char(-1) - assert False - except MutantError as e: - expected_msg = ("Number out of range 0...61") - assert_equal(e.__str__(), expected_msg) - - def test_bad_mutant_too_large(self): - try: - int_to_mutant_hex_char(62) - assert False - except MutantError as e: - expected_msg = ("Number out of range 0...61") - assert_equal(e.__str__(), expected_msg) - - def test_bad_mutant_not_number1(self): - try: - int_to_mutant_hex_char('9') - assert False - except MutantError as e: - expected_msg = ("Number out of range 0...61") - assert_equal(e.__str__(), expected_msg) - - def test_bad_mutant_not_number2(self): - try: - int_to_mutant_hex_char('FOO') - assert False - except MutantError as e: - expected_msg = ("Number out of range 0...61") - assert_equal(e.__str__(), expected_msg) - - def test_num_less_than_ten(self): - char = int_to_mutant_hex_char(8) - expected_char = '8' - assert_equal(char, expected_char) - - def test_num_less_than_thirtysix_t1(self): - char = int_to_mutant_hex_char(10) - expected_char = 'A' - assert_equal(char, expected_char) - - def test_num_less_than_thirtysix_t2(self): - char = int_to_mutant_hex_char(23) - expected_char = 'N' - assert_equal(char, expected_char) - - def test_num_less_than_thirtysix_t3(self): - char = int_to_mutant_hex_char(34) - expected_char = 'Y' - assert_equal(char, expected_char) - - def test_num_less_than_thirtysix_t4(self): - char = int_to_mutant_hex_char(35) - expected_char = 'Z' - assert_equal(char, expected_char) - - def test_num_greater_than_thirtysix_t1(self): - char = int_to_mutant_hex_char(36) - expected_char = 'a' - assert_equal(char, expected_char) - - def test_num_greater_than_thirtysix_t2(self): - char = int_to_mutant_hex_char(49) - expected_char = 'n' - assert_equal(char, expected_char) - - def test_num_greater_than_thirtysix_t3(self): - char = int_to_mutant_hex_char(60) - expected_char = 'y' - assert_equal(char, expected_char) - - def test_num_greater_than_thirtysix_t4(self): - char = int_to_mutant_hex_char(61) - expected_char = 'z' - assert_equal(char, expected_char) - - -class TestNormalToPacked(object): - '''Unit tests for normal_to_packed() method''' - - def test_number_t0(self): - expected_desig = '00001 ' - packed_desig, ret_code = normal_to_packed('1') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_number_t1(self): - expected_desig = '00719 ' - packed_desig, ret_code = normal_to_packed('719') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_number_t2(self): - expected_desig = 'B7317 ' - packed_desig, ret_code = normal_to_packed('117317') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_number_t3(self): - expected_desig = 'Z7317 ' - packed_desig, ret_code = normal_to_packed('357317') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_number_t4(self): - expected_desig = 'a7317 ' - packed_desig, ret_code = normal_to_packed('367317') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_number_t5(self): - expected_desig = 'g1234 ' - packed_desig, ret_code = normal_to_packed('421234') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_comet_t1(self): - expected_desig = ' CK13A010' - packed_desig, ret_code = normal_to_packed('C/2013 A1') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_comet_t2(self): - expected_desig = ' PK01T000' - packed_desig, ret_code = normal_to_packed('P/2001 T') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_comet_t3(self): - expected_desig = '0004P ' - packed_desig, ret_code = normal_to_packed('P/4') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_comet_t4(self): - expected_desig = '0314P ' - packed_desig, ret_code = normal_to_packed('P/314') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_comet_t5(self): - expected_desig = ' CJ83Z150' - packed_desig, ret_code = normal_to_packed('C/1983 Z15') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_provdesig_t1(self): - expected_desig = ' K15D00D' - packed_desig, ret_code = normal_to_packed('2015 DD') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_provdesig_t2(self): - expected_desig = ' J99Z01A' - packed_desig, ret_code = normal_to_packed('1999 ZA1') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_provdesig_t3(self): - expected_desig = ' J99Z11A' - packed_desig, ret_code = normal_to_packed('1999 ZA11') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_provdesig_t4(self): - expected_desig = ' K00LA0W' - packed_desig, ret_code = normal_to_packed('2000 LW100') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_provdesig_t5(self): - expected_desig = ' K10LZ9W' - packed_desig, ret_code = normal_to_packed('2010 LW359') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_provdesig_t6(self): - expected_desig = ' K00La0W' - packed_desig, ret_code = normal_to_packed('2000 LW360') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_provdesig_t7(self): - expected_desig = ' K00Az9Z' - packed_desig, ret_code = normal_to_packed('2000 AZ619') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, 0) - - def test_baddesig_t1(self): - expected_desig = ' ' - packed_desig, ret_code = normal_to_packed('FOO BAR') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, -1) - - def test_baddesig_t1(self): - expected_desig = ' ' - packed_desig, ret_code = normal_to_packed('12345 A') - assert_equal(packed_desig, expected_desig) - assert_equal(ret_code, -1) - -class TestGoldstoneChunkParser(object): - '''Unit tests for the sources_subs.parse_goldstone_chunks() method''' - - def test_specficdate_provis_desig(self): - expected_objid = '2015 FW117' - chunks = [u'2015', u'Apr', u'1', u'2015', u'FW117', u'Yes', u'Yes', u'Scheduled'] - obj_id = parse_goldstone_chunks(chunks) - assert_equal(expected_objid, obj_id) - - def test_specficdate_astnumber(self): - expected_objid = '285331' - chunks = [u'2015', u'May', u'16-17', u'285331', u'1999', u'FN53', u'No', u'Yes', u'R'] - obj_id = parse_goldstone_chunks(chunks) - assert_equal(expected_objid, obj_id) - - def test_unspecificdate_provis_desig(self): - expected_objid = '2010 NY65' - chunks = [u'2015', u'Jun', u'2010', u'NY65', u'No', u'Yes', u'R', u'PHA'] - obj_id = parse_goldstone_chunks(chunks) - assert_equal(expected_objid, obj_id) - - def test_unspecificdate_astnumber(self): - expected_objid = '385186' - chunks = [u'2015', u'Jul', u'385186', u'1994', u'AW1', u'No', u'Yes', u'PHA', u'BINARY', u'(not', u'previously', u'observed', u'with', u'radar)'] - obj_id = parse_goldstone_chunks(chunks) - assert_equal(expected_objid, obj_id) - - def test_specficdate_named_ast(self): - expected_objid = '1566' # '(1566) Icarus' - chunks = [u'2015', u'Jun', u'13-17', u'1566', u'Icarus', u'No', u'Yes', u'R', u'PHA', u'June', u'13/14,', u'14/15,', u'and', u'16/17'] - obj_id = parse_goldstone_chunks(chunks) - assert_equal(expected_objid, obj_id) - - def test_unspecficdate_named_ast(self): - expected_objid = '1685' # '(1685) Toro' - chunks = ['2016', 'Jan', '1685', 'Toro', 'No', 'No', 'R'] - obj_id = parse_goldstone_chunks(chunks) - assert_equal(expected_objid, obj_id) diff --git a/neoexchange/ingest/tests/__init__.py b/neoexchange/ingest/tests/__init__.py index d8090a0f9..5093a70d7 100644 --- a/neoexchange/ingest/tests/__init__.py +++ b/neoexchange/ingest/tests/__init__.py @@ -1,4 +1,5 @@ from test_ast_subs import * from test_ephem_subs import * +from test_forms import * from test_source_subs import * from test_views import * diff --git a/neoexchange/ingest/tests/test_forms.py b/neoexchange/ingest/tests/test_forms.py new file mode 100644 index 000000000..4d8bbd0fa --- /dev/null +++ b/neoexchange/ingest/tests/test_forms.py @@ -0,0 +1,68 @@ +''' +NEO exchange: NEO observing portal for Las Cumbres Observatory Global Telescope Network +Copyright (C) 2014-2015 LCOGT + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +''' + +from django.test import TestCase +from ingest.models import Body + +#Import module to test +from ingest.forms import EphemQuery + +class EphemQueryFormTest(TestCase): + + def setUp(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body = Body.objects.create(**params) + self.body.save() + + def test_form_has_label(self): + form = EphemQuery() + self.assertIn('Enter target name...', form.as_p()) + self.assertIn('Site code:', form.as_p()) + + def test_form_validation_for_blank_target(self): + form = EphemQuery(data = {'target' : ''}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['target'], + ['Target name is required'] + ) + + def test_form_validation_for_blank_date(self): + form = EphemQuery(data = {'utc_date' : ''}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['utc_date'], + ['UTC date is required'] + ) + + def test_form_handles_save(self): + form = EphemQuery(data = {'target' : 'N999r0q', + 'utc_date' : '2015-04-20', + 'site_code' : 'K92', + 'alt_limit' : 30.0 + }) + self.assertTrue(form.is_valid()) diff --git a/neoexchange/ingest/tests/test_views.py b/neoexchange/ingest/tests/test_views.py index 43caf867f..c7c987660 100644 --- a/neoexchange/ingest/tests/test_views.py +++ b/neoexchange/ingest/tests/test_views.py @@ -27,6 +27,7 @@ from ingest.ephem_subs import call_compute_ephem, determine_darkness_times from ingest.views import home, clean_NEOCP_object from ingest.models import Body +from ingest.forms import EphemQuery class TestClean_NEOCP_Object(TestCase): @@ -82,28 +83,33 @@ def test_missing_absmag(self): class HomePageTest(TestCase): - def test_root_url_resolves_to_home_page_view(self): - found = resolve('/') - self.assertEqual(found.func, home) - - def test_home_page_returns_correct_html(self): - request = HttpRequest() - response = home(request) - expected_html = render_to_string('ingest/home.html') - self.assertEqual(response.content.decode(), expected_html) - - def test_home_page_redirects_after_GET(self): - request = HttpRequest() - request.method = 'GET' - request.GET['target_name'] = 'New target' + def setUp(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body = Body.objects.create(**params) + self.body.save() - response = home(request) + def test_home_page_renders_home_template(self): + response = self.client.get('/') + self.assertTemplateUsed(response, 'ingest/home.html') - self.assertEqual(response.status_code, 302) - self.assertEqual(response['location'], '/ephemeris/') + def test_home_page_uses_ephemquery_form(self): + response = self.client.get('/') + self.assertIsInstance(response.context['form'], EphemQuery) - def test_home_page_ephem_form_shows_current_date(self): - pass class EphemPageTest(TestCase): maxDiff = None @@ -134,41 +140,58 @@ def test_home_page_can_save_a_GET_request(self): dark_start, dark_end = determine_darkness_times(site_code, utc_date ) response = self.client.get('/ephemeris/', - data={'target_name' : 'N999r0q', - 'site_code' : site_code, - 'utc_date' : '2015-04-21'} + data={'target' : 'N999r0q', + 'site_code' : site_code, + 'utc_date' : '2015-04-21', + 'alt_limit' : 0} ) self.assertIn('N999r0q', response.content.decode()) body_elements = model_to_dict(self.body) ephem_lines = call_compute_ephem(body_elements, dark_start, dark_end, site_code, '5m' ) expected_html = render_to_string( 'ingest/ephem.html', - {'new_target_name' : 'N999r0q', + {'new_target_name' : 'N999r0q', 'ephem_lines' : ephem_lines, 'site_code' : site_code } ) self.assertMultiLineEqual(response.content.decode(), expected_html) def test_displays_ephem(self): - response = self.client.get('/ephemeris/', data={'target_name' : 'N999r0q'}) - self.assertContains(response, 'Computing ephemeris for') + response = self.client.get('/ephemeris/', + data ={'target' : 'N999r0q', + 'utc_date' : '2015-05-11', + 'site_code' : 'V37', + 'alt_limit' : 30.0 + } + ) + self.assertContains(response, 'Ephemeris for') def test_uses_ephem_template(self): - response = self.client.get('/ephemeris/', data={'target_name' : 'N999r0q'}) + response = self.client.get('/ephemeris/', + data = {'target' : 'N999r0q', + 'site_code' : 'W86', + 'utc_date' : '2015-04-20', + 'alt_limit' : 40.0 + } + ) self.assertTemplateUsed(response, 'ingest/ephem.html') def test_form_errors_are_sent_back_to_home_page(self): - response = self.client.get('/ephemeris/', data={'target_name' : ''}) + response = self.client.get('/ephemeris/', data={'target' : ''}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'ingest/home.html') - expected_error = escape("You didn't specify a target") + expected_error = escape("Target name is required") self.assertContains(response, expected_error) def test_ephem_page_displays_site_code(self): - response = self.client.get('/ephemeris/', - data={'target_name' : 'N999r0q', 'site_code' : 'F65'}) - self.assertContains(response, - 'Computing ephemeris for: N999r0q for F65') + response = self.client.get('/ephemeris/', + data = {'target' : 'N999r0q', + 'site_code' : 'F65', + 'utc_date' : '2015-04-20', + 'alt_limit' : 30.0 + } + ) + self.assertContains(response, 'Ephemeris for N999r0q at F65') class TargetsPageTest(TestCase): diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index 39dc60a4a..af2e12759 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -14,16 +14,18 @@ ''' from datetime import datetime +from django.db.models import Q from django.forms.models import model_to_dict from django.shortcuts import render from django.views.generic import DetailView, ListView +from ingest.ephem_subs import call_compute_ephem, determine_darkness_times +from ingest.forms import EphemQuery from ingest.models import * from ingest.sources_subs import fetchpage_and_make_soup, packed_to_normal, fetch_mpcorbit from ingest.time_subs import extract_mpc_epoch, parse_neocp_date -from ingest.ephem_subs import call_compute_ephem, determine_darkness_times import logging import reversion -from django.db.models import Q + logger = logging.getLogger(__name__) @@ -31,7 +33,8 @@ def home(request): params = { 'targets' : Body.objects.filter(active=True).count(), 'blocks' : Block.objects.filter(active=True).count(), - 'latest' : Body.objects.latest('ingest') + 'latest' : Body.objects.latest('ingest'), + 'form' : EphemQuery() } return render(request,'ingest/home.html',params) @@ -65,44 +68,21 @@ def get_queryset(self): def ephemeris(request): - name = request.GET.get('target_name', '') + form = EphemQuery(request.GET) ephem_lines = [] - site_code = '' - error = '' - if name != '': - try: - body = Body.objects.get(provisional_name = name) - body_elements = model_to_dict(body) - try: - site_code = request.GET['site_code'] - except KeyError: - error = "Error ! You didn't specify a site" - utc_date_str = request.GET.get('utc_date', '') - if utc_date_str != '': - try: - utc_date = datetime.strptime(utc_date_str.strip(), '%Y-%m-%d') - except ValueError: - utc_date = datetime(2015, 4, 21, 3,0,0) - else: - utc_date = datetime.utcnow() - - alt_limit = request.GET.get('alt_limit', 0.0) - dark_start, dark_end = determine_darkness_times(site_code, utc_date ) - ephem_lines = call_compute_ephem(body_elements, dark_start, dark_end, site_code, 300, alt_limit ) - except Body.DoesNotExist: - error = "Error ! No object found" - return render(request, 'ingest/home.html', {'error' : error}) - except Body.MultipleObjectsReturned: - error = "Error ! Multiple objects specified" + if form.is_valid(): + data = form.cleaned_data + body_elements = model_to_dict(data['target']) + dark_start, dark_end = determine_darkness_times(data['site_code'], data['utc_date']) + ephem_lines = call_compute_ephem(body_elements, dark_start, dark_end, data['site_code'], 300, data['alt_limit'] ) else: - error = "You didn't specify a target" - return render(request, 'ingest/home.html', {'error' : error}) + return render(request, 'ingest/home.html', {'form' : form}) return render(request, 'ingest/ephem.html', - {'new_target_name' : name, + {'new_target_name' : form['target'].value, 'ephem_lines' : ephem_lines, - 'site_code' : site_code, - 'error' : error } + 'site_code' : form['site_code'].value(), + } ) def save_and_make_revision(body,kwargs): @@ -236,12 +216,25 @@ def update_crossids(astobj, dbg=False): def clean_crossid(astobj, dbg=False): + '''Takes an (a list of new designation, provisional designation, + reference and confirm date produced from the MPC's Previous NEOCP Objects + page) and determines the type and whether it should still be followed. + + Objects that were not confirmed, did not exist or "were not interesting + (normally a satellite) are set inactive immediately. For NEOs and comets, + we set it to inactive if more than 3 days have passed since the + confirmation date''' + + interesting_cutoff = 3 * 86400 # 3 days in seconds confirm_date = parse_neocp_date(astobj[3]) obj_id = astobj[0].rstrip() desig = astobj[1] reference = astobj[2] + time_from_confirm = datetime.utcnow() - confirm_date + time_from_confirm = time_from_confirm.total_seconds() + active = True if obj_id != '' and desig == 'wasnotconfirmed': # Unconfirmed, no longer interesting so set inactive @@ -253,18 +246,29 @@ def clean_crossid(astobj, dbg=False): objtype = 'X' desig = '' active = False + elif obj_id != '' and desig == '' and reference == '': + # "Was not interesting" (normally a satellite), no longer interesting + # so set inactive + objtype = 'W' + desig = '' + active = False elif obj_id != '' and desig != '': # Confirmed if 'CBET' in reference: # There is a reference to an CBET so we assume it's "very # interesting" i.e. a comet objtype = 'C' + if time_from_confirm > interesting_cutoff: + active = False elif 'MPEC' in reference: # There is a reference to an MPEC so we assume it's # "interesting" i.e. an NEO objtype = 'N' + if time_from_confirm > interesting_cutoff: + active = False else: objtype = 'A' + active = False params = { 'source_type' : objtype, 'name' : desig, diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 16b16ff29..067c6bdfe 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Django settings for neox project. import os, sys @@ -137,11 +138,6 @@ if "local_settings" not in str(e): raise e -# 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. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. LOGGING = { 'version': 1, 'disable_existing_loggers': True, @@ -189,7 +185,7 @@ 'level':'DEBUG', }, 'ingest' : { - 'handlers' : ['file'], + 'handlers' : ['file','console'], 'level' : 'DEBUG', } } diff --git a/neoexchange/neox/tests/base.py b/neoexchange/neox/tests/base.py index d716aa55e..247373cad 100644 --- a/neoexchange/neox/tests/base.py +++ b/neoexchange/neox/tests/base.py @@ -1,11 +1,32 @@ from django.test import LiveServerTestCase from selenium import webdriver +from ingest.models import Body class FunctionalTest(LiveServerTestCase): + + def insert_test_body(self): + params = { 'provisional_name' : 'N999r0q', + 'abs_mag' : 21.0, + 'slope' : 0.15, + 'epochofel' : '2015-03-19 00:00:00', + 'meananom' : 325.2636, + 'argofperih' : 85.19251, + 'longascnode' : 147.81325, + 'orbinc' : 8.34739, + 'eccentricity' : 0.1896865, + 'meandist' : 1.2176312, + 'source_type' : 'U', + 'elements_type' : 'MPC_MINOR_PLANET', + 'active' : True, + 'origin' : 'M', + } + self.body, created = Body.objects.get_or_create(**params) + def setUp(self): self.browser = webdriver.Firefox() self.browser.implicitly_wait(5) + self.insert_test_body() def tearDown(self): self.browser.refresh() @@ -25,3 +46,8 @@ def check_for_header_in_table(self, table_id, header_text): def get_item_input_box(self, element_id='id_target'): return self.browser.find_element_by_id(element_id) + + def get_item_input_box_and_clear(self, element_id='id_target'): + inputbox = self.browser.find_element_by_id(element_id) + inputbox.clear() + return inputbox diff --git a/neoexchange/neox/tests/test_ephemeris_creation.py b/neoexchange/neox/tests/test_ephemeris_creation.py index 7b651ab9f..cf81bd944 100644 --- a/neoexchange/neox/tests/test_ephemeris_creation.py +++ b/neoexchange/neox/tests/test_ephemeris_creation.py @@ -28,8 +28,6 @@ def insert_test_body(self): def test_can_compute_ephemeris(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -37,9 +35,9 @@ def test_can_compute_ephemeris(self): # He notices the page title has the name of the site and the header # mentions current targets - self.assertIn('NEOexchange', self.browser.title) - header_text = self.browser.find_element_by_tag_name('h1').text - self.assertIn('Current Targets', header_text) + self.assertIn('NEOx home | LCOGT', self.browser.title) + header_text = self.browser.find_element_by_class_name('masthead').text + self.assertIn('active targets', header_text) # He notices there are several targets that could be followed up self.check_for_header_in_table('id_neo_targets', @@ -53,13 +51,13 @@ def test_can_compute_ephemeris(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) inputbox.send_keys('N999r0q') - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box_and_clear('id_utc_date') datebox.send_keys('2015-04-21') # When he hits Enter, he is taken to a new page and now the page shows an ephemeris @@ -69,7 +67,8 @@ def test_can_compute_ephemeris(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for V37') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at V37', menu) self.check_for_header_in_table('id_ephemeris_table', 'Date/Time (UTC) RA Dec Mag "/min Alt Moon Phase Moon Dist. Moon Alt. Score H.A.' @@ -79,16 +78,16 @@ def test_can_compute_ephemeris(self): ) # There is a button asking whether to schedule the target + link = self.browser.find_element_by_link_text('No') # He clicks 'No' and is returned to the front page - self.assertIn('NEOexchange', self.browser.title) + link.click() + self.assertIn('NEOx home | LCOGT', self.browser.title) # Satisfied, he goes back to sleep def test_can_compute_ephemeris_for_specific_site(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -98,7 +97,7 @@ def test_can_compute_ephemeris_for_specific_site(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -106,14 +105,19 @@ def test_can_compute_ephemeris_for_specific_site(self): # He notices a new selection for the site code and chooses FTN (F65) # XXX Code smell: Too many static text constants - site_choices = Select(self.browser.find_element_by_id('id_sitecode')) + site_choices = Select(self.browser.find_element_by_id('id_site_code')) self.assertIn('FTN (F65)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('FTN (F65)') - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') + datebox.clear() datebox.send_keys('2015-04-21') + altlimitbox = self.get_item_input_box('id_alt_limit') + altlimitbox.clear() + altlimitbox.send_keys('20') + # When he hits Enter, he is taken to a new page and now the page shows an ephemeris # for the target with a column header and a series of rows for the position # as a function of time. @@ -122,7 +126,8 @@ def test_can_compute_ephemeris_for_specific_site(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for F65') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at F65', menu) # Check the results for V37 are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') @@ -140,8 +145,6 @@ def test_can_compute_ephemeris_for_specific_site(self): def test_can_compute_ephemeris_for_specific_date(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -151,7 +154,7 @@ def test_can_compute_ephemeris_for_specific_date(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -159,22 +162,24 @@ def test_can_compute_ephemeris_for_specific_date(self): # He notices a new selection for the site code and chooses ELP (V37) # XXX Code smell: Too many static text constants - site_choices = Select(self.get_item_input_box('id_sitecode')) + site_choices = Select(self.get_item_input_box('id_site_code')) self.assertIn('ELP (V37)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('ELP (V37)') # He notices a new textbox for the date that is wanted which is filled # in with the current date - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') current_date = datetime.utcnow().date() current_date_str = current_date.strftime('%Y-%m-%d') self.assertEqual( - datebox.get_attribute('placeholder'), + datebox.get_attribute('value'), current_date_str ) # He decides to see where it will be on a specific date in a future + # so clears the box and put his new date in + datebox.clear() datebox.send_keys('2015-04-28') # When he hits Enter, he is taken to a new page and now the page shows an ephemeris @@ -185,7 +190,8 @@ def test_can_compute_ephemeris_for_specific_date(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for V37') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at V37', menu) # Check the results for default date are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') @@ -203,8 +209,6 @@ def test_can_compute_ephemeris_for_specific_date(self): def test_can_compute_ephemeris_for_specific_alt_limit(self): - ## Insert test body otherwise things will fail - self.insert_test_body() # Eduardo has heard about a new website for NEOs. He goes to the # homepage @@ -214,7 +218,7 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): inputbox = self.get_item_input_box() self.assertEqual( inputbox.get_attribute('placeholder'), - 'Enter a target name' + 'Enter target name...' ) # He types N999r0q into the textbox (he is most interested in NEOWISE targets) @@ -222,28 +226,29 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): # He notices a new selection for the site code and chooses CPT (K91) # XXX Code smell: Too many static text constants - site_choices = Select(self.get_item_input_box('id_sitecode')) + site_choices = Select(self.get_item_input_box('id_site_code')) self.assertIn('CPT (K91-93)', [option.text for option in site_choices.options]) site_choices.select_by_visible_text('CPT (K91-93)') # He notices a new textbox for the date that is wanted which is filled # in with the current date - datebox = self.get_item_input_box('id_date') + datebox = self.get_item_input_box('id_utc_date') current_date = datetime.utcnow().date() current_date_str = current_date.strftime('%Y-%m-%d') self.assertEqual( - datebox.get_attribute('placeholder'), + datebox.get_attribute('value'), current_date_str ) # He decides to see where it will be on a specific date in a future + datebox.clear() datebox.send_keys('2015-09-04') # He notices a new textbox for the altitude limit that is wanted, below # which he doesn't want to see ephemeris output. It is filled in with # the default value of 30.0 degrees - datebox = self.get_item_input_box('id_altlimit') + datebox = self.get_item_input_box('id_alt_limit') self.assertEqual(datebox.get_attribute('value'), str(30.0)) @@ -255,7 +260,8 @@ def test_can_compute_ephemeris_for_specific_alt_limit(self): eduardo_ephem_url = self.browser.current_url self.assertRegexpMatches(eduardo_ephem_url, '/ephemeris/.+') - self.check_for_row_in_table('id_planning_table', 'Computing ephemeris for: N999r0q for K92') + menu = self.browser.find_element_by_id('extramenu').text + self.assertIn('Ephemeris for N999r0q at K92', menu) # Check the results for default date are not in the table table = self.browser.find_element_by_id('id_ephemeris_table') diff --git a/neoexchange/neox/tests/test_ephemeris_validation.py b/neoexchange/neox/tests/test_ephemeris_validation.py index 425600925..eb38fb00d 100644 --- a/neoexchange/neox/tests/test_ephemeris_validation.py +++ b/neoexchange/neox/tests/test_ephemeris_validation.py @@ -15,4 +15,4 @@ def test_cannot_get_ephem_for_bad_objects(self): # The page refreshes and there is an error message saying that targets' # can't be blank error = self.browser.find_element_by_css_selector('.error') - self.assertEqual(error.text, "You didn't specify a target") + self.assertEqual(error.text, "Target name is required") diff --git a/neoexchange/neox/tests/test_layout_and_styling.py b/neoexchange/neox/tests/test_layout_and_styling.py index d114f79b7..6ebfd865d 100644 --- a/neoexchange/neox/tests/test_layout_and_styling.py +++ b/neoexchange/neox/tests/test_layout_and_styling.py @@ -9,8 +9,8 @@ def test_layout_and_styling(self): self.browser.set_window_size(1280, 1024) # He notices the input box is nicely centered - inputbox = self.get_item_input_box('id_date') + link = self.browser.find_element_by_partial_link_text('active targets') self.assertAlmostEqual( - inputbox.location['x'] + inputbox.size['width'] / 2, - 640, delta=7 + link.location['x'] + link.size['width'] /2 , + 640, delta=50 ) diff --git a/neoexchange/pip_requirements.txt b/neoexchange/pip_requirements.txt index 4a71c33c3..fb60fb785 100644 --- a/neoexchange/pip_requirements.txt +++ b/neoexchange/pip_requirements.txt @@ -9,10 +9,8 @@ django-reversion==1.6.6 html5lib==1.0b3 ipython==3.0.0 nose==1.3.6 -numpy==1.9.1 -pyslalib +numpy==1.9.2 pytz==2015.2 -rise_set>=0.3.10 six==1.9.0 wsgiref==0.1.2 selenium diff --git a/neoexchange/update_crossids.py b/neoexchange/update_crossids.py deleted file mode 100644 index c627f2be9..000000000 --- a/neoexchange/update_crossids.py +++ /dev/null @@ -1,84 +0,0 @@ -''' -NEO exchange: NEO observing portal for Las Cumbres Observatory Global Telescope Network -Copyright (C) 2014-2015 LCOGT - -update_crossids.py -- Fetch the MPC Previous NEO Confirmation Page and update -DB entries for their new status. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -''' - -import os -from datetime import datetime, timedelta -from ingest.sources_subs import fetch_previous_NEOCP_desigs -from ingest.time_subs import parse_neocp_date - -# Need to set this so Django can find its settings -os.environ['DJANGO_SETTINGS_MODULE'] = 'neox.settings' -from ingest.models import Body, check_object_exists - -# Fetch Previous NEOCP webpage and parse - -object_list = fetch_previous_NEOCP_desigs() - -for astobj in object_list: - - confirm_date = parse_neocp_date(astobj[3]) - - objname = astobj[0].rstrip() - desig = astobj[1] - reference = astobj[2] - print objname, desig, reference - obj_found = check_object_exists(objname) - - if obj_found !=0: - # We have the object... - print "Object found" - print str(astobj) + ' ' + str(confirm_date) - if objname != '' and desig == 'wasnotconfirmed': - # Unconfirmed - objtype = 'U' - desig = '' - elif objname != '' and desig == 'doesnotexist': - objtype = 'X' - desig = '' - elif objname != '' and desig != '': - # Confirmed - if 'CBET' in reference: - # There is a reference to an CBET so we assume it's "very - # interesting" i.e. a comet - objtype = 'C' - elif 'MPEC' in reference: - # There is a reference to an MPEC so we assume it's - # "interesting" i.e. an NEO - objtype = 'N' - else: - objtype = 'A' - print objtype - try: - crossid = Body.objects.get(provisional_name__contains=objname) - except Body.MultipleObjectsReturned: - print "Multiple cross-ids found, shouldn't happen..." - except Body.DoesNotExist: - print "Object now not found, shouldn't happen..." - else: - print "Adding cross-identification" - # Insert new cross-identification into DB - crossid.name = desig - crossid.source_type = objtype - - # XXX We currently have nowhere to store the reference (e.g. MPEC) - # or the confirm date but we probably should... - ## crossid.reference = reference, - ## crossid.confirm_date = confirm_date - crossid.save() - else: - print "Object not found" From b1794cec90eb89e1eeb2d135ee0575c22add65d6 Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Wed, 6 May 2015 17:06:56 +0100 Subject: [PATCH 18/48] Refactoring scheduling code to get it to run --- neoexchange/ingest/forms.py | 12 ++- ...eld_proposal_pi__add_field_proposal_tag.py | 90 ++++++++++++++++ neoexchange/ingest/models.py | 2 + .../ingest/static/ingest/css/forms.css | 12 +++ neoexchange/ingest/templates/ingest/base.html | 2 +- .../ingest/templates/ingest/block_list.html | 0 .../ingest/templates/ingest/body_detail.html | 31 +++++- .../ingest/templates/ingest/body_list.html | 2 +- .../ingest/templates/ingest/ephem.html | 13 ++- neoexchange/ingest/templates/ingest/home.html | 26 ++--- neoexchange/ingest/views.py | 102 ++++++++++++++---- neoexchange/neox/settings.py | 6 +- neoexchange/neox/urls.py | 9 +- 13 files changed, 255 insertions(+), 52 deletions(-) create mode 100644 neoexchange/ingest/migrations/0004_auto__add_field_proposal_pi__add_field_proposal_tag.py create mode 100644 neoexchange/ingest/templates/ingest/block_list.html diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index e0cab01fd..8a5c9f7d1 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -24,11 +24,17 @@ def clean_target(self): class ScheduleForm(forms.Form): SITES = (('V37','ELP (V37)'),('F65','FTN (F65)'),('E10', 'FTS (E10)'),('W86','LSC (W85-87)'),('K92','CPT (K91-93)'),('Q63','COJ (Q63-64)')) proposals = Proposal.objects.all() - proposal_choices = [] - for proposal in proposals.values(): - proposal_choices.append((str(proposal['code']), str(proposal['title']))) + proposal_choices = [(str(proposal.code), str(proposal.title)) for proposal in proposals] proposal_code = forms.ChoiceField(required=True, choices=proposal_choices) site_code = forms.ChoiceField(required=True, choices=SITES) utc_date = forms.DateField(input_formats=['%Y-%m-%d',], initial=datetime.utcnow().date(), required=True, widget=forms.TextInput(attrs={'size':'10'}), error_messages={'required': _(u'UTC date is required')}) body_id = forms.IntegerField(widget=forms.HiddenInput()) ok_to_schedule = forms.BooleanField(initial=False, required=False, widget=forms.HiddenInput()) + + def clean_body_id(self): + body = Body.objects.filter(pk=self.cleaned_data['body_id']) + if body.count() == 1 : + return body[0] + elif body.count() == 0: + raise forms.ValidationError("Object not found.") + diff --git a/neoexchange/ingest/migrations/0004_auto__add_field_proposal_pi__add_field_proposal_tag.py b/neoexchange/ingest/migrations/0004_auto__add_field_proposal_pi__add_field_proposal_tag.py new file mode 100644 index 000000000..dc27fe5c2 --- /dev/null +++ b/neoexchange/ingest/migrations/0004_auto__add_field_proposal_pi__add_field_proposal_tag.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Proposal.pi' + db.add_column('ingest_proposal', 'pi', + self.gf('django.db.models.fields.CharField')(default='', max_length=50), + keep_default=False) + + # Adding field 'Proposal.tag' + db.add_column('ingest_proposal', 'tag', + self.gf('django.db.models.fields.CharField')(default='LCO', max_length=10), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Proposal.pi' + db.delete_column('ingest_proposal', 'pi') + + # Deleting field 'Proposal.tag' + db.delete_column('ingest_proposal', 'tag') + + + models = { + 'ingest.block': { + 'Meta': {'object_name': 'Block'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'block_end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'block_start': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ingest.Body']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'proposal': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ingest.Proposal']"}), + 'site': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + 'telclass': ('django.db.models.fields.CharField', [], {'default': "'1m0'", 'max_length': '3'}), + 'tracking_number': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'when_observed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ingest.body': { + 'Meta': {'object_name': 'Body'}, + 'abs_mag': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'argofperih': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'eccentricity': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'elements_type': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'blank': 'True'}), + 'epochofel': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'epochofperih': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'fast_moving': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ingest': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 5, 6, 0, 0)'}), + 'longascnode': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'meananom': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'meandist': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'orbinc': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'origin': ('django.db.models.fields.CharField', [], {'default': "'M'", 'max_length': '1', 'null': 'True', 'blank': 'True'}), + 'perihdist': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'provisional_name': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'provisional_packed': ('django.db.models.fields.CharField', [], {'max_length': '7', 'null': 'True', 'blank': 'True'}), + 'slope': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'source_type': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True', 'blank': 'True'}), + 'urgency': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ingest.proposal': { + 'Meta': {'object_name': 'Proposal'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pi': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50'}), + 'tag': ('django.db.models.fields.CharField', [], {'default': "'LCO'", 'max_length': '10'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'ingest.record': { + 'Meta': {'object_name': 'Record'}, + 'block': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ingest.Block']"}), + 'exp': ('django.db.models.fields.FloatField', [], {}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '31'}), + 'filter': ('django.db.models.fields.CharField', [], {'max_length': '15'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instrument': ('django.db.models.fields.CharField', [], {'max_length': '4'}), + 'site': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + 'whentaken': ('django.db.models.fields.DateTimeField', [], {}) + } + } + + complete_apps = ['ingest'] \ No newline at end of file diff --git a/neoexchange/ingest/models.py b/neoexchange/ingest/models.py index 0fcbcf070..a49a77d0a 100644 --- a/neoexchange/ingest/models.py +++ b/neoexchange/ingest/models.py @@ -73,6 +73,8 @@ def check_object_exists(objname,dbg=False): class Proposal(models.Model): code = models.CharField(max_length=20) title = models.CharField(max_length=255) + pi = models.CharField(max_length=50, default='') + tag = models.CharField(max_length=10, default='LCO') def __unicode__(self): if len(self.title)>=10: diff --git a/neoexchange/ingest/static/ingest/css/forms.css b/neoexchange/ingest/static/ingest/css/forms.css index 6592f4afc..a5513a876 100644 --- a/neoexchange/ingest/static/ingest/css/forms.css +++ b/neoexchange/ingest/static/ingest/css/forms.css @@ -175,6 +175,18 @@ form.proposalsubmit_admin .proposalsubmit_credit label { color:#555; } +.compact-field { + float:left; + padding-right: 16px; +} +.compact-field input, .compact-field label { + display: table-row; +} +.compact-field label { + font-size: 70%; + color:#aaa; +} + @media (max-width: 800px), (max-device-width: 800px) { .pform { padding: 10px; margin:0px 20px 20px 20px; width:auto; } } diff --git a/neoexchange/ingest/templates/ingest/base.html b/neoexchange/ingest/templates/ingest/base.html index 887cbcebf..533d9ea05 100644 --- a/neoexchange/ingest/templates/ingest/base.html +++ b/neoexchange/ingest/templates/ingest/base.html @@ -4,7 +4,7 @@ - {%block header %}{% endblock %} | LCOGT + {%block header %}{% endblock %} | LCOGT NEOx diff --git a/neoexchange/ingest/templates/ingest/block_list.html b/neoexchange/ingest/templates/ingest/block_list.html new file mode 100644 index 000000000..e69de29bb diff --git a/neoexchange/ingest/templates/ingest/body_detail.html b/neoexchange/ingest/templates/ingest/body_detail.html index cfb39cbfc..05e6bb3ca 100644 --- a/neoexchange/ingest/templates/ingest/body_detail.html +++ b/neoexchange/ingest/templates/ingest/body_detail.html @@ -1,6 +1,9 @@ {% extends 'ingest/base.html' %} {% load url from future %} -{% block header %}NEOx home{% endblock %} +{% load staticfiles %} + +{% block css-content %}{% endblock %} +{% block header %}{{body.current_name}} details{% endblock %} {% block bodyclass %}page{% endblock %} @@ -14,12 +17,36 @@

    Object: {{body.current_name}}

    +

    Type{{body.get_source_type_display}}

    Status {% if body.active %}Actively Following{% else %}Not Following{% endif %}

    Source {{body.get_origin_display}}

    - Schedule Observations +
    +
    + +
    +
    + {{ form.utc_date }} +
    +
    + {{ form.site_code }} +
    +
    + {{ form.alt_limit }} +
    + +
    + {% for key, error in form.errors.items %} +
    + {{ error }} +
    + {% endfor %} +
    +

    Recent Observations

    diff --git a/neoexchange/ingest/templates/ingest/body_list.html b/neoexchange/ingest/templates/ingest/body_list.html index 7606c2fff..7e3e6ee71 100644 --- a/neoexchange/ingest/templates/ingest/body_list.html +++ b/neoexchange/ingest/templates/ingest/body_list.html @@ -1,6 +1,6 @@ {% extends 'ingest/base.html' %} {% load url from future %} -{% block header %}NEOx{% endblock %} +{% block header %}Targets{% endblock %} {% block bodyclass %}page{% endblock %} {% block extramenu %} diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index 2aeb29612..b3752d55a 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -9,12 +9,17 @@ {% block extramenu %}
    -

    Ephemeris for {{ new_target_name }} at {{ site_code }}

    +

    Ephemeris for {{ target.current_name }} at {{ site_code }}

    {% endblock%} {% block main-content %}
    +
    @@ -76,10 +81,4 @@

    Ephemeris for {{ new_target_name }} at {{ site_code }}

    - {% endblock %} diff --git a/neoexchange/ingest/templates/ingest/home.html b/neoexchange/ingest/templates/ingest/home.html index c41885384..3457314e5 100644 --- a/neoexchange/ingest/templates/ingest/home.html +++ b/neoexchange/ingest/templates/ingest/home.html @@ -4,7 +4,7 @@ {% block css-content %}{% endblock %} -{% block header %}NEOx home{% endblock %} +{% block header %}Home{% endblock %} {% block bodyclass %}page{% endblock %} {% block extramenucss %}extramenu-home{% endblock %} @@ -65,18 +65,20 @@ + {% for body in newest%} - - - - - - - - - - - + + + + + + {% empty%} + + {% endfor%}
    N999r0qUnknown/NEO CandidateMPCApril 8, 2015, 9:23 p.m.
    P10kfudUnknown/NEO CandidateMPCApril 8, 2015, 8:57 p.m.
    {{body.current_name}}{{body.get_source_type_display}}{{body.get_origin_display}} + +
    No new targets
    diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index 1514c4c17..eeaeb22c1 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -17,7 +17,8 @@ from django.db.models import Q from django.forms.models import model_to_dict from django.shortcuts import render -from django.views.generic import DetailView, ListView +from django.views.generic import DetailView, ListView, FormView, TemplateView +from django.core.urlresolvers import reverse from ingest.ephem_subs import call_compute_ephem, compute_ephem, \ determine_darkness_times, determine_slot_length, determine_exp_time_count from ingest.forms import EphemQuery, ScheduleForm @@ -32,32 +33,32 @@ def home(request): + latest = Body.objects.latest('ingest') + newest = Body.objects.filter(ingest=latest.ingest) params = { 'targets' : Body.objects.filter(active=True).count(), 'blocks' : Block.objects.filter(active=True).count(), - 'latest' : Body.objects.latest('ingest'), + 'latest' : latest, + 'newest' : newest, 'form' : EphemQuery() } return render(request,'ingest/home.html',params) class BodyDetailView(DetailView): - context_object_name = "body" model = Body def get_context_data(self, **kwargs): - # Call the base implementation first to get a context context = super(BodyDetailView, self).get_context_data(**kwargs) - # Add in a QuerySet of all the books - context['body_list'] = Body.objects.filter(active=True) + context['form'] = EphemQuery() return context + class BodySearchView(ListView): template_name = 'ingest/body_list.html' model = Body def get_queryset(self): - print self.kwargs try: name = self.request.REQUEST.get("q") except: @@ -79,25 +80,85 @@ def ephemeris(request): ephem_lines = call_compute_ephem(body_elements, dark_start, dark_end, data['site_code'], 300, data['alt_limit'] ) else: return render(request, 'ingest/home.html', {'form' : form}) - return render(request, 'ingest/ephem.html', - {'new_target_name' : form['target'].value, - 'ephem_lines' : ephem_lines, - 'site_code' : form['site_code'].value(), + {'target' : data['target'], + 'ephem_lines' : ephem_lines, + 'site_code' : form['site_code'].value(), } ) -def schedule(request): +class ScheduleTarget(FormView): + template_name = 'ingest/schedule.html' + form_class = ScheduleForm + success_url = reverse('schedule-success') + + def form_valid(self, form): + data = schedule(form) + logger.debug(data) + self.request.session['results'] = data + return super(ScheduleTarget, self).form_valid(form) + +def schedule(data): + body_elements = model_to_dict(data['body_id']) + # Check for valid proposal + # validate_proposal_time(data['proposal_code']) + + # Determine magnitude + dark_start, dark_end = determine_darkness_times(data['site_code'], data['utc_date']) + dark_midpoint = dark_start + (dark_end-dark_start)/2 + emp = compute_ephem(dark_midpoint, body_elements, data['site_code'], False, False, False) + magnitude = emp[3] + speed = emp[4] + + # Determine slot length + try: + slot_length = determine_slot_length(body_elements['provisional_name'], magnitude, data['site_code']) + except MagRangeError: + ok_to_schedule = False + # Determine exposure length and count + exp_length, exp_count = determine_exp_time_count(speed, data['site_code'], slot_length) + if exp_length == None or exp_count == None: + ok_to_schedule = False + + if data['ok_to_schedule'] == True: + logger.info(body_elements) + # Assemble request + body_elements['epochofel_mjd'] = body.epochofel_mjd() + body_elements['current_name'] = body.current_name() + params = { 'proposal_code' : data['proposal_code'], + 'exp_count' : exp_count, + 'exp_time' : exp_length, + 'site_code' : data['site_code'], + 'start_time' : dark_start, + 'end_time' : dark_end, + 'group_id' : body_elements['current_name'] + '_' + data['site_code'].upper() + '-' + datetime.strftime(data['utc_date'], '%Y%m%d') + } + # Record block and submit to scheduler +# if check_block_exists == 0: + request_number = submit_block_to_scheduler(body_elements, params) + resp = { + 'target_name' : body.current_name(), + 'magnitude' : magnitude, + 'speed' : speed, + 'slot_length' : slot_length, + 'exp_count' : exp_count, + 'exp_length' : exp_length, + 'schedule_ok' : ok_to_schedule, + 'request_number' : request_number + } + return resp + + +def schedule_old(request): body_id = request.GET.get('body_id', 1) ok_to_schedule = request.GET.get('ok_to_schedule', False) - print "top of form", ok_to_schedule, body_id - form = ScheduleForm(request.GET, initial={'body_id' : body_id, 'ok_to_schedule' : ok_to_schedule}) - body = Body.objects.get(id=form.initial['body_id']) + logger.info("top of form", ok_to_schedule, body_id) + form = ScheduleForm() if form.is_valid(): data = form.cleaned_data - print data - body_elements = model_to_dict(body) + logger.info(data) + body_elements = model_to_dict(data['body_id']) # Check for valid proposal # validate_proposal_time(data['proposal_code']) @@ -119,7 +180,7 @@ def schedule(request): ok_to_schedule = False if data['ok_to_schedule'] == True: - print body_elements + logger.info(body_elements) # Assemble request body_elements['epochofel_mjd'] = body.epochofel_mjd() body_elements['current_name'] = body.current_name() @@ -136,7 +197,8 @@ def schedule(request): request_number = submit_block_to_scheduler(body_elements, params) # record_block() return render(request, 'ingest/schedule.html', - {'form' : form, 'target_name' : body.current_name(), + {'form' : form, + 'target_name' : body.current_name(), 'magnitude' : magnitude, 'speed' : speed, 'slot_length' : slot_length, @@ -146,6 +208,8 @@ def schedule(request): 'request_number' : request_number} ) else: + logger.debug(form) + body = Body.objects.get(id=body_id) return render(request, 'ingest/schedule.html', {'form' : form, 'target_name' : body.current_name()}) return render(request, 'ingest/schedule.html', diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 067c6bdfe..08783bee7 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -169,7 +169,7 @@ 'filters': ['require_debug_false'] }, 'console': { - 'level': 'INFO', + 'level': 'DEBUG', 'class': 'logging.StreamHandler', } }, @@ -180,9 +180,9 @@ 'propagate': True, }, 'django': { - 'handlers':['file','console'], + 'handlers':['file'], 'propagate': True, - 'level':'DEBUG', + 'level':'ERROR', }, 'ingest' : { 'handlers' : ['file','console'], diff --git a/neoexchange/neox/urls.py b/neoexchange/neox/urls.py index 8510612e4..8403a83f6 100644 --- a/neoexchange/neox/urls.py +++ b/neoexchange/neox/urls.py @@ -16,8 +16,8 @@ from django.contrib import admin from django.conf import settings from django.conf.urls.static import static -from django.views.generic import ListView, DetailView -from ingest.views import BodySearchView +from django.views.generic import ListView, DetailView, TemplateView +from ingest.views import BodySearchView,BodyDetailView, ScheduleTarget from ingest.models import Body, Block admin.autodiscover() @@ -25,11 +25,12 @@ urlpatterns = patterns('ingest.views', url(r'^$', 'home', name='home'), url(r'^block/list/$', ListView.as_view(model=Block, queryset=Block.objects.filter(active=True).order_by('-block_start'), context_object_name="block_list"), name='blocklist'), + url(r'^target/(?P\d+)/$',BodyDetailView.as_view(model=Body), name='target'), url(r'^target/$', ListView.as_view(model=Body, queryset=Body.objects.filter(active=True).order_by('-origin','-ingest'), context_object_name="target_list"), name='targetlist'), - url(r'^target/(?P\d+)/$',DetailView.as_view(model=Body, context_object_name='body'), name='target'), url(r'^search/$', BodySearchView.as_view(context_object_name="target_list"), name='search'), url(r'^ephemeris/$', 'ephemeris', name='ephemeris'), - url(r'^schedule/$', 'schedule', name='schedule'), + url(r'^schedule/success/$',TemplateView.as_view(template_name='ingest/schedule.html'),name='schedule-success'), + url(r'^schedule/$', ScheduleTarget.as_view(), name='schedule'), url(r'^grappelli/', include('grappelli.urls')), url(r'^admin/', include(admin.site.urls)), )+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) From 83bb1810a31e2e8d19ed11fe3d377cfb455d926a Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Wed, 6 May 2015 17:40:25 +0100 Subject: [PATCH 19/48] Intermediate fix for problem reversing URLs --- neoexchange/ingest/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index eeaeb22c1..f7a6b25b7 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -90,7 +90,7 @@ def ephemeris(request): class ScheduleTarget(FormView): template_name = 'ingest/schedule.html' form_class = ScheduleForm - success_url = reverse('schedule-success') + success_url = '/schedule/' #reverse('schedule-success') def form_valid(self, form): data = schedule(form) From 2428f9649febb08dfb8a9716b52135dad22c0908 Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Thu, 7 May 2015 15:09:51 +0100 Subject: [PATCH 20/48] Instigating CBVs for handling the submission to scheduler --- neoexchange/ingest/forms.py | 28 ++- .../ingest/templates/ingest/body_detail.html | 2 +- .../ingest/templates/ingest/schedule.html | 70 +++--- .../templates/ingest/schedule_confirm.html | 77 +++++++ neoexchange/ingest/views.py | 210 ++++++++++-------- neoexchange/neox/urls.py | 16 +- 6 files changed, 254 insertions(+), 149 deletions(-) create mode 100644 neoexchange/ingest/templates/ingest/schedule_confirm.html diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index 8a5c9f7d1..65cb34948 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -24,17 +24,27 @@ def clean_target(self): class ScheduleForm(forms.Form): SITES = (('V37','ELP (V37)'),('F65','FTN (F65)'),('E10', 'FTS (E10)'),('W86','LSC (W85-87)'),('K92','CPT (K91-93)'),('Q63','COJ (Q63-64)')) proposals = Proposal.objects.all() - proposal_choices = [(str(proposal.code), str(proposal.title)) for proposal in proposals] + proposal_choices = [(proposal.code, proposal.title) for proposal in proposals] + proposal_code = forms.ChoiceField(required=True, choices=proposal_choices) site_code = forms.ChoiceField(required=True, choices=SITES) utc_date = forms.DateField(input_formats=['%Y-%m-%d',], initial=datetime.utcnow().date(), required=True, widget=forms.TextInput(attrs={'size':'10'}), error_messages={'required': _(u'UTC date is required')}) - body_id = forms.IntegerField(widget=forms.HiddenInput()) - ok_to_schedule = forms.BooleanField(initial=False, required=False, widget=forms.HiddenInput()) + # body_id = forms.IntegerField(widget=forms.HiddenInput()) + # ok_to_schedule = forms.BooleanField(initial=False, required=False, widget=forms.HiddenInput()) - def clean_body_id(self): - body = Body.objects.filter(pk=self.cleaned_data['body_id']) - if body.count() == 1 : - return body[0] - elif body.count() == 0: - raise forms.ValidationError("Object not found.") + # def clean_body_id(self): + # body = Body.objects.filter(pk=self.cleaned_data['body_id']) + # if body.count() == 1 : + # return body[0] + # elif body.count() == 0: + # raise forms.ValidationError("Object not found.") + +class ScheduleBlockForm(forms.Form): + start_time = forms.DateTimeField(widget=forms.HiddenInput()) + end_time = forms.DateTimeField(widget=forms.HiddenInput()) + exp_count = forms.IntegerField(widget=forms.HiddenInput()) + exp_length = forms.FloatField(widget=forms.HiddenInput()) + proposal_code = forms.CharField(max_length=15,widget=forms.HiddenInput()) + site_code = forms.CharField(max_length=5,widget=forms.HiddenInput()) + group_id = forms.CharField(max_length=30,widget=forms.HiddenInput()) diff --git a/neoexchange/ingest/templates/ingest/body_detail.html b/neoexchange/ingest/templates/ingest/body_detail.html index 05e6bb3ca..6eef6c300 100644 --- a/neoexchange/ingest/templates/ingest/body_detail.html +++ b/neoexchange/ingest/templates/ingest/body_detail.html @@ -18,7 +18,7 @@

    Object: {{body.current_name}}

    diff --git a/neoexchange/ingest/templates/ingest/schedule.html b/neoexchange/ingest/templates/ingest/schedule.html index 55666cddb..8c9860976 100644 --- a/neoexchange/ingest/templates/ingest/schedule.html +++ b/neoexchange/ingest/templates/ingest/schedule.html @@ -4,57 +4,51 @@ {% block bodyclass %}page{% endblock %} + {% block extramenu %}
    -

    Scheduling for: {{target_name}}

    +

    Scheduling

    {% endblock%} {% block main-content %} -
    -
    -
    -
    -
    - {{ form.proposal_code }} - {{ form.site_code }} -
    -
    - {{ form.body_id }} - {{ form.utc_date }} - {% if schedule_ok %} - - - {% else %} - - - {% endif %} -
    +
    +
    + {% if body.id and form %} + +
    + {% csrf_token %} +

    Parameters for: {{body.current_name}}

    + + + + + + + + + + + + + + + +
    Proposal{{ form.proposal_code }}
    Site code{{ form.site_code }}
    UTC Date{{ form.utc_date }}
    {% for key, error in form.errors.items %}
    {{ error }}
    {% endfor %} -
    -
    -
    - {% if schedule_ok %} - - {% endif %} +
    + {% endblock %} diff --git a/neoexchange/ingest/templates/ingest/schedule_confirm.html b/neoexchange/ingest/templates/ingest/schedule_confirm.html new file mode 100644 index 000000000..549afd906 --- /dev/null +++ b/neoexchange/ingest/templates/ingest/schedule_confirm.html @@ -0,0 +1,77 @@ +{% extends 'ingest/base.html' %} +{% load url from future %} +{% block header %}NEOx scheduling{% endblock %} + +{% block bodyclass %}page{% endblock %} + + +{% block extramenu %} +{% with vars=request.session.confirm %} +
    +

    Confirm Scheduling for: {{body.current_name}}

    +
    +{% endwith %} +{% endblock%} + +{% block main-content %} + {% with vars=request.session.confirm %} +
    +
    +
    + + {% if vars.data %} +

    Submitted Parameters

    + + + + + + + + + + + + + + + +
    Proposal{{ vars.data.proposal_code }}
    Site{{vars.data.site_code}}
    UTC date{{ vars.data.utc_date}}
    +

    Calculated characteristics

    + + + + + + + + + + + + + + + + + + + + + + + +
    Magnitude{{ vars.data.magnitude|floatformat:2 }}
    Speed{{ vars.data.speed|floatformat:2 }} '/min
    Slot length{{ vars.data.slot_length }} mins
    No. of exposures{{ vars.data.exp_count }}
    Exposure length{{ vars.data.exp_length|floatformat:1 }} secs
    + {% endif %} +
    +
    +
    + {% csrf_token %} + {{form}} + +
    +
    +
    +
    +{% endwith %} +{% endblock %} \ No newline at end of file diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index f7a6b25b7..639e3e56c 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -17,11 +17,12 @@ from django.db.models import Q from django.forms.models import model_to_dict from django.shortcuts import render -from django.views.generic import DetailView, ListView, FormView, TemplateView from django.core.urlresolvers import reverse +from django.views.generic import DetailView, ListView, FormView, TemplateView, View +from django.views.generic.detail import SingleObjectMixin from ingest.ephem_subs import call_compute_ephem, compute_ephem, \ determine_darkness_times, determine_slot_length, determine_exp_time_count -from ingest.forms import EphemQuery, ScheduleForm +from ingest.forms import EphemQuery, ScheduleForm, ScheduleBlockForm from ingest.models import * from ingest.sources_subs import fetchpage_and_make_soup, packed_to_normal, \ fetch_mpcorbit, submit_block_to_scheduler @@ -87,21 +88,103 @@ def ephemeris(request): } ) -class ScheduleTarget(FormView): +class ScheduleSuccess(TemplateView): + template_name='ingest/schedule.html' + +class SchedFormDisplay(DetailView): + template_name = 'ingest/schedule.html' + model = Body + + def get_context_data(self, **kwargs): + context = super(SchedFormDisplay, self).get_context_data(**kwargs) + context['form'] = ScheduleForm + return context + +class ScheduleProcess(SingleObjectMixin, FormView): template_name = 'ingest/schedule.html' form_class = ScheduleForm - success_url = '/schedule/' #reverse('schedule-success') + model = Body + ok_to_schedule = False + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + #self.confirm = kwargs['confirm'] + return super(ScheduleProcess, self).post(request, *args, **kwargs) + + def form_valid(self, form): + data = schedule_check(form.cleaned_data, self.object, self.ok_to_schedule) + #logger.debug(self.confirm) + self.request.session['confirm'] = {'data': data, 'body':self.object,'form':form} + return super(ScheduleProcess, self).form_valid(form) + + def get_success_url(self): + body = self.get_object() + return reverse('schedule-confirm', kwargs={'pk':body.pk}) + +class ScheduleCheck(View): + ''' + Controls the Scheduling forms + GET will render the from - SchedFormDisplay + POST will process the results - ScheduleProcess + ''' + + def get(self, request, *args, **kwargs): + view = SchedFormDisplay.as_view() + return view(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + view = ScheduleProcess.as_view() + return view(request, *args, **kwargs) + + +class SchedConfDisplay(DetailView): + template_name = 'ingest/schedule_confirm.html' + model = Body + + def get_context_data(self, **kwargs): + context = super(SchedConfDisplay, self).get_context_data(**kwargs) + form = ScheduleBlockForm(self.request.session['confirm']['data']) + context['form'] = form + return context + +class ScheduleSubmit(SingleObjectMixin, FormView): + template_name = 'ingest/schedule_confirm.html' + form_class = ScheduleBlockForm + model = Body + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + return super(ScheduleSubmit, self).post(request, *args, **kwargs) def form_valid(self, form): - data = schedule(form) - logger.debug(data) - self.request.session['results'] = data - return super(ScheduleTarget, self).form_valid(form) + response = schedule_submit(form.cleaned_data, self.object) + logger.debug(response) + return super(ScheduleSubmit, self).form_valid(form) -def schedule(data): - body_elements = model_to_dict(data['body_id']) + def get_success_url(self): + return reverse('home') + +class ScheduleConfirm(View): + ''' + Controls the Scheduling forms + GET will render the intermedia confirmation page (including hidden form) - SchedConfDisplay + POST will post to the scheduler - ScheduleSubmit + ''' + + def get(self, request, *args, **kwargs): + view = SchedConfDisplay.as_view() + return view(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + view = ScheduleSubmit.as_view() + return view(request, *args, **kwargs) + + +def schedule_check(data,body,ok_to_schedule): + body_elements = model_to_dict(body) # Check for valid proposal # validate_proposal_time(data['proposal_code']) + ok_to_schedule = True # Determine magnitude dark_start, dark_end = determine_darkness_times(data['site_code'], data['utc_date']) @@ -120,22 +203,6 @@ def schedule(data): if exp_length == None or exp_count == None: ok_to_schedule = False - if data['ok_to_schedule'] == True: - logger.info(body_elements) - # Assemble request - body_elements['epochofel_mjd'] = body.epochofel_mjd() - body_elements['current_name'] = body.current_name() - params = { 'proposal_code' : data['proposal_code'], - 'exp_count' : exp_count, - 'exp_time' : exp_length, - 'site_code' : data['site_code'], - 'start_time' : dark_start, - 'end_time' : dark_end, - 'group_id' : body_elements['current_name'] + '_' + data['site_code'].upper() + '-' + datetime.strftime(data['utc_date'], '%Y%m%d') - } - # Record block and submit to scheduler -# if check_block_exists == 0: - request_number = submit_block_to_scheduler(body_elements, params) resp = { 'target_name' : body.current_name(), 'magnitude' : magnitude, @@ -144,77 +211,32 @@ def schedule(data): 'exp_count' : exp_count, 'exp_length' : exp_length, 'schedule_ok' : ok_to_schedule, - 'request_number' : request_number + 'site_code' : data['site_code'], + 'proposal_code' : data['proposal_code'], + 'group_id' : body.current_name() + '_' + data['site_code'].upper() + '-' + datetime.strftime(data['utc_date'], '%Y%m%d'), + 'utc_date' : data['utc_date'], + 'start_time' : dark_start, + 'end_time' : dark_end, } return resp - -def schedule_old(request): - - body_id = request.GET.get('body_id', 1) - ok_to_schedule = request.GET.get('ok_to_schedule', False) - logger.info("top of form", ok_to_schedule, body_id) - form = ScheduleForm() - if form.is_valid(): - data = form.cleaned_data - logger.info(data) - body_elements = model_to_dict(data['body_id']) - # Check for valid proposal - # validate_proposal_time(data['proposal_code']) - - # Determine magnitude - dark_start, dark_end = determine_darkness_times(data['site_code'], data['utc_date']) - dark_midpoint = dark_start + (dark_end-dark_start)/2 - emp = compute_ephem(dark_midpoint, body_elements, data['site_code'], False, False, False) - magnitude = emp[3] - speed = emp[4] - - # Determine slot length - try: - slot_length = determine_slot_length(body_elements['provisional_name'], magnitude, data['site_code']) - except MagRangeError: - ok_to_schedule = False - # Determine exposure length and count - exp_length, exp_count = determine_exp_time_count(speed, data['site_code'], slot_length) - if exp_length == None or exp_count == None: - ok_to_schedule = False - - if data['ok_to_schedule'] == True: - logger.info(body_elements) - # Assemble request - body_elements['epochofel_mjd'] = body.epochofel_mjd() - body_elements['current_name'] = body.current_name() - params = { 'proposal_code' : data['proposal_code'], - 'exp_count' : exp_count, - 'exp_time' : exp_length, - 'site_code' : data['site_code'], - 'start_time' : dark_start, - 'end_time' : dark_end, - 'group_id' : body_elements['current_name'] + '_' + data['site_code'].upper() + '-' + datetime.strftime(data['utc_date'], '%Y%m%d') - } - # Record block and submit to scheduler -# if check_block_exists == 0: - request_number = submit_block_to_scheduler(body_elements, params) -# record_block() - return render(request, 'ingest/schedule.html', - {'form' : form, - 'target_name' : body.current_name(), - 'magnitude' : magnitude, - 'speed' : speed, - 'slot_length' : slot_length, - 'exp_count' : exp_count, - 'exp_length' : exp_length, - 'schedule_ok' : ok_to_schedule, - 'request_number' : request_number} - ) - else: - logger.debug(form) - body = Body.objects.get(id=body_id) - return render(request, 'ingest/schedule.html', {'form' : form, 'target_name' : body.current_name()}) - - return render(request, 'ingest/schedule.html', - {'target_name' : body.current_name()} - ) +def schedule_submit(data,body): + # Assemble request + # Send to scheduler + body_elements = model_to_dict(body) + body_elements['epochofel_mjd'] = body.epochofel_mjd() + body_elements['current_name'] = body.current_name() + params = { 'proposal_code' : data['proposal_code'], + 'exp_count' : data['exp_count'], + 'exp_time' : data['exp_length'], + 'site_code' : data['site_code'], + 'start_time' : data['start_time'], + 'end_time' : data['end_time'], + 'group_id' : data['group_id'] + } + # Record block and submit to scheduler + request_number = submit_block_to_scheduler(body_elements, params) + return request_number def save_and_make_revision(body,kwargs): ''' Make a revision if any of the parameters have changed, but only do it once per ingest not for each parameter diff --git a/neoexchange/neox/urls.py b/neoexchange/neox/urls.py index 8403a83f6..d9a406550 100644 --- a/neoexchange/neox/urls.py +++ b/neoexchange/neox/urls.py @@ -12,25 +12,27 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ''' -from django.conf.urls import patterns, include, url -from django.contrib import admin from django.conf import settings +from django.conf.urls import patterns, include, url from django.conf.urls.static import static -from django.views.generic import ListView, DetailView, TemplateView -from ingest.views import BodySearchView,BodyDetailView, ScheduleTarget +from django.contrib import admin +from django.views.generic import ListView, DetailView from ingest.models import Body, Block +from ingest.views import BodySearchView,BodyDetailView, ScheduleCheck, ScheduleSuccess, SchedFormDisplay, ScheduleConfirm admin.autodiscover() urlpatterns = patterns('ingest.views', url(r'^$', 'home', name='home'), url(r'^block/list/$', ListView.as_view(model=Block, queryset=Block.objects.filter(active=True).order_by('-block_start'), context_object_name="block_list"), name='blocklist'), - url(r'^target/(?P\d+)/$',BodyDetailView.as_view(model=Body), name='target'), url(r'^target/$', ListView.as_view(model=Body, queryset=Body.objects.filter(active=True).order_by('-origin','-ingest'), context_object_name="target_list"), name='targetlist'), + url(r'^target/(?P\d+)/$',BodyDetailView.as_view(model=Body), name='target'), url(r'^search/$', BodySearchView.as_view(context_object_name="target_list"), name='search'), url(r'^ephemeris/$', 'ephemeris', name='ephemeris'), - url(r'^schedule/success/$',TemplateView.as_view(template_name='ingest/schedule.html'),name='schedule-success'), - url(r'^schedule/$', ScheduleTarget.as_view(), name='schedule'), + url(r'^schedule/(?P\d+)/confirm/$',ScheduleConfirm.as_view(), name='schedule-confirm'), + url(r'^schedule/(?P\d+)/$', ScheduleCheck.as_view(), name='schedule-body'), + url(r'^schedule/success/$',ScheduleSuccess.as_view(), name='schedule-success'), + url(r'^schedule/$', SchedFormDisplay.as_view(), name='schedule'), url(r'^grappelli/', include('grappelli.urls')), url(r'^admin/', include(admin.site.urls)), )+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) From c258f1cda01c91509e23b4075cf335719a6f4f6e Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Fri, 8 May 2015 12:13:00 +0100 Subject: [PATCH 21/48] Fixing url in ephemeris --- neoexchange/ingest/templates/ingest/ephem.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index b3752d55a..d23919606 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -17,7 +17,7 @@

    Ephemeris for {{ target.current_name
    From 09a14355e640605b4d71e213d49a7637a7ee4114 Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Wed, 13 May 2015 13:23:52 +0100 Subject: [PATCH 22/48] Updating unittests --- neoexchange/ingest/sources_subs.py | 10 ++-- .../ingest/templates/ingest/ephem.html | 2 +- .../templates/ingest/schedule_confirm.html | 18 +++---- neoexchange/ingest/tests/test_source_subs.py | 4 +- neoexchange/ingest/tests/test_views.py | 16 +++--- neoexchange/neox/settings.py | 51 ++++++++++++++----- neoexchange/neox/tests/base.py | 15 +++++- .../neox/tests/test_ephemeris_creation.py | 19 ++++--- .../neox/tests/test_schedule_observations.py | 45 +++++++--------- 9 files changed, 103 insertions(+), 77 deletions(-) diff --git a/neoexchange/ingest/sources_subs.py b/neoexchange/ingest/sources_subs.py index d906b9b97..6af86f927 100644 --- a/neoexchange/ingest/sources_subs.py +++ b/neoexchange/ingest/sources_subs.py @@ -620,7 +620,7 @@ def submit_block_to_scheduler(elements, params): params = configure_defaults(params) # Create Location (site, observatory etc) and add to Request location = make_location(params) - logger.debug("Location=",location) + logger.debug("Location=%s" % location) request.set_location(location) # Create Target (pointing) and add to Request if len(elements) > 0: @@ -629,17 +629,17 @@ def submit_block_to_scheduler(elements, params): else: logger.debug("Making a static object") target = make_target(params) - logger.debug("Target=",target) + logger.debug("Target=%s" % target) request.set_target(target) # Create Window and add to Request window = make_window(params) - logger.debug("Window=",window) + logger.debug("Window=%s" % window) request.add_window(window) # Create Molecule and add to Request molecule = make_molecule(params) request.add_molecule(molecule) # add exposure to the request request.set_note('Submitted by NEOexchange') - logger.debug("Request=",request) + logger.debug("Request=%s" % request) constraints = make_constraints(params) request.set_constraints(constraints) @@ -658,6 +658,6 @@ def submit_block_to_scheduler(elements, params): request_numbers = response_data.get('request_numbers', '') # request_numbers = (-42,) request_number = request_numbers[0] - logger.debug("Req number=", request_number) + logger.debug("Req number=%s" % request_number) return request_number diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index d23919606..4a9533b62 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -1,7 +1,7 @@ {% extends 'ingest/base.html' %} {% load url from future %} -{% block header %}NEOx{% endblock %} +{% block header %}Ephemeris for {{ target.current_name }} at {{ site_code }}{% endblock %} {% block bodyclass %}page{% endblock %} diff --git a/neoexchange/ingest/templates/ingest/schedule_confirm.html b/neoexchange/ingest/templates/ingest/schedule_confirm.html index 549afd906..6cf2cbf2e 100644 --- a/neoexchange/ingest/templates/ingest/schedule_confirm.html +++ b/neoexchange/ingest/templates/ingest/schedule_confirm.html @@ -23,15 +23,15 @@

    Confirm Scheduling for: {{body.current_name}}

    Submitted Parameters

    - + - + - + @@ -40,23 +40,23 @@

    Submitted Parameters

    Calculated characteristics

    Proposal {{ vars.data.proposal_code }}
    Site {{vars.data.site_code}}
    UTC date {{ vars.data.utc_date}}
    - + - + - + - + - + @@ -68,7 +68,7 @@

    Calculated characteristics

    {% csrf_token %} {{form}} - + diff --git a/neoexchange/ingest/tests/test_source_subs.py b/neoexchange/ingest/tests/test_source_subs.py index 345f8c817..05be9f571 100644 --- a/neoexchange/ingest/tests/test_source_subs.py +++ b/neoexchange/ingest/tests/test_source_subs.py @@ -16,7 +16,7 @@ from django.test import TestCase from django.forms.models import model_to_dict from ingest.models import Body -from datetime import datetime +from datetime import datetime, timedelta from ingest.ephem_subs import determine_darkness_times #Import module to test @@ -88,7 +88,7 @@ def test_submit_body_for_cpt(self): body_elements['epochofel_mjd'] = self.body.epochofel_mjd() body_elements['current_name'] = self.body.current_name() site_code = 'K92' - utc_date = datetime(2015, 4, 21, 0, 0, 0) + utc_date = datetime.now()+timedelta(days=1) dark_start, dark_end = determine_darkness_times(site_code, utc_date) params = { 'proposal_code' : 'LCO2015A-009', 'exp_count' : 18, diff --git a/neoexchange/ingest/tests/test_views.py b/neoexchange/ingest/tests/test_views.py index 6422befd6..ee29186fa 100644 --- a/neoexchange/ingest/tests/test_views.py +++ b/neoexchange/ingest/tests/test_views.py @@ -138,7 +138,7 @@ def test_home_page_can_save_a_GET_request(self): utc_date = datetime(2015, 4, 21, 3,0,0) dark_start, dark_end = determine_darkness_times(site_code, utc_date ) - response = self.client.get('/ephemeris/', + response = self.client.get(reverse('ephemeris'), data={'target' : 'N999r0q', 'site_code' : site_code, 'utc_date' : '2015-04-21', @@ -149,14 +149,14 @@ def test_home_page_can_save_a_GET_request(self): ephem_lines = call_compute_ephem(body_elements, dark_start, dark_end, site_code, '5m' ) expected_html = render_to_string( 'ingest/ephem.html', - {'new_target_name' : 'N999r0q', + {'target' : self.body, 'ephem_lines' : ephem_lines, 'site_code' : site_code } ) self.assertMultiLineEqual(response.content.decode(), expected_html) def test_displays_ephem(self): - response = self.client.get('/ephemeris/', + response = self.client.get(reverse('ephemeris'), data ={'target' : 'N999r0q', 'utc_date' : '2015-05-11', 'site_code' : 'V37', @@ -176,14 +176,14 @@ def test_uses_ephem_template(self): self.assertTemplateUsed(response, 'ingest/ephem.html') def test_form_errors_are_sent_back_to_home_page(self): - response = self.client.get('/ephemeris/', data={'target' : ''}) + response = self.client.get(reverse('ephemeris'), data={'target' : ''}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'ingest/home.html') expected_error = escape("Target name is required") self.assertContains(response, expected_error) def test_ephem_page_displays_site_code(self): - response = self.client.get('/ephemeris/', + response = self.client.get(reverse('ephemeris'), data = {'target' : 'N999r0q', 'site_code' : 'F65', 'utc_date' : '2015-04-20', @@ -239,7 +239,7 @@ def setUp(self): self.test_proposal, created = Proposal.objects.get_or_create(**test_proposal_params) def test_uses_schedule_template(self): - response = self.client.get('/schedule/', + response = self.client.get(reverse('schedule-body', kwargs={'pk':self.body.pk}), data = {'body_id' : self.body.pk, 'site_code' : 'F65', 'utc_date' : '2015-04-20', @@ -248,11 +248,11 @@ def test_uses_schedule_template(self): self.assertTemplateUsed(response, 'ingest/schedule.html') def test_schedule_page_contains_object_name(self): - response = self.client.get('/schedule/', + response = self.client.get(reverse('schedule-body', kwargs={'pk':self.body.pk}), data = {'body_id' : self.body.pk, 'site_code' : 'F65', 'utc_date' : '2015-04-20', 'proposal_code' : self.neo_proposal.code } ) - self.assertContains(response, 'Scheduling for: ' + self.body.current_name()) + self.assertContains(response, 'Parameters for: ' + self.body.current_name()) diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 08783bee7..9c1d6e3d5 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -125,19 +125,6 @@ 'south' ) -################## -# LOCAL SETTINGS # -################## - -# Allow any settings to be defined in local_settings.py which should be -# ignored in your version control system allowing for settings to be -# defined per machine. -try: - from local_settings import * -except ImportError as e: - if "local_settings" not in str(e): - raise e - LOGGING = { 'version': 1, 'disable_existing_loggers': True, @@ -186,8 +173,44 @@ }, 'ingest' : { 'handlers' : ['file','console'], - 'level' : 'DEBUG', + 'level' : 'INFO', } } } +####################### +# Test Database setup # +####################### + +if 'test' in sys.argv: + # If you also want to speed up password hashing in test cases. + PASSWORD_HASHERS = ( + 'django.contrib.auth.hashers.MD5PasswordHasher', + ) + # Use SQLite3 for the database engine during testing. + DATABASES = { 'default': + { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'test_db', # Add the name of your SQLite3 database file here. + }, + 'rbauth': + { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'test_rbauth', # Add the name of your SQLite3 database file here. + } + } + +################## +# LOCAL SETTINGS # +################## + +# Allow any settings to be defined in local_settings.py which should be +# ignored in your version control system allowing for settings to be +# defined per machine. +try: + from local_settings import * +except ImportError as e: + if "local_settings" not in str(e): + raise e + + diff --git a/neoexchange/neox/tests/base.py b/neoexchange/neox/tests/base.py index 247373cad..5ca891792 100644 --- a/neoexchange/neox/tests/base.py +++ b/neoexchange/neox/tests/base.py @@ -1,6 +1,6 @@ from django.test import LiveServerTestCase from selenium import webdriver -from ingest.models import Body +from ingest.models import Body, Proposal class FunctionalTest(LiveServerTestCase): @@ -23,10 +23,23 @@ def insert_test_body(self): } self.body, created = Body.objects.get_or_create(**params) + def insert_test_proposals(self): + + neo_proposal_params = { 'code' : 'LCO2015A-009', + 'title' : 'LCOGT NEO Follow-up Network' + } + self.neo_proposal, created = Proposal.objects.get_or_create(**neo_proposal_params) + + test_proposal_params = { 'code' : 'LCOEngineering', + 'title' : 'Test Proposal' + } + self.test_proposal, created = Proposal.objects.get_or_create(**test_proposal_params) + def setUp(self): self.browser = webdriver.Firefox() self.browser.implicitly_wait(5) self.insert_test_body() + self.insert_test_proposals() def tearDown(self): self.browser.refresh() diff --git a/neoexchange/neox/tests/test_ephemeris_creation.py b/neoexchange/neox/tests/test_ephemeris_creation.py index cf81bd944..f2800a7bb 100644 --- a/neoexchange/neox/tests/test_ephemeris_creation.py +++ b/neoexchange/neox/tests/test_ephemeris_creation.py @@ -35,17 +35,16 @@ def test_can_compute_ephemeris(self): # He notices the page title has the name of the site and the header # mentions current targets - self.assertIn('NEOx home | LCOGT', self.browser.title) + self.assertIn('Home | LCOGT NEOx', self.browser.title) header_text = self.browser.find_element_by_class_name('masthead').text self.assertIn('active targets', header_text) # He notices there are several targets that could be followed up self.check_for_header_in_table('id_neo_targets', 'Target Name Type Origin Ingested') - self.check_for_row_in_table('id_neo_targets', - 'N999r0q Unknown/NEO Candidate MPC April 8, 2015, 9:23 p.m.') - self.check_for_row_in_table('id_neo_targets', - 'P10kfud Unknown/NEO Candidate MPC April 8, 2015, 8:57 p.m.') + testlines =[u'N999r0q\nUnknown/NEO Candidate Minor Planet Center %s' % self.body.ingest.strftime('%-d %B %Y, %H:%M'), + u'P10kfud\nUnknown/NEO Candidate Minor Planet Center %s' % self.body.ingest.strftime('%-d %B %Y, %H:%M')] + self.check_for_row_in_table('id_neo_targets', testlines[0]) # He is invited to enter a target to compute an ephemeris inputbox = self.get_item_input_box() @@ -77,12 +76,12 @@ def test_can_compute_ephemeris(self): '2015 04 21 08:45 20 10 05.99 +29 56 57.5 20.4 2.43 +33 0.09 107 -42 +047 -04:25' ) - # There is a button asking whether to schedule the target - link = self.browser.find_element_by_link_text('No') + # # There is a button asking whether to schedule the target + # link = self.browser.find_element_by_link_text('No') - # He clicks 'No' and is returned to the front page - link.click() - self.assertIn('NEOx home | LCOGT', self.browser.title) + # # He clicks 'No' and is returned to the front page + # link.click() + # self.assertIn('NEOx home | LCOGT', self.browser.title) # Satisfied, he goes back to sleep diff --git a/neoexchange/neox/tests/test_schedule_observations.py b/neoexchange/neox/tests/test_schedule_observations.py index dab5043d1..9314b68a5 100644 --- a/neoexchange/neox/tests/test_schedule_observations.py +++ b/neoexchange/neox/tests/test_schedule_observations.py @@ -3,6 +3,7 @@ from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from datetime import datetime +from django.core.urlresolvers import reverse from ingest.models import Body, Proposal class ScheduleObservations(FunctionalTest): @@ -42,38 +43,27 @@ def insert_test_bodies(self): } self.body2, created = Body.objects.get_or_create(**params2) - def insert_test_proposals(self): - - neo_proposal_params = { 'code' : 'LCO2015A-009', - 'title' : 'LCOGT NEO Follow-up Network' - } - self.neo_proposal, created = Proposal.objects.get_or_create(**neo_proposal_params) - - test_proposal_params = { 'code' : 'LCOEngineering', - 'title' : 'Test Proposal' - } - self.test_proposal, created = Proposal.objects.get_or_create(**test_proposal_params) - def test_can_schedule_observations(self): ## Insert test bodies and proposals otherwise things will fail self.insert_test_bodies() - self.insert_test_proposals() + #self.insert_test_proposals() # Sharon has heard about a new website for NEOs. She goes to the # page of the first target # (XXX semi-hardwired but the targets link should be being tested in # test_targets_validation.TargetsValidationTest - self.browser.get(self.live_server_url + '/target/1/') + start_url = reverse('target',kwargs={'pk':1}) + self.browser.get(self.live_server_url + start_url) # She sees a Schedule Observations button link = self.browser.find_element_by_link_text('Schedule Observations') - target_url = self.live_server_url + '/schedule/?body_id=1' + target_url = self.live_server_url + reverse('schedule-body',kwargs={'pk':1}) self.assertEqual(link.get_attribute('href'), target_url) # She clicks the link to go to the Schedule Observations page link.click() - self.browser.implicitly_wait(3) + self.browser.implicitly_wait(10) new_url = self.browser.current_url self.assertEqual(str(new_url), target_url) @@ -97,17 +87,18 @@ def test_can_schedule_observations(self): # The page refreshes and a series of values for magnitude, speed, slot # length, number and length of exposures appear - magnitude = self.browser.find_element_by_id('id_magnitude').text - self.assertIn('Magnitude: 20.39', magnitude) - speed = self.browser.find_element_by_id('id_speed').text - self.assertIn("Speed: 2.52 '/min", speed) - slot_length = self.browser.find_element_by_id('id_slot_length').text - self.assertIn('Slot length: 22.5 mins', slot_length) - num_exp = self.browser.find_element_by_id('id_no_of_exps').text - self.assertIn('No. of exp: 18', num_exp) - exp_length = self.browser.find_element_by_id('id_exp_length').text - self.assertIn('Exp length: 50.0 secs', exp_length) + magnitude = self.browser.find_element_by_id('id_magnitude').find_element_by_class_name('kv-value').text + self.assertIn('20.39', magnitude) + speed = self.browser.find_element_by_id('id_speed').find_element_by_class_name('kv-value').text + self.assertIn("2.52 '/min", speed) + slot_length = self.browser.find_element_by_id('id_slot_length').find_element_by_class_name('kv-value').text + self.assertIn('22.5 mins', slot_length) + num_exp = self.browser.find_element_by_id('id_no_of_exps').find_element_by_class_name('kv-value').text + self.assertIn('18', num_exp) + exp_length = self.browser.find_element_by_id('id_exp_length').find_element_by_class_name('kv-value').text + self.assertIn('50.0 secs', exp_length) # At this point, a 'Schedule this object' button appears - link = self.browser.find_element_by_link_text('Schedule this Object') + submit = self.browser.find_element_by_id('id_submit_button').get_attribute("value") + self.assertIn('Schedule this Object',submit) self.fail("Finish the test!") From a2eb6aaed4cfc4df3153aadf33680695370650e1 Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Mon, 18 May 2015 13:39:19 +0100 Subject: [PATCH 23/48] Slimming down the docker config --- docker/neoexchange.dockerfile => Dockerfile | 12 +- Makefile | 56 --------- config/{neoexchange.ini => processes.ini} | 0 docker/bin/loaddata.sh | 4 - docker/bin/nginx.sh | 8 -- docker/bin/run.sh | 19 --- docker/bin/stop.sh | 5 - docker/bin/uwsgi.sh | 4 - neoexchange/ingest/ephem_subs.py | 118 +++++++++--------- neoexchange/ingest/forms.py | 14 +++ .../templates/ingest/{500x.html => 500.html} | 0 .../templates/ingest/schedule_confirm.html | 6 + neoexchange/ingest/views.py | 3 +- 13 files changed, 85 insertions(+), 164 deletions(-) rename docker/neoexchange.dockerfile => Dockerfile (87%) delete mode 100644 Makefile rename config/{neoexchange.ini => processes.ini} (100%) delete mode 100755 docker/bin/loaddata.sh delete mode 100755 docker/bin/nginx.sh delete mode 100755 docker/bin/run.sh delete mode 100755 docker/bin/stop.sh delete mode 100755 docker/bin/uwsgi.sh rename neoexchange/ingest/templates/ingest/{500x.html => 500.html} (100%) diff --git a/docker/neoexchange.dockerfile b/Dockerfile similarity index 87% rename from docker/neoexchange.dockerfile rename to Dockerfile index 5d25e12d6..9eb852e51 100644 --- a/docker/neoexchange.dockerfile +++ b/Dockerfile @@ -10,11 +10,14 @@ # just default to using the nginx port only (recommended). There is no # requirement to map all exposed container ports onto host ports. # -# To run with nginx only: -# docker run -d -p 8200:8200 --name=neoexchange lcogtwebmaster/lcogt:neoexchange_$BRANCH +# Build with +# docker build -t registry.lcogt.net/neoexchange:latest . +# +# Push to Registry with +# docker push registry.lcogt.net/neoexchange:latest # # To run with nginx + uwsgi both exposed: -# docker run -d -p 8200:8200 -p 8201:8201 --name=neox lcogtwebmaster/lcogt:neoexchange_$BRANCH +# docker run -d -p 8200:8200 -p 8201:8201 --name=neox registry.lcogt.net/neoexchange:latest # # See the notes in the code below about NFS mounts. # @@ -40,7 +43,7 @@ ENV BRANCH ${BRANCH} # Copy configuration files COPY config/uwsgi.ini /etc/uwsgi.ini COPY config/nginx/* /etc/nginx/ -COPY config/neoexchange.ini /etc/supervisord.d/neoexchange.ini +COPY config/processes.ini /etc/supervisord.d/processes.ini COPY config/crontab.root /var/spool/cron/root # nginx runs on port 8200, uwsgi runs on port 8201 @@ -62,5 +65,6 @@ RUN pip install pip==1.3 && pip install uwsgi==2.0.8 \ # Setup the LCOGT Mezzanine webapp RUN python /var/www/apps/neoexchange/manage.py validate RUN python /var/www/apps/neoexchange/manage.py collectstatic --noinput +# If any schema changed have happened but not been appliedc RUN python /var/www/apps/neoexchange/manage.py syncdb --noinput RUN python /var/www/apps/neoexchange/manage.py migrate --noinput diff --git a/Makefile b/Makefile deleted file mode 100644 index fa0cd6989..000000000 --- a/Makefile +++ /dev/null @@ -1,56 +0,0 @@ -#----------------------------------------------------------------------------------------------------------------------- -# neoexchange docker image makefile -# -# 'make' will create the docker image needed to run the neoexchange app: -# lcogtwebmaster/lcogt:neoexchange_$BRANCH -# -# where $BRANCH is the git branch name presently in use. -# -# Once built, this image can be pushed up the docker hub repository via 'make install', -# and can then be run via something like: -# -# docker run -d -p 8200:8200 -p 8201:8201 --name=neoexchange lcogtwebmaster/lcogt:neoexchange_$BRANCH -# -# at which point nginx will be exposed on the host at port 8100 -# and uwsgi will be exposed on the host at port 8101 (optional, leave out the -p 8101:8101 argument if you don't need it) -# -# In order to get this work on Fedora (after installing Docker), I needed -# to do (as root or sudo): -# $ systemctl start docker -# $ systemctl enable docker (to get it to restart at boot) -# $ groupadd docker -# $ chown root:docker /var/run/docker.sock -# $ usermod -a -G docker -# (and then log out and back in again as that user) -# -# Ira W. Snyder -# Doug Thomas -# Tim Lister -# LCOGT -# -#----------------------------------------------------------------------------------------------------------------------- - -NAME := lcogtwebmaster/lcogt -BRANCH := $(shell git rev-parse --abbrev-ref HEAD) -BUILDDATE := $(shell date +%Y%m%d%H%M) -TAG1 := neoexchange_$(BRANCH) -ENVSUBST := $(shell command -v envsubst) -$(info $$ENVSUBST is [${ENVSUBST}]) - -.PHONY: all neoexchange login install - -all: neoexchange - -login: - docker login --username="lcogtwebmaster" --password="lc0GT!" --email="webmaster@lcogt.net" - -neoexchange: - export BUILDDATE=$(BUILDDATE) && \ - export BRANCH=$(BRANCH) && \ - cat docker/neoexchange.dockerfile | $(ENVSUBST) '$$BRANCH $$BUILDDATE' > Dockerfile - docker build -t $(NAME):$(TAG1) --rm . - rm -f Dockerfile - -install: login - @if ! docker images $(NAME) | awk '{ print $$2 }' | grep -q -F $(TAG1); then echo "$(NAME):$(TAG1) is not yet built. Please run 'make'"; false; fi - docker push $(NAME):$(TAG1) diff --git a/config/neoexchange.ini b/config/processes.ini similarity index 100% rename from config/neoexchange.ini rename to config/processes.ini diff --git a/docker/bin/loaddata.sh b/docker/bin/loaddata.sh deleted file mode 100755 index 8d83e41ed..000000000 --- a/docker/bin/loaddata.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -cd /var/www/apps/neoexchange -python manage.py loaddata - diff --git a/docker/bin/nginx.sh b/docker/bin/nginx.sh deleted file mode 100755 index 6aab4bbed..000000000 --- a/docker/bin/nginx.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -touch /var/log/nginx/error.log -touch /var/log/nginx/access.log -mkdir -p /run -tail --pid $$ -f /var/log/nginx/access.log & -tail --pid $$ -f /var/log/nginx/error.log & -cat /var/www/apps/neoexchange/docker/config/nginx.conf | envsubst '$PREFIX $NEOEXCHANGE_UWSGI_PORT_8001_TCP_ADDR' > /etc/nginx/nginx.conf -/usr/sbin/nginx -c /etc/nginx/nginx.conf diff --git a/docker/bin/run.sh b/docker/bin/run.sh deleted file mode 100755 index 2273843b8..000000000 --- a/docker/bin/run.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -BRANCH=`git name-rev --name-only HEAD` -docker stop_uwsgi 2>&1 > /dev/null -docker rm_uwsgi 2>&1 > /dev/null -docker stop_nginx 2>&1 > /dev/null -docker rm_nginx 2>&1 > /dev/null -docker login --username="lcogtwebmaster" --password="lc0GT!" --email="webmaster@lcogt.net" -if [ "$DEBUG" != "" ]; then - DEBUGENV="-e DEBUG=True" -fi -if [ "$PREFIX" == "" ]; then - PREFIX="" -fi -docker run -d --name_uwsgi -e PREFIX=$PREFIX $DEBUGENV lcogtwebmaster/lcogt_$BRANCH /var/www/apps/docker/bin/uwsgi.sh -docker run -d --name_nginx -p 8000:8000 -e PREFIX=$PREFIX $DEBUGENV --link_uwsgi_uwsgi lcogtwebmaster/lcogt_$BRANCH /var/www/apps/docker/bin/nginx.sh -if [ "$DEBUG" != "" ]; then - docker logs -f_nginx & - docker logs -f_uwsgi & -fi \ No newline at end of file diff --git a/docker/bin/stop.sh b/docker/bin/stop.sh deleted file mode 100755 index fa7de3c36..000000000 --- a/docker/bin/stop.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -docker stop observations_uwsgi 2>&1 > /dev/null -docker rm observations_uwsgi 2>&1 > /dev/null -docker stop observations_nginx 2>&1 > /dev/null -docker rm observations_nginx 2>&1 > /dev/null \ No newline at end of file diff --git a/docker/bin/uwsgi.sh b/docker/bin/uwsgi.sh deleted file mode 100755 index e3ca8194b..000000000 --- a/docker/bin/uwsgi.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -touch /var/log/uwsgi.log -tail --pid $$ -f /var/log/uwsgi.log & -/usr/bin/uwsgi --ini /var/www/apps/neoexchange/docker/config/uwsgi.ini \ No newline at end of file diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/ingest/ephem_subs.py index ae5c83b73..96ca98a56 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/ingest/ephem_subs.py @@ -31,9 +31,9 @@ def compute_phase_angle(r, delta, es_Rsq, dbg=False): '''Method to compute the phase angle (beta), trapping bad values''' # Compute phase angle, beta (Sun-Target-Earth angle) - if dbg: print "r,r^2,delta,delta^2,es_Rsq=",r,r*r,delta,delta*delta,es_Rsq + logger.debug("r,r^2,delta,delta^2,es_Rsq=",r,r*r,delta,delta*delta,es_Rsq) arg = (r*r+delta*delta-es_Rsq)/(2.0*r*delta) - if dbg: print "arg=", arg + logger.debug("arg=%s" % arg) if arg >= 1.0: beta = 0.0 @@ -42,8 +42,7 @@ def compute_phase_angle(r, delta, es_Rsq, dbg=False): else: beta = acos(arg) - if dbg: print - if dbg: print "Phase angle, beta (deg)=", beta, degrees(beta) + logger.debug("Phase angle, beta (deg)=%s %s" % (beta, degrees(beta))) return beta def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False): @@ -76,33 +75,32 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) else: epoch_mjd = orbelems['epoch'] - if dbg: print 'Element Epoch=', epoch_mjd - if dbg: print 'MJD(UTC) = ', mjd_utc - if dbg: print ' JD(UTC) =', mjd_utc + 2400000.5 + logger.debug('Element Epoch=', epoch_mjd) + logger.debug('MJD(UTC) = ', mjd_utc) + logger.debug(' JD(UTC) =', mjd_utc + 2400000.5) # Convert MJD(UTC) to MJD(TT) mjd_tt = mjd_utc2mjd_tt(mjd_utc) - if dbg: print 'MJD(TT) = %.15f' % (mjd_tt) + logger.debug('MJD(TT) = %.15f' % (mjd_tt)) # Compute UT1-UTC dut = ut1_minus_utc(mjd_utc) - if dbg: print "UT1-UTC =", dut + logger.debug("UT1-UTC =%s" % dut) # Obtain precession-nutation 3x3 rotation matrix # Should really be TDB but "TT will do" says The Wallace... rmat = S.sla_prenut(2000.0, mjd_tt) - if dbg: print rmat + logger.debug(rmat) # Obtain latitude, longitude of the observing site. # Reverse longitude to get the more normal East-positive convention # (site_num, site_name, site_long, site_lat, site_hgt) = S.sla_obs(0, 'SAAO74') # site_long = -site_long (site_name, site_long, site_lat, site_hgt) = get_sitepos(sitecode) - if dbg: print - if dbg: print sitecode, site_name, site_long, site_lat, site_hgt + logger.debug(sitecode, site_name, site_long, site_lat, site_hgt) # Compute local apparent sidereal time # Do GMST first which takes UT1 and then add East longitiude and the equation of the equinoxes @@ -115,10 +113,10 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) pvobs = S.sla_pvobs(site_lat, site_hgt, stl) if site_name == '?': - if dbg: print "WARN: No site co-ordinates found, computing for geocenter" + logger.debug("WARN: No site co-ordinates found, computing for geocenter") pvobs = pvobs * 0.0 - if dbg: print "PVobs(orig)=", pvobs[0:3], "\n ", pvobs[3:6]*86400.0 + logger.debug("PVobs(orig)=%s\n %s" % (pvobs[0:3],pvobs[3:6]*86400.0)) # Apply transpose of precession/nutation matrix to pv vector to go from # true equator and equinox of date to J2000.0 mean equator and equinox (to @@ -127,7 +125,7 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) pos_new = S.sla_dimxv(rmat, pvobs[0:3]) vel_new = S.sla_dimxv(rmat, pvobs[3:6]) pvobs_new = concatenate([pos_new, vel_new]) - if dbg: print "PVobs(new)=", pvobs_new[0:3], "\n ", pvobs_new[3:6]*86400.0 + logger.debug("PVobs(new)=%s\n %s" % (pvobs_new[0:3],pvobs_new[3:6]*86400.0)) # Earth position and velocity @@ -146,18 +144,16 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) # (e_pos_hel, e_vel_hel, e_pos_bar, e_vel_bar ) = ephem.epv(mjd_tt) e_vel_hel = e_vel_hel/86400.0 - if dbg: print - if dbg: print "Sun->Earth [X, Y, Z]=", e_pos_hel - if dbg: print "Sun->Earth [X, Y, Z]= %20.15E %20.15E %20.15E" % (e_pos_hel[0], e_pos_hel[1], e_pos_hel[2]) - if dbg: print "Sun->Earth [Xdot, Ydot, Zdot]=", e_vel_hel - if dbg: print "Sun->Earth [Xdot, Ydot, Zdot]= %20.15E %20.15E %20.15E" % (e_vel_hel[0]*86400.0, e_vel_hel[1]*86400.0, e_vel_hel[2]*86400.0) + logger.debug("Sun->Earth [X, Y, Z]=%s" % e_pos_hel) + logger.debug("Sun->Earth [X, Y, Z]= %20.15E %20.15E %20.15E" % (e_pos_hel[0], e_pos_hel[1], e_pos_hel[2])) + logger.debug("Sun->Earth [Xdot, Ydot, Zdot]=%s" % e_vel_hel) + logger.debug("Sun->Earth [Xdot, Ydot, Zdot]= %20.15E %20.15E %20.15E" % (e_vel_hel[0]*86400.0, e_vel_hel[1]*86400.0, e_vel_hel[2]*86400.0)) # Add topocentric offset in position and velocity e_pos_hel = e_pos_hel + pvobs_new[0:3] e_vel_hel = e_vel_hel + pvobs_new[3:6] - if dbg: print - if dbg: print "Sun->Obsvr [X, Y, Z]=", e_pos_hel - if dbg: print "Sun->Obsvr [Xdot, Ydot, Zdot]=", e_vel_hel + logger.debug("Sun->Obsvr [X, Y, Z]=%s" % e_pos_hel) + logger.debug("Sun->Obsvr [Xdot, Ydot, Zdot]=%s" % e_vel_hel) # Asteroid position (and velocity) @@ -199,7 +195,7 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) orbelems['arg_perihelion'].in_radians(), orbelems['semi_axis'], orbelems['eccentricity'], orbelems['mean_anomaly'].in_radians()) else: - if dbg: print "Not perturbing" + logger.debug("Not perturbing") p_epoch_mjd = epoch_mjd j = 0 else: @@ -232,7 +228,7 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) j = 0 if j != 0: - print "Perturbing error=", j + print "Perturbing error=%s" % j r3 = -100. @@ -256,8 +252,8 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) p_orbelems['MeanAnom'], 0.0) - if dbg: print "Sun->Asteroid [x,y,z]=", pv[0:3], status - if dbg: print "Sun->Asteroid [xdot,ydot,zdot]=", pv[3:6], status + logger.debug("Sun->Asteroid [x,y,z]=%s %s" % (pv[0:3], status)) + logger.debug("Sun->Asteroid [xdot,ydot,zdot]=%s %s" % (pv[3:6], status)) for i, e_pos in enumerate(e_pos_hel): pos[i] = pv[i] - e_pos @@ -265,32 +261,31 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) for i, e_vel in enumerate(e_vel_hel): vel[i] = pv[i+3] - e_vel - if dbg: print "Earth->Asteroid [x,y,z]=", pos - if dbg: print "Earth->Asteroid [xdot,ydot,zdot]=", vel + logger.debug("Earth->Asteroid [x,y,z]=%s" % pos) + logger.debug("Earth->Asteroid [xdot,ydot,zdot]=%s" % vel) # geometric distance, delta (AU) delta = sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]) - if dbg: print - if dbg: print "Geometric distance, delta (AU)=", delta + + logger.debug("Geometric distance, delta (AU)=%s" % delta) # Light travel time to asteroid ltt = tau * delta - if dbg: print "Light travel time (sec, min, days)=", ltt, ltt/60.0, ltt/86400.0 + logger.debug("Light travel time (sec, min, days)=%s %s %s" % (ltt, ltt/60.0, ltt/86400.0)) # Correct position for planetary aberration for i, a_pos in enumerate(pos): pos[i] = a_pos - (ltt * vel[i]) - if dbg: print - if dbg: print "Earth->Asteroid [x,y,z]=", pos - if dbg: print "Earth->Asteroid [x,y,z]= %20.15E %20.15E %20.15E" % (pos[0], pos[1], pos[2]) - if dbg: print "Earth->Asteroid [xdot,ydot,zdot]=", vel*86400.0 + logger.debug("Earth->Asteroid [x,y,z]=%s" % pos) + logger.debug("Earth->Asteroid [x,y,z]= %20.15E %20.15E %20.15E" % (pos[0], pos[1], pos[2])) + logger.debug("Earth->Asteroid [xdot,ydot,zdot]=%s %s %s" % (vel[0]*86400.0,vel[1]*86400.0,vel[2]*86400.0)) # Convert Cartesian to RA, Dec (ra, dec) = S.sla_dcc2s(pos) - if dbg: print "ra,dec=", ra, dec + logger.debug("ra,dec=%s %s" % (ra, dec)) ra = S.sla_dranrm(ra) - if dbg: print "ra,dec=", ra, dec + logger.debug("ra,dec=%s %s" % (ra, dec)) (rsign, ra_geo_deg) = S.sla_dr2tf(2, ra) (dsign, dec_geo_deg) = S.sla_dr2af(1, dec) @@ -300,19 +295,19 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) cposz = pv[2] - (ltt * pv[5]) r = sqrt(cposx*cposx + cposy*cposy + cposz*cposz) - if dbg: print "r (AU) =", r + logger.debug("r (AU) =%s" % r) # Compute R, the Earth-Sun distance. (Only actually need R^2 for the mag. formula) es_Rsq = (e_pos_hel[0]*e_pos_hel[0] + e_pos_hel[1]*e_pos_hel[1] + e_pos_hel[2]*e_pos_hel[2]) - if dbg: print "R (AU) =", sqrt(es_Rsq) - if dbg: print "delta (AU)=", delta + logger.debug("R (AU) =%s" % sqrt(es_Rsq)) + logger.debug("delta (AU)=%s" % delta) # Compute sky motion sky_vel = compute_relative_velocity_vectors(e_pos_hel, e_vel_hel, pos, vel, delta, dbg) - if dbg: print "vel1, vel2, r= %15.10lf %15.10lf %15.10lf" % (sky_vel[1], sky_vel[2], delta) - if dbg: print "vel1, vel2, r= %15.10e %15.10e %15.10lf\n" % (sky_vel[1], sky_vel[2], delta) + logger.debug("vel1, vel2, r= %15.10lf %15.10lf %15.10lf" % (sky_vel[1], sky_vel[2], delta)) + logger.debug("vel1, vel2, r= %15.10e %15.10e %15.10lf\n" % (sky_vel[1], sky_vel[2], delta)) total_motion, sky_pa, ra_motion, dec_motion = compute_sky_motion(sky_vel, delta, dbg) @@ -329,7 +324,7 @@ def compute_ephem(d, orbelems, sitecode, dbg=False, perturb=True, display=False) phi1 = exp(-3.33 * (tan(beta/2.0))**0.63) phi2 = exp(-1.87 * (tan(beta/2.0))**1.22) - # if dbg: print "Phi1, phi2=", phi1,phi2 + # logger.debug("Phi1, phi2=%s" % phi1,phi2) # Calculate magnitude of object mag = p_orbelems['H'] + 5.0 * log10(r * delta) - \ @@ -365,12 +360,12 @@ def compute_relative_velocity_vectors(obs_pos_hel, obs_vel_hel, obj_pos, obj_vel j2000_vel[i] = obj_vel[i] - obs_vel_hel[i] matrix[i] = obj_pos[i] / delta i += 1 - if dbg: print " obj_vel= %15.10f %15.10f %15.10f" % (obj_vel[0], obj_vel[1], obj_vel[2]) - if dbg: print " obs_vel= %15.10f %15.10f %15.10f" % (obs_vel_hel[0], obs_vel_hel[1], obs_vel_hel[2]) - if dbg: print " obs_vel= %15.10e %15.10e %15.10e" % (obs_vel_hel[0], obs_vel_hel[1], obs_vel_hel[2]) + logger.debug(" obj_vel= %15.10f %15.10f %15.10f" % (obj_vel[0], obj_vel[1], obj_vel[2])) + logger.debug(" obs_vel= %15.10f %15.10f %15.10f" % (obs_vel_hel[0], obs_vel_hel[1], obs_vel_hel[2])) + logger.debug(" obs_vel= %15.10e %15.10e %15.10e" % (obs_vel_hel[0], obs_vel_hel[1], obs_vel_hel[2])) - if dbg: print " j2000_vel= %15.10e %15.10e %15.10e" % (j2000_vel[0], j2000_vel[1], j2000_vel[2]) - if dbg: print "matrix_vel= %15.10f %15.10f %15.10f" % (matrix[0], matrix[1], matrix[2] ) + logger.debug(" j2000_vel= %15.10e %15.10e %15.10e" % (j2000_vel[0], j2000_vel[1], j2000_vel[2])) + logger.debug("matrix_vel= %15.10f %15.10f %15.10f" % (matrix[0], matrix[1], matrix[2] )) length = sqrt( matrix[0] * matrix[0] + matrix[1] * matrix[1]) matrix[3] = matrix[1] / length @@ -401,10 +396,10 @@ def compute_sky_motion(sky_vel, delta, dbg=True): dec_motion = dec_motion * 60.0 / 24.0 sky_pa = 180.0 + degrees(atan2(-ra_motion, -dec_motion)) - if dbg: print "RA motion, Dec motion, PA=%10.7f %10.7f %6.1f" % (ra_motion, dec_motion, sky_pa ) + logger.debug( "RA motion, Dec motion, PA=%10.7f %10.7f %6.1f" % (ra_motion, dec_motion, sky_pa )) total_motion = sqrt(ra_motion * ra_motion + dec_motion * dec_motion) - if dbg: print "Total motion=%10.7f" % (total_motion) + logger.debug( "Total motion=%10.7f" % (total_motion)) return (total_motion, sky_pa, ra_motion, dec_motion) @@ -920,8 +915,7 @@ def get_sitepos(site_code, dbg=False): site_name = site_name.rstrip() site_long = -site_long - if dbg: print - if dbg: print site_name, site_long, site_lat, site_hgt + logger.debug(site_name, site_long, site_lat, site_hgt) return (site_name, site_long, site_lat, site_hgt) def moon_ra_dec(date, obsvr_long, obsvr_lat, obsvr_hgt, dbg=False): @@ -938,7 +932,7 @@ def moon_ra_dec(date, obsvr_long, obsvr_lat, obsvr_hgt, dbg=False): # Compute Moon's apparent RA, Dec, diameter (all in radians) (moon_ra, moon_dec, diam) = S.sla_rdplan(mjd_tdb, body, obsvr_long, obsvr_lat) - if dbg: print "Moon RA, Dec, diam=", moon_ra, moon_dec, diam + logger.debug("Moon RA, Dec, diam=%s %s %s" % (moon_ra, moon_dec, diam)) return (moon_ra, moon_dec, diam) def atmos_params(airless): @@ -977,11 +971,11 @@ def moon_alt_az(date, moon_app_ra, moon_app_dec, obsvr_long, obsvr_lat,\ # Compute MJD_UTC mjd_utc = datetime2mjd_utc(date) - if dbg: print mjd_utc + logger.debug(mjd_utc) # Compute UT1-UTC dut = ut1_minus_utc(mjd_utc) - if dbg: print dut + logger.debug(dut) # Perform apparent->observed place transformation (obs_az, obs_zd, obs_ha, obs_dec, obs_ra) = S.sla_aop(moon_app_ra, moon_app_dec,\ mjd_utc, dut, obsvr_long, obsvr_lat, obsvr_hgt, xp, yp, \ @@ -993,7 +987,7 @@ def moon_alt_az(date, moon_app_ra, moon_app_dec, obsvr_long, obsvr_lat,\ # due to observers' elevation above sea level) obs_alt = (pi/2.0)-obs_zd - if dbg: print obs_az, obs_zd, obs_alt + logger.debug(obs_az, obs_zd, obs_alt) return (obs_az, obs_alt) def moonphase(date, obsvr_long, obsvr_lat, obsvr_hgt, dbg=False): @@ -1005,7 +999,7 @@ def moonphase(date, obsvr_long, obsvr_lat, obsvr_hgt, dbg=False): cosphi = ( sin(sun_dec) * sin(moon_dec) + cos(sun_dec) \ * cos(moon_dec) * cos(sun_ra - moon_ra) ) - if dbg: print "cos(phi)=", cosphi + logger.debug("cos(phi)=%s" % cosphi) # Full formula for phase angle, i. Requires r (Earth-Sun distance) and del(ta) (the # Earth-Moon distance) neither of which we have with our methods. However Meeus @@ -1014,7 +1008,7 @@ def moonphase(date, obsvr_long, obsvr_lat, obsvr_hgt, dbg=False): # i = atan2( r * sin(phi), del - r * cos(phi) ) cosi = -cosphi - if dbg: print "cos(i)=", cosi + logger.debug("cos(i)=%s" % cosi) mphase = (1.0 + cosi) / 2.0 return mphase @@ -1039,11 +1033,11 @@ def compute_hourangle(date, obsvr_long, obsvr_lat, obsvr_hgt, mean_ra, mean_dec, print 'GMST, LAST, EQEQX, GAST, long=', gmst, stl, S.sla_eqeqx(mjd_tdb), gmst+S.sla_eqeqx(mjd_tdb), obsvr_long (app_ra, app_dec) = S.sla_map(mean_ra, mean_dec, 0.0, 0.0, 0.0, 0.0, 2000.0, mjd_tdb) - if dbg: print app_ra, app_dec, radec2strings(app_ra, app_dec) + logger.debug(app_ra, app_dec, radec2strings(app_ra, app_dec)) hour_angle = stl - app_ra - if dbg: print hour_angle + logger.debug(hour_angle) hour_angle = S.sla_drange(hour_angle) - if dbg: print hour_angle + logger.debug(hour_angle) return hour_angle diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index 65cb34948..a1488fe9f 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -48,3 +48,17 @@ class ScheduleBlockForm(forms.Form): site_code = forms.CharField(max_length=5,widget=forms.HiddenInput()) group_id = forms.CharField(max_length=30,widget=forms.HiddenInput()) + def clean_start(self): + start = self.cleaned_data['start_time'] + if start <= datetime.now(): + raise forms.ValidationError("Window cannot start in the past") + else: + return self.cleaned_data + + def clean_end(self): + end = self.cleaned_data['end_time'] + if end <= datetime.now(): + raise forms.ValidationError("Window cannot end in the past") + else: + return self.cleaned_data + diff --git a/neoexchange/ingest/templates/ingest/500x.html b/neoexchange/ingest/templates/ingest/500.html similarity index 100% rename from neoexchange/ingest/templates/ingest/500x.html rename to neoexchange/ingest/templates/ingest/500.html diff --git a/neoexchange/ingest/templates/ingest/schedule_confirm.html b/neoexchange/ingest/templates/ingest/schedule_confirm.html index 6cf2cbf2e..408c32be1 100644 --- a/neoexchange/ingest/templates/ingest/schedule_confirm.html +++ b/neoexchange/ingest/templates/ingest/schedule_confirm.html @@ -68,7 +68,13 @@

    Calculated characteristics

    {% csrf_token %} {{form}} + {% for key, error in form.errors.items %} +
    + {{ error }} +
    + {% empty %} + {% endfor %} diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index 639e3e56c..ce6ba77a4 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -158,7 +158,6 @@ def post(self, request, *args, **kwargs): def form_valid(self, form): response = schedule_submit(form.cleaned_data, self.object) - logger.debug(response) return super(ScheduleSubmit, self).form_valid(form) def get_success_url(self): @@ -167,7 +166,7 @@ def get_success_url(self): class ScheduleConfirm(View): ''' Controls the Scheduling forms - GET will render the intermedia confirmation page (including hidden form) - SchedConfDisplay + GET will render the intermediate confirmation page (including hidden form) - SchedConfDisplay POST will post to the scheduler - ScheduleSubmit ''' From 9120d2a7b4b13acf9a09a613971b5bf770b28015 Mon Sep 17 00:00:00 2001 From: zemogle Date: Tue, 2 Jun 2015 14:15:57 +0100 Subject: [PATCH 24/48] Stashed features --- docker/neoexchange.dockerfile | 66 ------------------- neoexchange/ingest/ephem_subs.py | 2 +- neoexchange/ingest/forms.py | 17 +++++ .../ingest/management/commands/runserver.py | 58 ++++++++++++++++ .../ingest/static/ingest/css/styles.css | 2 +- .../ingest/templates/{ingest => }/403.html | 0 .../ingest/templates/{ingest => }/404.html | 0 .../templates/{ingest/500x.html => 500.html} | 0 .../ingest/templates/ingest/ephem.html | 1 + neoexchange/ingest/tests/test_views.py | 57 +++++++++++++++- neoexchange/ingest/views.py | 31 +++++---- neoexchange/neox/settings.py | 2 +- neoexchange/pip_requirements.txt | 1 + 13 files changed, 155 insertions(+), 82 deletions(-) delete mode 100644 docker/neoexchange.dockerfile create mode 100644 neoexchange/ingest/management/commands/runserver.py rename neoexchange/ingest/templates/{ingest => }/403.html (100%) rename neoexchange/ingest/templates/{ingest => }/404.html (100%) rename neoexchange/ingest/templates/{ingest/500x.html => 500.html} (100%) diff --git a/docker/neoexchange.dockerfile b/docker/neoexchange.dockerfile deleted file mode 100644 index 0f78292ad..000000000 --- a/docker/neoexchange.dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -################################################################################ -# -# Runs the LCOGT Python Django NEO Exchange webapp using nginx + uwsgi -# -# The decision to run both nginx and uwsgi in the same container was made because -# it avoids duplicating all of the Python code and static files in two containers. -# It is convenient to have the whole webapp logically grouped into the same container. -# -# You can choose to expose the nginx and uwsgi ports separately, or you can -# just default to using the nginx port only (recommended). There is no -# requirement to map all exposed container ports onto host ports. -# -# To run with nginx only: -# docker run -d -p 8200:8200 --name=neoexchange lcogtwebmaster/lcogt:neoexchange_$BRANCH -# -# To run with nginx + uwsgi both exposed: -# docker run -d -p 8200:8200 -p 8201:8201 --name=neox lcogtwebmaster/lcogt:neoexchange_$BRANCH -# -# See the notes in the code below about NFS mounts. -# -################################################################################ -FROM centos:centos7 -MAINTAINER LCOGT - -# Install package repositories -RUN yum -y install epel-release - -# Install packages and update base system -RUN yum -y install nginx python-pip mysql-devel python-devel supervisor -RUN yum -y groupinstall "Development Tools" -RUN yum -y update - -# Copy the LCOGT Mezzanine webapp files -COPY neoexchange /var/www/apps/neoexchange - -# Install the LCOGT NEO exchange Python required packages -RUN pip install pip==1.3 && pip install uwsgi==2.0.8 -RUN pip install -r /var/www/apps/neoexchange/pip_requirements.txt -# LCOGT packages which have to be installed after the normal pip install -RUN pip install pyslalib --extra-index-url=http://buildsba.lco.gtn/python/ -RUN pip install rise_set --extra-index-url=http://buildsba.lco.gtn/python/ - - -# Setup the Python Django environment -ENV PYTHONPATH /var/www/apps -ENV DJANGO_SETTINGS_MODULE neox.settings -ENV BRANCH ${BRANCH} -#ENV BUILDDATE ${BUILDDATE} - -# Setup the LCOGT Mezzanine webapp -RUN python /var/www/apps/neoexchange/manage.py validate -RUN python /var/www/apps/neoexchange/manage.py collectstatic --noinput -RUN python /var/www/apps/neoexchange/manage.py syncdb --noinput -RUN python /var/www/apps/neoexchange/manage.py migrate --noinput - -# Copy configuration files -COPY config/uwsgi.ini /etc/uwsgi.ini -COPY config/nginx/* /etc/nginx/ -COPY config/neoexchange.ini /etc/supervisord.d/neoexchange.ini -COPY config/crontab.root /var/spool/cron/root - -# nginx runs on port 8200, uwsgi runs on port 8201 -EXPOSE 8200 8201 - -# Entry point is the supervisord daemon -ENTRYPOINT [ "/usr/bin/supervisord", "-n" ] diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/ingest/ephem_subs.py index ae5c83b73..28567a1ed 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/ingest/ephem_subs.py @@ -31,7 +31,7 @@ def compute_phase_angle(r, delta, es_Rsq, dbg=False): '''Method to compute the phase angle (beta), trapping bad values''' # Compute phase angle, beta (Sun-Target-Earth angle) - if dbg: print "r,r^2,delta,delta^2,es_Rsq=",r,r*r,delta,delta*delta,es_Rsq + logger.debug("r(%s), r^2 (%s),delta (%s),delta^2 (%s), es_Rsq (%s)" % (r,r*r,delta,delta*delta,es_Rsq)) arg = (r*r+delta*delta-es_Rsq)/(2.0*r*delta) if dbg: print "arg=", arg diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index 65cb34948..e649ad47d 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -38,6 +38,12 @@ class ScheduleForm(forms.Form): # return body[0] # elif body.count() == 0: # raise forms.ValidationError("Object not found.") + def clean_utc_date(self): + start = self.cleaned_data['utc_date'] + if start < datetime.now().date(): + raise forms.ValidationError("Window cannot start in the past") + return start + class ScheduleBlockForm(forms.Form): start_time = forms.DateTimeField(widget=forms.HiddenInput()) @@ -48,3 +54,14 @@ class ScheduleBlockForm(forms.Form): site_code = forms.CharField(max_length=5,widget=forms.HiddenInput()) group_id = forms.CharField(max_length=30,widget=forms.HiddenInput()) + def clean_start(self): + start = self.cleaned_data['start_time'] + if start <= datetime.now(): + raise forms.ValidationError("Window cannot start in the past") + return start + + def clean_end(self): + end = self.cleaned_data['end_time'] + if end <= datetime.now(): + raise forms.ValidationError("Window cannot end in the past") + return end diff --git a/neoexchange/ingest/management/commands/runserver.py b/neoexchange/ingest/management/commands/runserver.py new file mode 100644 index 000000000..9317b8495 --- /dev/null +++ b/neoexchange/ingest/management/commands/runserver.py @@ -0,0 +1,58 @@ +import os +import platform +import sys + +import django +from django.conf import settings +from django.contrib.staticfiles.management.commands import runserver +from django.core.management.commands.runserver import BaseRunserverCommand +from django.contrib.staticfiles.handlers import StaticFilesHandler +from django.db import connection +from django.http import Http404 +from django.utils.termcolors import colorize +from django.views.static import serve + +def banner(): + + # The raw banner split into lines. + lines = (""" +.__ __. _______ ______ ___ ___ +| \ | | | ____| / __ \ \ \ / / +| \| | | |__ | | | | \ V / +| . ` | | __| | | | | > < +| |\ | | |____ | `--' | / . \ +|__| \__| |_______| \______/ /__/ \__\ + +* Django %(django_version)s +* Python %(python_version)s +* %(os_name)s %(os_version)s + +""" % { + "django_version": django.get_version(), + "python_version": sys.version.split(" ", 1)[0], + "os_name": platform.system(), + "os_version": platform.release(), + }).splitlines() + if django.VERSION >= (1, 7): + lines = lines[2:] + + return "\n".join(lines) + + +class Command(BaseRunserverCommand): + """ + Overrides runserver to display an ODIN banner + """ + + def inner_run(self, *args, **kwargs): + # Show the funky ODIN banner in the terminal. There + # aren't really any exceptions to catch here, but we do + # so blanketly since such a trivial thing like the banner + # shouldn't be able to crash the development server. + + try: + self.stdout.write(banner()) + except Exception, e: + print e + pass + super(Command, self).inner_run(*args, **kwargs) \ No newline at end of file diff --git a/neoexchange/ingest/static/ingest/css/styles.css b/neoexchange/ingest/static/ingest/css/styles.css index 5fc9a3b87..671154f41 100644 --- a/neoexchange/ingest/static/ingest/css/styles.css +++ b/neoexchange/ingest/static/ingest/css/styles.css @@ -23,7 +23,7 @@ img { border: 0px; } .padded-horizontal { padding-top: 0px; padding-bottom: 0px; } #lcogt-bar { -o-background-size: 100% 100%;-moz-background-size: 100% 100%;-webkit-background-size: 100% 100%;background-size: 100% 100%;/* Internet Explorer */*background: #33363c;background: #33363c;filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr=#FF222429, endColorstr=#FF33363c);/* Recent browsers */background-image: -webkit-gradient(linear,left top, left bottom,from(#222429),to(#33363c));background-image: -webkit-linear-gradient(top,#222429,#33363c);background-image: -moz-linear-gradient(top,#222429,#33363c);background-image: -o-linear-gradient(top,#222429,#33363c);background-image: linear-gradient(top,#222429,#33363c);color: white; } div.compactonly { display: none; } -#header-holder { background: url("http://lcogt.net/sites/default/themes/lcogt/images/new-header-bg.png") no-repeat scroll -100px top #d4d4d4; } +#header-holder { background: url("http://lcogt.net/files/new-header-bg.png") no-repeat scroll -100px top #d4d4d4; } #lcogt-bar { margin: 0; height: 32px; clear:both; line-height: 26px; color: #cccccc; font-size: 0.9em; overflow: hidden; } #lcogt-bar a { color: #e6e6e6; text-decoration: none; } #lcogt-bar ul { margin: 0; padding: 0; list-style: none; } diff --git a/neoexchange/ingest/templates/ingest/403.html b/neoexchange/ingest/templates/403.html similarity index 100% rename from neoexchange/ingest/templates/ingest/403.html rename to neoexchange/ingest/templates/403.html diff --git a/neoexchange/ingest/templates/ingest/404.html b/neoexchange/ingest/templates/404.html similarity index 100% rename from neoexchange/ingest/templates/ingest/404.html rename to neoexchange/ingest/templates/404.html diff --git a/neoexchange/ingest/templates/ingest/500x.html b/neoexchange/ingest/templates/500.html similarity index 100% rename from neoexchange/ingest/templates/ingest/500x.html rename to neoexchange/ingest/templates/500.html diff --git a/neoexchange/ingest/templates/ingest/ephem.html b/neoexchange/ingest/templates/ingest/ephem.html index 4a9533b62..77a04d815 100644 --- a/neoexchange/ingest/templates/ingest/ephem.html +++ b/neoexchange/ingest/templates/ingest/ephem.html @@ -18,6 +18,7 @@

    Ephemeris for {{ target.current_name
    diff --git a/neoexchange/ingest/tests/test_views.py b/neoexchange/ingest/tests/test_views.py index ee29186fa..046ce17d4 100644 --- a/neoexchange/ingest/tests/test_views.py +++ b/neoexchange/ingest/tests/test_views.py @@ -25,7 +25,7 @@ #Import module to test from ingest.ephem_subs import call_compute_ephem, determine_darkness_times -from ingest.views import home, clean_NEOCP_object +from ingest.views import home, clean_NEOCP_object, save_and_make_revision from ingest.models import Body, Proposal from ingest.forms import EphemQuery @@ -81,6 +81,61 @@ def test_missing_absmag(self): for element in expected_elements: self.assertEqual(expected_elements[element], elements[element]) + def save_N007riz(self): + obj_id ='N007riz' + elements = { 'abs_mag' : 23.9, + 'slope' : 0.15, + 'epochofel' : datetime(2015, 3, 19, 0, 0, 0), + 'meananom' : 340.52798, + 'argofperih' : 59.01148, + 'longascnode' : 160.84695, + 'orbinc' : 10.51732, + 'eccentricity': 0.3080134, + 'meandist' : 1.4439768, + 'elements_type': 'MPC_MINOR_PLANET', + 'origin' : 'M', + 'source_type' : 'U', + 'active' : True + } + body, created = Body.objects.get_or_create(provisional_name=obj_id) + # We are creating this object + self.assertEqual(True,created) + resp = save_and_make_revision(body,elements) + # We are saving all the detailing elements + self.assertEqual(True,resp) + + def test_revise_N007riz(self): + self.save_N007riz() + obj_id ='N007riz' + elements = { 'abs_mag' : 23.9, + 'slope' : 0.15, + 'epochofel' : datetime(2015, 4, 19, 0, 0, 0), + 'meananom' : 340.52798, + 'argofperih' : 59.01148, + 'longascnode' : 160.84695, + 'orbinc' : 10.51732, + 'eccentricity': 0.4080134, + 'meandist' : 1.4439768, + 'elements_type': 'MPC_MINOR_PLANET', + 'origin' : 'M', + 'source_type' : 'U', + 'active' : False + } + body, created = Body.objects.get_or_create(provisional_name=obj_id) + # Created should now be false + self.assertEqual(False, created) + resp = save_and_make_revision(body,elements) + # Saving the new elements + self.assertEqual(True,resp) + + def test_update_MPC(self): + + def test_update_MPC_duplicate(self): + self.save_N007riz() + obj_id ='N007riz' + update_MPC_orbit(obj_id) + + class HomePageTest(TestCase): def setUp(self): diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index 639e3e56c..a624d3bb5 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -35,7 +35,7 @@ def home(request): latest = Body.objects.latest('ingest') - newest = Body.objects.filter(ingest=latest.ingest) + newest = Body.objects.filter(ingest=latest.ingest,active=True) params = { 'targets' : Body.objects.filter(active=True).count(), 'blocks' : Block.objects.filter(active=True).count(), @@ -288,7 +288,7 @@ def update_NEOCP_orbit(obj_id, dbg=False): save_and_make_revision(body,kwargs) logger.info("Added %s" % obj_id) else: - save_and_make_revision(check_body,{'active':False}) + save_and_make_revision(body,{'active':False}) logger.info("Object %s no longer exists on the NEOCP." % obj_id) return True @@ -454,20 +454,27 @@ def clean_mpcorbit(elements, dbg=False, origin='M'): def update_MPC_orbit(obj_id, dbg=False, origin='M'): - + ''' + Performs remote look up of orbital elements for object with id obj_id, + Gets or creates corresponding Body instance and updates entry + ''' elements = fetch_mpcorbit(obj_id, dbg) - - body, created = Body.objects.get_or_create(name=obj_id) + try: + body, created = Body.objects.get_or_create(name=obj_id) + except MultipleObjectsReturned: + # When the crossid happens we end up with multiple versions of the body. + # Need to pick the one has been most recently updated + bodies = Body.objects.filter(name=obj_id,provisional_name__isnull=False).order('-ingest') + created = False + if not bodies: + bodies = Body.objects.filter(name=obj_id).order('-ingest') + body = bodies[0] # Determine what type of new object it is and whether to keep it active kwargs = clean_mpcorbit(elements, dbg, origin) + # Save, make revision, or do not update depending on the what has happened to the object + save_and_make_revision(body,kwargs) if not created: - # Find out if the details have changed, if they have, save a revision - check_body = Body.objects.filter(name=obj_id, **kwargs) - if check_body.count() == 1 and check_body[0] == body: - if save_and_make_revision(check_body[0],kwargs): - logger.info("Updated elements for %s" % obj_id) + logger.info("Updated elements for %s" % obj_id) else: - # Didn't know about this object before so create - save_and_make_revision(body,kwargs) logger.info("Added new orbit for %s" % obj_id) return True diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 9c1d6e3d5..4a30b90df 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -173,7 +173,7 @@ }, 'ingest' : { 'handlers' : ['file','console'], - 'level' : 'INFO', + 'level' : 'DEBUG', } } } diff --git a/neoexchange/pip_requirements.txt b/neoexchange/pip_requirements.txt index aacdf50b2..74c5581e9 100644 --- a/neoexchange/pip_requirements.txt +++ b/neoexchange/pip_requirements.txt @@ -13,5 +13,6 @@ numpy==1.9.2 pytz==2015.2 six==1.9.0 wsgiref==0.1.2 +requests selenium reqdbclient From ea36a1bb956a512c4a0c386e55eeb7205d6a6199 Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Fri, 5 Jun 2015 10:09:15 +0100 Subject: [PATCH 25/48] New runserver banner --- .../ingest/management/commands/runserver.py | 58 +++++++++++++++++++ neoexchange/ingest/templates/403.html | 6 ++ neoexchange/ingest/templates/404.html | 6 ++ neoexchange/ingest/templates/500.html | 6 ++ 4 files changed, 76 insertions(+) create mode 100644 neoexchange/ingest/management/commands/runserver.py create mode 100644 neoexchange/ingest/templates/403.html create mode 100644 neoexchange/ingest/templates/404.html create mode 100644 neoexchange/ingest/templates/500.html diff --git a/neoexchange/ingest/management/commands/runserver.py b/neoexchange/ingest/management/commands/runserver.py new file mode 100644 index 000000000..9317b8495 --- /dev/null +++ b/neoexchange/ingest/management/commands/runserver.py @@ -0,0 +1,58 @@ +import os +import platform +import sys + +import django +from django.conf import settings +from django.contrib.staticfiles.management.commands import runserver +from django.core.management.commands.runserver import BaseRunserverCommand +from django.contrib.staticfiles.handlers import StaticFilesHandler +from django.db import connection +from django.http import Http404 +from django.utils.termcolors import colorize +from django.views.static import serve + +def banner(): + + # The raw banner split into lines. + lines = (""" +.__ __. _______ ______ ___ ___ +| \ | | | ____| / __ \ \ \ / / +| \| | | |__ | | | | \ V / +| . ` | | __| | | | | > < +| |\ | | |____ | `--' | / . \ +|__| \__| |_______| \______/ /__/ \__\ + +* Django %(django_version)s +* Python %(python_version)s +* %(os_name)s %(os_version)s + +""" % { + "django_version": django.get_version(), + "python_version": sys.version.split(" ", 1)[0], + "os_name": platform.system(), + "os_version": platform.release(), + }).splitlines() + if django.VERSION >= (1, 7): + lines = lines[2:] + + return "\n".join(lines) + + +class Command(BaseRunserverCommand): + """ + Overrides runserver to display an ODIN banner + """ + + def inner_run(self, *args, **kwargs): + # Show the funky ODIN banner in the terminal. There + # aren't really any exceptions to catch here, but we do + # so blanketly since such a trivial thing like the banner + # shouldn't be able to crash the development server. + + try: + self.stdout.write(banner()) + except Exception, e: + print e + pass + super(Command, self).inner_run(*args, **kwargs) \ No newline at end of file diff --git a/neoexchange/ingest/templates/403.html b/neoexchange/ingest/templates/403.html new file mode 100644 index 000000000..9cce29d52 --- /dev/null +++ b/neoexchange/ingest/templates/403.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% block title %}Access denied{% endblock %} + +{% block content %} +You do not have permission to access this resource. +{% endblock %} diff --git a/neoexchange/ingest/templates/404.html b/neoexchange/ingest/templates/404.html new file mode 100644 index 000000000..281fe9caf --- /dev/null +++ b/neoexchange/ingest/templates/404.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% block title %}Page not found{% endblock %} + +{% block content %} +The page you requested could not be found. +{% endblock %} diff --git a/neoexchange/ingest/templates/500.html b/neoexchange/ingest/templates/500.html new file mode 100644 index 000000000..ea97b712a --- /dev/null +++ b/neoexchange/ingest/templates/500.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% block title %}Server error{% endblock %} + +{% block content %} +There was an error while handling your request. +{% endblock %} From 65591a1d2477897f9e900e28286d79d8ec01c813 Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Fri, 5 Jun 2015 11:03:46 +0100 Subject: [PATCH 26/48] Refactor of tempaltes --- neoexchange/neox/settings.py | 27 +++++-------------- ...{pip_requirements.txt => requirements.txt} | 2 +- 2 files changed, 7 insertions(+), 22 deletions(-) rename neoexchange/{pip_requirements.txt => requirements.txt} (94%) diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 4a30b90df..6366c94d3 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -15,8 +15,6 @@ PREFIX ="" BASE_DIR = os.path.dirname(CURRENT_PATH) -TEMPLATE_DEBUG = DEBUG - ADMINS = ( # ('Your Name', 'your_email@example.com'), ) @@ -73,18 +71,6 @@ # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', -) - -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.core.context_processors.request", - 'django.contrib.auth.context_processors.auth', -) - MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -101,12 +87,12 @@ # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'neox.wsgi.application' -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - os.path.join(BASE_DIR,'ingest','templates'), -) +TEMPLATES = [ + { + 'BACKEND' : 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS' : True, + }, +] # GRAPPELLI_INDEX_DASHBOARD = 'neox.dashboard.CustomIndexDashboard' @@ -122,7 +108,6 @@ 'neox', 'ingest', 'reversion', - 'south' ) LOGGING = { diff --git a/neoexchange/pip_requirements.txt b/neoexchange/requirements.txt similarity index 94% rename from neoexchange/pip_requirements.txt rename to neoexchange/requirements.txt index 74c5581e9..914025661 100644 --- a/neoexchange/pip_requirements.txt +++ b/neoexchange/requirements.txt @@ -1,5 +1,5 @@ --extra-index-url=http://buildsba.lco.gtn/python/ -Django==1.4.20 +Django>1.8,<1.9 MySQL-python==1.2.5 South==0.7.6 astropy==1.0 From 06fbd7a37735cbf7e9a3e5325619b5d924af0399 Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Fri, 5 Jun 2015 14:55:13 +0100 Subject: [PATCH 27/48] Working django 1.8 version --- neoexchange/ingest/ephem_subs.py | 4 -- neoexchange/ingest/forms.py | 8 --- .../ingest/management/commands/runserver.py | 1 + neoexchange/ingest/models.py | 4 +- .../ingest/templates/ingest/schedule.html | 2 +- neoexchange/ingest/views.py | 14 ++--- neoexchange/neox/settings.py | 52 +++++++++++++------ neoexchange/neox/urls.py | 19 ++++--- neoexchange/requirements.txt | 9 ++-- 9 files changed, 65 insertions(+), 48 deletions(-) diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/ingest/ephem_subs.py index 0ee1789a7..98cf22f5d 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/ingest/ephem_subs.py @@ -31,11 +31,7 @@ def compute_phase_angle(r, delta, es_Rsq, dbg=False): '''Method to compute the phase angle (beta), trapping bad values''' # Compute phase angle, beta (Sun-Target-Earth angle) -<<<<<<< HEAD logger.debug("r(%s), r^2 (%s),delta (%s),delta^2 (%s), es_Rsq (%s)" % (r,r*r,delta,delta*delta,es_Rsq)) -======= - logger.debug("r,r^2,delta,delta^2,es_Rsq=",r,r*r,delta,delta*delta,es_Rsq) ->>>>>>> release arg = (r*r+delta*delta-es_Rsq)/(2.0*r*delta) logger.debug("arg=%s" % arg) diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index 1bc13191f..20641e69f 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -58,21 +58,13 @@ def clean_start(self): start = self.cleaned_data['start_time'] if start <= datetime.now(): raise forms.ValidationError("Window cannot start in the past") -<<<<<<< HEAD - return start -======= else: return self.cleaned_data ->>>>>>> release def clean_end(self): end = self.cleaned_data['end_time'] if end <= datetime.now(): raise forms.ValidationError("Window cannot end in the past") -<<<<<<< HEAD - return end -======= else: return self.cleaned_data ->>>>>>> release diff --git a/neoexchange/ingest/management/commands/runserver.py b/neoexchange/ingest/management/commands/runserver.py index 9317b8495..2ef3477ca 100644 --- a/neoexchange/ingest/management/commands/runserver.py +++ b/neoexchange/ingest/management/commands/runserver.py @@ -16,6 +16,7 @@ def banner(): # The raw banner split into lines. lines = (""" + .__ __. _______ ______ ___ ___ | \ | | | ____| / __ \ \ \ / / | \| | | |__ | | | | \ V / diff --git a/neoexchange/ingest/models.py b/neoexchange/ingest/models.py index a49a77d0a..d41f8cabe 100644 --- a/neoexchange/ingest/models.py +++ b/neoexchange/ingest/models.py @@ -13,7 +13,7 @@ GNU General Public License for more details. ''' from django.db import models -from datetime import datetime +from django.utils.timezone import now from django.utils.translation import ugettext as _ from astropy.time import Time import reversion @@ -105,7 +105,7 @@ class Body(models.Model): epochofperih = models.DateTimeField('Epoch of perihelion', blank=True, null=True, help_text='for comets') abs_mag = models.FloatField('H - absolute magnitude', blank=True, null=True) slope = models.FloatField('G - slope parameter', blank=True, null=True) - ingest = models.DateTimeField(default=datetime.now()) + ingest = models.DateTimeField(default=now) def epochofel_mjd(self): t = Time(self.epochofel.isoformat(), format='isot', scale='tt') diff --git a/neoexchange/ingest/templates/ingest/schedule.html b/neoexchange/ingest/templates/ingest/schedule.html index 8c9860976..399f602b4 100644 --- a/neoexchange/ingest/templates/ingest/schedule.html +++ b/neoexchange/ingest/templates/ingest/schedule.html @@ -18,7 +18,7 @@

    Scheduling

    {% csrf_token %} -

    Parameters for: {{body.current_name}}

    +

    Parameters for: {{body.name}}

    Magnitude {{ vars.data.magnitude|floatformat:2 }}
    Speed {{ vars.data.speed|floatformat:2 }} '/min
    Slot length {{ vars.data.slot_length }} mins
    No. of exposures {{ vars.data.exp_count }}
    Exposure length {{ vars.data.exp_length|floatformat:1 }} secs
    diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index 758a4caa5..e4464ec30 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -21,7 +21,7 @@ from django.views.generic import DetailView, ListView, FormView, TemplateView, View from django.views.generic.detail import SingleObjectMixin from ingest.ephem_subs import call_compute_ephem, compute_ephem, \ - determine_darkness_times, determine_slot_length, determine_exp_time_count + determine_darkness_times, determine_slot_length, determine_exp_time_count, MagRangeError from ingest.forms import EphemQuery, ScheduleForm, ScheduleBlockForm from ingest.models import * from ingest.sources_subs import fetchpage_and_make_soup, packed_to_normal, \ @@ -29,6 +29,7 @@ from ingest.time_subs import extract_mpc_epoch, parse_neocp_date import logging import reversion +import json logger = logging.getLogger(__name__) @@ -113,8 +114,8 @@ def post(self, request, *args, **kwargs): def form_valid(self, form): data = schedule_check(form.cleaned_data, self.object, self.ok_to_schedule) - #logger.debug(self.confirm) - self.request.session['confirm'] = {'data': data, 'body':self.object,'form':form} + #logger.debug() + self.request.session['confirm'] = {'data': data, 'body': {'id': self.object.pk,'name': self.object.current_name()},'form':form} return super(ScheduleProcess, self).form_valid(form) def get_success_url(self): @@ -196,6 +197,7 @@ def schedule_check(data,body,ok_to_schedule): try: slot_length = determine_slot_length(body_elements['provisional_name'], magnitude, data['site_code']) except MagRangeError: + slot_length = 0. ok_to_schedule = False # Determine exposure length and count exp_length, exp_count = determine_exp_time_count(speed, data['site_code'], slot_length) @@ -213,9 +215,9 @@ def schedule_check(data,body,ok_to_schedule): 'site_code' : data['site_code'], 'proposal_code' : data['proposal_code'], 'group_id' : body.current_name() + '_' + data['site_code'].upper() + '-' + datetime.strftime(data['utc_date'], '%Y%m%d'), - 'utc_date' : data['utc_date'], - 'start_time' : dark_start, - 'end_time' : dark_end, + 'utc_date' : data['utc_date'].isoformat(), + 'start_time' : dark_start.isoformat(), + 'end_time' : dark_end.isoformat() } return resp diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 6366c94d3..6229337cc 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -5,7 +5,7 @@ CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) PRODUCTION = True if CURRENT_PATH.startswith('/var/www') else False -DEBUG = False +DEBUG = True BRANCH = os.environ.get('BRANCH',None) if BRANCH: BRANCH = '-' + BRANCH @@ -66,17 +66,15 @@ # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', -) + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder" + ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.transaction.TransactionMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', @@ -89,8 +87,17 @@ TEMPLATES = [ { - 'BACKEND' : 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS' : True, + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, }, ] @@ -98,6 +105,8 @@ INSTALLED_APPS = ( 'grappelli', + 'neox', + 'ingest', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -105,8 +114,6 @@ 'django.contrib.staticfiles', 'django.contrib.admin', 'django.contrib.messages', - 'neox', - 'ingest', 'reversion', ) @@ -163,6 +170,21 @@ } } +SECRET_KEY = os.environ['SECRET_KEY'] + +DATABASES = { + "default": { + # Live DB + "ENGINE": "django.db.backends.mysql", + "NAME": "neoexchange", + "USER": os.environ['NEOX_DB_USER'], + "PASSWORD": os.environ['NEOX_DB_PASSWD'], + "HOST": os.environ['NEOX_DB_HOST'], + "OPTIONS" : {'init_command': 'SET storage_engine=INNODB'}, + + } +} + ####################### # Test Database setup # ####################### @@ -192,10 +214,10 @@ # Allow any settings to be defined in local_settings.py which should be # ignored in your version control system allowing for settings to be # defined per machine. -try: - from local_settings import * -except ImportError as e: - if "local_settings" not in str(e): - raise e +# try: +# from local_settings import * +# except ImportError as e: +# if "local_settings" not in str(e): +# raise e diff --git a/neoexchange/neox/urls.py b/neoexchange/neox/urls.py index d9a406550..c88800a14 100644 --- a/neoexchange/neox/urls.py +++ b/neoexchange/neox/urls.py @@ -13,26 +13,31 @@ GNU General Public License for more details. ''' from django.conf import settings -from django.conf.urls import patterns, include, url -from django.conf.urls.static import static +from django.conf.urls import include, url +from django.contrib.staticfiles import views from django.contrib import admin from django.views.generic import ListView, DetailView from ingest.models import Body, Block -from ingest.views import BodySearchView,BodyDetailView, ScheduleCheck, ScheduleSuccess, SchedFormDisplay, ScheduleConfirm +from ingest.views import BodySearchView,BodyDetailView, ScheduleCheck, ScheduleSuccess, SchedFormDisplay, ScheduleConfirm, ephemeris, home admin.autodiscover() -urlpatterns = patterns('ingest.views', - url(r'^$', 'home', name='home'), +urlpatterns = [ + url(r'^$', home, name='home'), url(r'^block/list/$', ListView.as_view(model=Block, queryset=Block.objects.filter(active=True).order_by('-block_start'), context_object_name="block_list"), name='blocklist'), url(r'^target/$', ListView.as_view(model=Body, queryset=Body.objects.filter(active=True).order_by('-origin','-ingest'), context_object_name="target_list"), name='targetlist'), url(r'^target/(?P\d+)/$',BodyDetailView.as_view(model=Body), name='target'), url(r'^search/$', BodySearchView.as_view(context_object_name="target_list"), name='search'), - url(r'^ephemeris/$', 'ephemeris', name='ephemeris'), + url(r'^ephemeris/$', ephemeris, name='ephemeris'), url(r'^schedule/(?P\d+)/confirm/$',ScheduleConfirm.as_view(), name='schedule-confirm'), url(r'^schedule/(?P\d+)/$', ScheduleCheck.as_view(), name='schedule-body'), url(r'^schedule/success/$',ScheduleSuccess.as_view(), name='schedule-success'), url(r'^schedule/$', SchedFormDisplay.as_view(), name='schedule'), url(r'^grappelli/', include('grappelli.urls')), url(r'^admin/', include(admin.site.urls)), -)+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +] + +if settings.DEBUG: + urlpatterns += [ + url(r'^static/(?P.*)$', views.serve), + ] \ No newline at end of file diff --git a/neoexchange/requirements.txt b/neoexchange/requirements.txt index 914025661..c1f22421b 100644 --- a/neoexchange/requirements.txt +++ b/neoexchange/requirements.txt @@ -1,18 +1,17 @@ --extra-index-url=http://buildsba.lco.gtn/python/ Django>1.8,<1.9 -MySQL-python==1.2.5 -South==0.7.6 +MySQL-python astropy==1.0 beautifulsoup4==4.3.2 django-grappelli==2.4.12 -django-reversion==1.6.6 +django-reversion==1.8.7 html5lib==1.0b3 -ipython==3.0.0 +ipython nose==1.3.6 numpy==1.9.2 pytz==2015.2 six==1.9.0 -wsgiref==0.1.2 +wsgiref requests selenium reqdbclient From c523545c9f666c13b3a1d4694ba88da34128734f Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Fri, 5 Jun 2015 16:52:07 +0100 Subject: [PATCH 28/48] Simplified scheduling forms --- neoexchange/ingest/forms.py | 20 ++++- .../ingest/templates/ingest/schedule.html | 2 +- .../templates/ingest/schedule_confirm.html | 30 +++---- neoexchange/ingest/views.py | 87 +++++-------------- neoexchange/neox/urls.py | 8 +- 5 files changed, 60 insertions(+), 87 deletions(-) diff --git a/neoexchange/ingest/forms.py b/neoexchange/ingest/forms.py index 20641e69f..316aeb3b2 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/ingest/forms.py @@ -46,25 +46,37 @@ def clean_utc_date(self): class ScheduleBlockForm(forms.Form): - start_time = forms.DateTimeField(widget=forms.HiddenInput()) - end_time = forms.DateTimeField(widget=forms.HiddenInput()) + start_time = forms.DateTimeField(widget=forms.HiddenInput(), input_formats=['%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%S']) + end_time = forms.DateTimeField(widget=forms.HiddenInput(), input_formats=['%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%S']) exp_count = forms.IntegerField(widget=forms.HiddenInput()) exp_length = forms.FloatField(widget=forms.HiddenInput()) proposal_code = forms.CharField(max_length=15,widget=forms.HiddenInput()) site_code = forms.CharField(max_length=5,widget=forms.HiddenInput()) group_id = forms.CharField(max_length=30,widget=forms.HiddenInput()) - def clean_start(self): + def clean_start_time(self): start = self.cleaned_data['start_time'] if start <= datetime.now(): raise forms.ValidationError("Window cannot start in the past") else: return self.cleaned_data - def clean_end(self): + def clean_end_time(self): end = self.cleaned_data['end_time'] if end <= datetime.now(): raise forms.ValidationError("Window cannot end in the past") else: return self.cleaned_data + def clean_exp_length(self): + if self.cleaned_data['exp_length'] > 0.: + return self.cleaned_data + else: + raise forms.ValidationError("Exposure length is too short") + + def clean_exp_count(self): + if self.cleaned_data['exp_count'] > 1: + return self.cleaned_data + else: + raise forms.ValidationError("There must be more than 1 exposure") + diff --git a/neoexchange/ingest/templates/ingest/schedule.html b/neoexchange/ingest/templates/ingest/schedule.html index 399f602b4..8c9860976 100644 --- a/neoexchange/ingest/templates/ingest/schedule.html +++ b/neoexchange/ingest/templates/ingest/schedule.html @@ -18,7 +18,7 @@

    Scheduling

    {% csrf_token %} -

    Parameters for: {{body.name}}

    +

    Parameters for: {{body.current_name}}

    diff --git a/neoexchange/ingest/templates/ingest/schedule_confirm.html b/neoexchange/ingest/templates/ingest/schedule_confirm.html index 408c32be1..ee520c8c2 100644 --- a/neoexchange/ingest/templates/ingest/schedule_confirm.html +++ b/neoexchange/ingest/templates/ingest/schedule_confirm.html @@ -6,34 +6,31 @@ {% block extramenu %} -{% with vars=request.session.confirm %}

    Confirm Scheduling for: {{body.current_name}}

    -{% endwith %} {% endblock%} {% block main-content %} - {% with vars=request.session.confirm %}
    - {% if vars.data %} + {% if data %}

    Submitted Parameters

    - + - + - +
    Proposal{{ vars.data.proposal_code }}{{ data.proposal_code }}
    Site{{vars.data.site_code}}{{data.site_code}}
    UTC date{{ vars.data.utc_date}}{{ data.utc_date}}
    @@ -42,42 +39,45 @@

    Calculated characteristics

    Magnitude - {{ vars.data.magnitude|floatformat:2 }} + {{ data.magnitude|floatformat:2 }} Speed - {{ vars.data.speed|floatformat:2 }} '/min + {{ data.speed|floatformat:2 }} '/min Slot length - {{ vars.data.slot_length }} mins + {{ data.slot_length }} mins No. of exposures - {{ vars.data.exp_count }} + {{ data.exp_count }} Exposure length - {{ vars.data.exp_length|floatformat:1 }} secs + {{ data.exp_length|floatformat:1 }} secs {% endif %}
    - + {% csrf_token %} - {{form}} {% for key, error in form.errors.items %}
    {{ error }}
    + {% empty %} {% endfor %} + {% if form.errors%} + Return to schedule parameters page + {% endif%} + {{form}}

    -{% endwith %} {% endblock %} \ No newline at end of file diff --git a/neoexchange/ingest/views.py b/neoexchange/ingest/views.py index e4464ec30..e23145c73 100644 --- a/neoexchange/ingest/views.py +++ b/neoexchange/ingest/views.py @@ -20,6 +20,7 @@ from django.core.urlresolvers import reverse from django.views.generic import DetailView, ListView, FormView, TemplateView, View from django.views.generic.detail import SingleObjectMixin +from django.http import Http404 from ingest.ephem_subs import call_compute_ephem, compute_ephem, \ determine_darkness_times, determine_slot_length, determine_exp_time_count, MagRangeError from ingest.forms import EphemQuery, ScheduleForm, ScheduleBlockForm @@ -89,64 +90,39 @@ def ephemeris(request): } ) -class ScheduleSuccess(TemplateView): - template_name='ingest/schedule.html' - -class SchedFormDisplay(DetailView): - template_name = 'ingest/schedule.html' - model = Body - - def get_context_data(self, **kwargs): - context = super(SchedFormDisplay, self).get_context_data(**kwargs) - context['form'] = ScheduleForm - return context +class LookUpBodyMixin(object): + def dispatch(self, request, *args, **kwargs): + try: + body = Body.objects.get(pk=kwargs['pk']) + self.body = body + return super(LookUpBodyMixin, self).dispatch(request, *args, **kwargs) + except Body.DoesNotExist: + raise Http404("Body does not exist") -class ScheduleProcess(SingleObjectMixin, FormView): +class ScheduleParameters(LookUpBodyMixin, FormView): template_name = 'ingest/schedule.html' form_class = ScheduleForm - model = Body ok_to_schedule = False - def post(self, request, *args, **kwargs): - self.object = self.get_object() - #self.confirm = kwargs['confirm'] - return super(ScheduleProcess, self).post(request, *args, **kwargs) + def get(self, request, *args, **kwargs): + form = self.get_form() + # logger.debug(self.body) + return self.render_to_response(self.get_context_data(form=form,body=self.body)) - def form_valid(self, form): - data = schedule_check(form.cleaned_data, self.object, self.ok_to_schedule) + def form_valid(self, form, request): + data = schedule_check(form.cleaned_data, self.body, self.ok_to_schedule) #logger.debug() - self.request.session['confirm'] = {'data': data, 'body': {'id': self.object.pk,'name': self.object.current_name()},'form':form} - return super(ScheduleProcess, self).form_valid(form) - - def get_success_url(self): - body = self.get_object() - return reverse('schedule-confirm', kwargs={'pk':body.pk}) - -class ScheduleCheck(View): - ''' - Controls the Scheduling forms - GET will render the from - SchedFormDisplay - POST will process the results - ScheduleProcess - ''' - - def get(self, request, *args, **kwargs): - view = SchedFormDisplay.as_view() - return view(request, *args, **kwargs) + new_form = ScheduleBlockForm(data) + return render(request,'ingest/schedule_confirm.html', {'form':new_form, 'data': data,'body':self.body}) def post(self, request, *args, **kwargs): - view = ScheduleProcess.as_view() - return view(request, *args, **kwargs) - - -class SchedConfDisplay(DetailView): - template_name = 'ingest/schedule_confirm.html' - model = Body + form = self.get_form() + logger.debug(form) + if form.is_valid(): + return self.form_valid(form, request) + else: + return self.render_to_response(self.get_context_data(form=form,body=self.body)) - def get_context_data(self, **kwargs): - context = super(SchedConfDisplay, self).get_context_data(**kwargs) - form = ScheduleBlockForm(self.request.session['confirm']['data']) - context['form'] = form - return context class ScheduleSubmit(SingleObjectMixin, FormView): template_name = 'ingest/schedule_confirm.html' @@ -164,21 +140,6 @@ def form_valid(self, form): def get_success_url(self): return reverse('home') -class ScheduleConfirm(View): - ''' - Controls the Scheduling forms - GET will render the intermediate confirmation page (including hidden form) - SchedConfDisplay - POST will post to the scheduler - ScheduleSubmit - ''' - - def get(self, request, *args, **kwargs): - view = SchedConfDisplay.as_view() - return view(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - view = ScheduleSubmit.as_view() - return view(request, *args, **kwargs) - def schedule_check(data,body,ok_to_schedule): body_elements = model_to_dict(body) diff --git a/neoexchange/neox/urls.py b/neoexchange/neox/urls.py index c88800a14..0f03fe0af 100644 --- a/neoexchange/neox/urls.py +++ b/neoexchange/neox/urls.py @@ -18,7 +18,7 @@ from django.contrib import admin from django.views.generic import ListView, DetailView from ingest.models import Body, Block -from ingest.views import BodySearchView,BodyDetailView, ScheduleCheck, ScheduleSuccess, SchedFormDisplay, ScheduleConfirm, ephemeris, home +from ingest.views import BodySearchView,BodyDetailView, ScheduleParameters, ScheduleConfirm, ephemeris, home admin.autodiscover() @@ -30,9 +30,9 @@ url(r'^search/$', BodySearchView.as_view(context_object_name="target_list"), name='search'), url(r'^ephemeris/$', ephemeris, name='ephemeris'), url(r'^schedule/(?P\d+)/confirm/$',ScheduleConfirm.as_view(), name='schedule-confirm'), - url(r'^schedule/(?P\d+)/$', ScheduleCheck.as_view(), name='schedule-body'), - url(r'^schedule/success/$',ScheduleSuccess.as_view(), name='schedule-success'), - url(r'^schedule/$', SchedFormDisplay.as_view(), name='schedule'), + url(r'^schedule/(?P\d+)/$', ScheduleParameters.as_view(), name='schedule-body'), + # url(r'^schedule/success/$',ScheduleSuccess.as_view(), name='schedule-success'), + # url(r'^schedule/$', SchedFormDisplay.as_view(), name='schedule'), url(r'^grappelli/', include('grappelli.urls')), url(r'^admin/', include(admin.site.urls)), ] From 3fdd5231d4fa8149773f443cbf747f7cd043f806 Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Fri, 5 Jun 2015 16:52:58 +0100 Subject: [PATCH 29/48] Removing scheduleconfirm --- neoexchange/neox/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neoexchange/neox/urls.py b/neoexchange/neox/urls.py index 0f03fe0af..3e32b96b1 100644 --- a/neoexchange/neox/urls.py +++ b/neoexchange/neox/urls.py @@ -18,7 +18,7 @@ from django.contrib import admin from django.views.generic import ListView, DetailView from ingest.models import Body, Block -from ingest.views import BodySearchView,BodyDetailView, ScheduleParameters, ScheduleConfirm, ephemeris, home +from ingest.views import BodySearchView,BodyDetailView, ScheduleParameters, ephemeris, home admin.autodiscover() @@ -29,7 +29,7 @@ url(r'^target/(?P\d+)/$',BodyDetailView.as_view(model=Body), name='target'), url(r'^search/$', BodySearchView.as_view(context_object_name="target_list"), name='search'), url(r'^ephemeris/$', ephemeris, name='ephemeris'), - url(r'^schedule/(?P\d+)/confirm/$',ScheduleConfirm.as_view(), name='schedule-confirm'), + # url(r'^schedule/(?P\d+)/confirm/$',ScheduleConfirm.as_view(), name='schedule-confirm'), url(r'^schedule/(?P\d+)/$', ScheduleParameters.as_view(), name='schedule-body'), # url(r'^schedule/success/$',ScheduleSuccess.as_view(), name='schedule-success'), # url(r'^schedule/$', SchedFormDisplay.as_view(), name='schedule'), From e8ed0c30718a6565b125653e378ebff81e15bb8d Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Mon, 8 Jun 2015 16:02:05 +0100 Subject: [PATCH 30/48] Using ENV variables for secret info --- Dockerfile | 18 ++++++++++++------ config/nginx/nginx.conf | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9eb852e51..3ec2d519b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,13 +11,13 @@ # requirement to map all exposed container ports onto host ports. # # Build with -# docker build -t registry.lcogt.net/neoexchange:latest . +# docker build -t docker.lcogt.net/neoexchange:latest . # -# Push to Registry with -# docker push registry.lcogt.net/neoexchange:latest +# Push to docker registry with +# docker push docker.lcogt.net/neoexchange:latest # # To run with nginx + uwsgi both exposed: -# docker run -d -p 8200:8200 -p 8201:8201 --name=neox registry.lcogt.net/neoexchange:latest +# docker run -d -p 8200:8200 --name=neox docker.lcogt.net/neoexchange:latest # # See the notes in the code below about NFS mounts. # @@ -40,6 +40,12 @@ ENV DJANGO_SETTINGS_MODULE neox.settings ENV BRANCH ${BRANCH} #ENV BUILDDATE ${BUILDDATE} +# Setup the secret Django settings +ENV NEOX_DB_HOST ${DB_HOST_DEPLOY} +ENV NEOX_DB_USER ${NEOX_DB_USER_DEPLOY} +ENV NEOX_DB_PASSWD ${NEOX_DB_PASSWD_DEPLOY} +ENV SECRET_KEY ${SECRET_KEY_DEPLOY} + # Copy configuration files COPY config/uwsgi.ini /etc/uwsgi.ini COPY config/nginx/* /etc/nginx/ @@ -47,7 +53,7 @@ COPY config/processes.ini /etc/supervisord.d/processes.ini COPY config/crontab.root /var/spool/cron/root # nginx runs on port 8200, uwsgi runs on port 8201 -EXPOSE 8200 8201 +EXPOSE 80 # Entry point is the supervisord daemon ENTRYPOINT [ "/usr/bin/supervisord", "-n" ] @@ -57,7 +63,7 @@ COPY neoexchange /var/www/apps/neoexchange # Install the LCOGT NEO exchange Python required packages RUN pip install pip==1.3 && pip install uwsgi==2.0.8 \ - && pip install -r /var/www/apps/neoexchange/pip_requirements.txt \ + && pip install -r /var/www/apps/neoexchange/requirements.txt \ # LCOGT packages which have to be installed after the normal pip install && pip install pyslalib --extra-index-url=http://buildsba.lco.gtn/python/ \ && pip install rise_set --extra-index-url=http://buildsba.lco.gtn/python/ diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index 92bd49ce4..71745a346 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -34,7 +34,7 @@ http { } server { - listen 8200; + listen 80; server_name dockerhost; charset utf-8; From 3c25ae9c6492de2b1304cae800b4e19b28ed3afc Mon Sep 17 00:00:00 2001 From: Edward Gomez Date: Mon, 8 Jun 2015 17:53:11 +0100 Subject: [PATCH 31/48] Add docker-compose support --- Dockerfile | 18 ++++++------------ docker-compose.yml | 12 ++++++++++++ neoexchange/neox/settings.py | 8 ++++---- 3 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 3ec2d519b..9e49c6af9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ # docker push docker.lcogt.net/neoexchange:latest # # To run with nginx + uwsgi both exposed: -# docker run -d -p 8200:8200 --name=neox docker.lcogt.net/neoexchange:latest +# docker run -d -p 8200:80 --name=neox docker.lcogt.net/neoexchange:latest # # See the notes in the code below about NFS mounts. # @@ -37,14 +37,9 @@ RUN sed -i -e 's/\(session\s*required\s*pam_loginuid.so\)/#\1/' /etc/pam.d/crond # Setup the Python Django environment ENV PYTHONPATH /var/www/apps ENV DJANGO_SETTINGS_MODULE neox.settings -ENV BRANCH ${BRANCH} +#ENV BRANCH ${BRANCH} #ENV BUILDDATE ${BUILDDATE} -# Setup the secret Django settings -ENV NEOX_DB_HOST ${DB_HOST_DEPLOY} -ENV NEOX_DB_USER ${NEOX_DB_USER_DEPLOY} -ENV NEOX_DB_PASSWD ${NEOX_DB_PASSWD_DEPLOY} -ENV SECRET_KEY ${SECRET_KEY_DEPLOY} # Copy configuration files COPY config/uwsgi.ini /etc/uwsgi.ini @@ -52,7 +47,7 @@ COPY config/nginx/* /etc/nginx/ COPY config/processes.ini /etc/supervisord.d/processes.ini COPY config/crontab.root /var/spool/cron/root -# nginx runs on port 8200, uwsgi runs on port 8201 +# nginx runs on port 80, uwsgi is linked in the nginx conf EXPOSE 80 # Entry point is the supervisord daemon @@ -64,13 +59,12 @@ COPY neoexchange /var/www/apps/neoexchange # Install the LCOGT NEO exchange Python required packages RUN pip install pip==1.3 && pip install uwsgi==2.0.8 \ && pip install -r /var/www/apps/neoexchange/requirements.txt \ + # LCOGT packages which have to be installed after the normal pip install && pip install pyslalib --extra-index-url=http://buildsba.lco.gtn/python/ \ && pip install rise_set --extra-index-url=http://buildsba.lco.gtn/python/ -# Setup the LCOGT Mezzanine webapp -RUN python /var/www/apps/neoexchange/manage.py validate +# Setup the LCOGT NEOx webapp RUN python /var/www/apps/neoexchange/manage.py collectstatic --noinput -# If any schema changed have happened but not been appliedc +# If any schema changed have happened but not been applied RUN python /var/www/apps/neoexchange/manage.py syncdb --noinput -RUN python /var/www/apps/neoexchange/manage.py migrate --noinput diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..bb706455f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +web: + build: . + ports: + - "8200:80" + restart: "always" + mem_limit: "64m" + + environment: + - NEOX_DB_HOST_DEPLOY= + - NEOX_DB_USER_DEPLOY= + - NEOX_DB_PASSWD_DEPLOY= + - SECRET_KEY= \ No newline at end of file diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 6229337cc..801e096ff 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -170,16 +170,16 @@ } } -SECRET_KEY = os.environ['SECRET_KEY'] +SECRET_KEY = os.environ.get('SECRET_KEY','') DATABASES = { "default": { # Live DB "ENGINE": "django.db.backends.mysql", "NAME": "neoexchange", - "USER": os.environ['NEOX_DB_USER'], - "PASSWORD": os.environ['NEOX_DB_PASSWD'], - "HOST": os.environ['NEOX_DB_HOST'], + "USER": os.environ.get('NEOX_DB_USER_DEPLOY','') if PRODUCTION else os.environ.get('NEOX_DB_USER',''), + "PASSWORD": os.environ.get('NEOX_DB_PASSWD_DEPLOY','') if PRODUCTION else os.environ.get('NEOX_DB_PASSWD',''), + "HOST": os.environ.get('NEOX_DB_HOST_DEPLOY','') if PRODUCTION else os.environ.get('NEOX_DB_HOST',''), "OPTIONS" : {'init_command': 'SET storage_engine=INNODB'}, } From 5fb5d3d4f8d06b0013945cfad38a6cbde7234a33 Mon Sep 17 00:00:00 2001 From: zemogle Date: Thu, 11 Jun 2015 13:03:49 +0100 Subject: [PATCH 32/48] tidying up after django1.8 upgrade --- .gitignore | 1 + Dockerfile | 20 ++++++++---------- README.md | 21 +++++++++++++++---- config/init | 8 +++++++ docker-compose.yml | 12 ----------- neoexchange/ingest/models.py | 6 ++++++ .../ingest/templates/ingest/body_detail.html | 3 +++ neoexchange/neox/settings.py | 21 +++++++++++-------- 8 files changed, 56 insertions(+), 36 deletions(-) create mode 100755 config/init delete mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore index 42b0cdc3e..892a00645 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,4 @@ ingest/static/ingest/css/admin.css ingest/static/ingest/css/dashboard.css neoexchange/neox/local_settings.py *.db +docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 9e49c6af9..96e4fdf94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,8 +18,7 @@ # # To run with nginx + uwsgi both exposed: # docker run -d -p 8200:80 --name=neox docker.lcogt.net/neoexchange:latest -# -# See the notes in the code below about NFS mounts. +# Or use the docker-compose.yml from github.com/LCOGT/docker/compose/neoexchange/ # ################################################################################ FROM centos:centos7 @@ -37,9 +36,6 @@ RUN sed -i -e 's/\(session\s*required\s*pam_loginuid.so\)/#\1/' /etc/pam.d/crond # Setup the Python Django environment ENV PYTHONPATH /var/www/apps ENV DJANGO_SETTINGS_MODULE neox.settings -#ENV BRANCH ${BRANCH} -#ENV BUILDDATE ${BUILDDATE} - # Copy configuration files COPY config/uwsgi.ini /etc/uwsgi.ini @@ -50,21 +46,23 @@ COPY config/crontab.root /var/spool/cron/root # nginx runs on port 80, uwsgi is linked in the nginx conf EXPOSE 80 -# Entry point is the supervisord daemon -ENTRYPOINT [ "/usr/bin/supervisord", "-n" ] +# The entry point is our init script, which runs startup tasks, then +# execs the supervisord daemon +ENTRYPOINT [ "/init" ] + +# Copy configuration files +COPY config/init /init # Copy the LCOGT Mezzanine webapp files COPY neoexchange /var/www/apps/neoexchange # Install the LCOGT NEO exchange Python required packages RUN pip install pip==1.3 && pip install uwsgi==2.0.8 \ - && pip install -r /var/www/apps/neoexchange/requirements.txt \ + && pip install -r /var/www/apps/neoexchange/requirements.txt # LCOGT packages which have to be installed after the normal pip install - && pip install pyslalib --extra-index-url=http://buildsba.lco.gtn/python/ \ +RUN pip install pyslalib --extra-index-url=http://buildsba.lco.gtn/python/ \ && pip install rise_set --extra-index-url=http://buildsba.lco.gtn/python/ # Setup the LCOGT NEOx webapp RUN python /var/www/apps/neoexchange/manage.py collectstatic --noinput -# If any schema changed have happened but not been applied -RUN python /var/www/apps/neoexchange/manage.py syncdb --noinput diff --git a/README.md b/README.md index 4733084a5..d0748a5c2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ NEO Exchange ============ -Portal for scheduling observations of NEOs using LCOGT (Version 0.1.1) +Portal for scheduling observations of NEOs using LCOGT (Version 1.0) Setup ----- @@ -14,7 +14,21 @@ Construct a Python Virtual Enviroment (virtualenv) by executing: or: `source /bin/activate.csh # for (t)csh-shells` -`pip install -r pip-requirements.txt` + +then: + +`pip install -r neox/requirements.txt` + +Deployment +---------- + +You will need to set up 3 environment variables before deploying (if you are just locally testing see instructions below). + +If you are using BASH or ZSH add the following to your .profile or .zshrc files: + +export NEOX_DB_USER='' +export NEOX_DB_PASSWORD='' +export NEOX_DB_HOST='' Local Testing ------------- @@ -53,5 +67,4 @@ STATIC_ROOT = os.path.abspath(os.path.join(BASE_DIR, '../../static/')) To prepare the local SQLite DB for use, you should follow these steps: 1. `cd neoexchange\neoexchange` -2. Run `python manage.py syncdb` -3. Migrate the apps with `python manage.py migrate` +2. Run `python manage.py syncdb`. This is perform migrations as necessary. diff --git a/config/init b/config/init new file mode 100755 index 000000000..afab0fe74 --- /dev/null +++ b/config/init @@ -0,0 +1,8 @@ +#!/bin/bash + +# Substitute the prefix into the nginx configuration +# If any schema changed have happened but not been applied +python /var/www/apps/neoexchange/manage.py syncdb --noinput + +# Run under supervisord +exec /usr/bin/supervisord -n -c /etc/supervisord.conf \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index bb706455f..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -web: - build: . - ports: - - "8200:80" - restart: "always" - mem_limit: "64m" - - environment: - - NEOX_DB_HOST_DEPLOY= - - NEOX_DB_USER_DEPLOY= - - NEOX_DB_PASSWD_DEPLOY= - - SECRET_KEY= \ No newline at end of file diff --git a/neoexchange/ingest/models.py b/neoexchange/ingest/models.py index d41f8cabe..d65cfe2e0 100644 --- a/neoexchange/ingest/models.py +++ b/neoexchange/ingest/models.py @@ -123,6 +123,12 @@ def current_name(self): else: return "Unknown" + def old_name(self): + if self.provisional_name and self.name: + return self.provisional_name + else: + return False + class Meta: verbose_name = _('Minor Body') verbose_name_plural = _('Minor Bodies') diff --git a/neoexchange/ingest/templates/ingest/body_detail.html b/neoexchange/ingest/templates/ingest/body_detail.html index 6eef6c300..b35d100c6 100644 --- a/neoexchange/ingest/templates/ingest/body_detail.html +++ b/neoexchange/ingest/templates/ingest/body_detail.html @@ -22,6 +22,9 @@

    Object: {{body.current_name}}

    + {% if body.old_name %} +

    Name{{body.old_name}} → {{body.name}}

    + {% endif %}

    Type{{body.get_source_type_display}}

    Status {% if body.active %}Actively Following{% else %}Not Following{% endif %}

    Source {{body.get_origin_display}}

    diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 801e096ff..a4ac19eec 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -2,6 +2,7 @@ # Django settings for neox project. import os, sys +from django.utils.crypto import get_random_string CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) PRODUCTION = True if CURRENT_PATH.startswith('/var/www') else False @@ -170,16 +171,17 @@ } } -SECRET_KEY = os.environ.get('SECRET_KEY','') +chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' +SECRET_KEY = get_random_string(50, chars) DATABASES = { "default": { # Live DB "ENGINE": "django.db.backends.mysql", "NAME": "neoexchange", - "USER": os.environ.get('NEOX_DB_USER_DEPLOY','') if PRODUCTION else os.environ.get('NEOX_DB_USER',''), - "PASSWORD": os.environ.get('NEOX_DB_PASSWD_DEPLOY','') if PRODUCTION else os.environ.get('NEOX_DB_PASSWD',''), - "HOST": os.environ.get('NEOX_DB_HOST_DEPLOY','') if PRODUCTION else os.environ.get('NEOX_DB_HOST',''), + "USER": os.environ.get('NEOX_DB_USER',''), + "PASSWORD": os.environ.get('NEOX_DB_PASSWD',''), + "HOST": os.environ.get('NEOX_DB_HOST',''), "OPTIONS" : {'init_command': 'SET storage_engine=INNODB'}, } @@ -214,10 +216,11 @@ # Allow any settings to be defined in local_settings.py which should be # ignored in your version control system allowing for settings to be # defined per machine. -# try: -# from local_settings import * -# except ImportError as e: -# if "local_settings" not in str(e): -# raise e +if not CURRENT_PATH.startswith('/var/www'): + try: + from local_settings import * + except ImportError as e: + if "local_settings" not in str(e): + raise e From 6eb16ec621e4464abdc05b90f4d5ecf7aaa3c8c3 Mon Sep 17 00:00:00 2001 From: zemogle Date: Thu, 11 Jun 2015 13:07:50 +0100 Subject: [PATCH 33/48] Adding more deployment details --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d0748a5c2..aacfca911 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,24 @@ Deployment You will need to set up 3 environment variables before deploying (if you are just locally testing see instructions below). If you are using BASH or ZSH add the following to your .profile or .zshrc files: - +``` export NEOX_DB_USER='' export NEOX_DB_PASSWORD='' export NEOX_DB_HOST='' +``` + +Docker +------ +If you are building a Docker container use the following syntax: +``` +docker build -t docker.lcogt.net/neoexchange:latest . +``` +This will build a Docker image which will need to be pushed into a Docker registry with: +``` +docker push docker.lcogt.net/neoexchange:latest +``` +Starting a Docker container from this image can be done with a `docker run` command or using `docker-compose`. + Local Testing ------------- From c6936726a993ab7eb3393165ed0f70d65125a888 Mon Sep 17 00:00:00 2001 From: zemogle Date: Thu, 11 Jun 2015 16:15:37 +0100 Subject: [PATCH 34/48] Restructuring complete selenium tests are not working because of Firefox issues --- config/init | 2 +- .../{ingest => astrometrics}/__init__.py | 0 .../{ingest => astrometrics}/ast_subs.py | 0 .../{ingest => astrometrics}/ephem_subs.py | 2 +- .../{ingest => astrometrics}/sources_subs.py | 0 .../tests/__init__.py | 2 - .../tests/test_ast_subs.py | 2 +- .../tests/test_ephem_subs.py | 4 +- .../tests/test_source_subs.py | 6 +- .../{ingest => astrometrics}/time_subs.py | 0 .../{ingest/management => core}/__init__.py | 0 neoexchange/{ingest => core}/admin.py | 0 neoexchange/{ingest => core}/forms.py | 2 +- .../commands => core/management}/__init__.py | 0 .../management/commands}/__init__.py | 0 .../commands/fetch_goldstone_targets.py | 0 .../management/commands/runserver.py | 0 .../management/commands/update_crossids.py | 0 .../management/commands/update_neocp_data.py | 0 neoexchange/core/migrations/0001_initial.py | 105 +++++ .../migrations/__init__.py} | 0 neoexchange/{ingest => core}/models.py | 6 + .../ingest => core/static/core}/css/admin.css | 0 .../static/core}/css/dashboard.css | 0 .../ingest => core/static/core}/css/forms.css | 0 .../static/core}/css/styles.css | 0 .../static/core}/images/favicon.ico | Bin .../static/core}/images/icons.svg | 0 .../static/core}/images/icons_16.png | Bin .../static/core}/images/icons_16_white.png | Bin .../static/core}/images/lcogt.png | Bin .../static/core}/js/d3.v3.min.js | 0 .../static/core}/js/jquery-1.10.2.js | 0 .../static/core}/js/jquery-ui-1.10.3.min.js | 0 .../static/core}/js/jquery.burn.js | 0 .../static/core}/js/jquery.cycle2.js | 0 .../static/core}/js/jquery.jeditable.js | 0 .../static/core}/js/jquery.jumble.js | 0 .../static/core}/js/jquery.knob.js | 0 .../static/core}/js/jquery.min.js | 0 .../static/core}/js/jquery.timepicker.js | 0 .../static/core}/js/jquery.typewriter.js | 0 .../core}/js/jquery.ui.nestedSortable.js | 0 .../static/core}/js/jquery.ui.pack.js | 0 .../js/jquery.dj.selectable.js?v=0.8.0 | 0 .../{ingest => core}/templates/403.html | 0 .../{ingest => core}/templates/404.html | 0 .../{ingest => core}/templates/500.html | 0 .../ingest => core/templates}/base.html | 10 +- .../core/templates/core/block_list.html | 0 .../templates/core}/body_detail.html | 5 +- .../templates/core}/body_list.html | 2 +- .../ingest => core/templates/core}/ephem.html | 2 +- .../ingest => core/templates/core}/home.html | 4 +- .../ingest => core/templates/core}/login.html | 2 +- .../templates/core}/schedule.html | 2 +- .../templates/core}/schedule_confirm.html | 2 +- neoexchange/core/tests/__init__.py | 2 + .../{ingest => core}/tests/test_forms.py | 4 +- .../{ingest => core}/tests/test_views.py | 22 +- neoexchange/{ingest => core}/views.py | 26 +- neoexchange/ingest/migrations/0001_initial.py | 143 ------- ...g_field_body_epochofel__chg_field_body_.py | 124 ------ ...ield_body_abs_mag__add_field_body_slope.py | 88 ---- ...eld_proposal_pi__add_field_proposal_tag.py | 90 ----- neoexchange/ingest/templates/ingest/500.html | 6 - neoexchange/initial_data.json | 380 ------------------ neoexchange/neox/admin.py | 2 +- neoexchange/neox/settings.py | 4 +- neoexchange/neox/tests/base.py | 2 +- .../neox/tests/test_ephemeris_creation.py | 2 +- .../neox/tests/test_ephemeris_validation.py | 2 +- .../neox/tests/test_schedule_observations.py | 2 +- neoexchange/neox/urls.py | 4 +- neoexchange/pytest.ini | 2 + 75 files changed, 172 insertions(+), 893 deletions(-) rename neoexchange/{ingest => astrometrics}/__init__.py (100%) rename neoexchange/{ingest => astrometrics}/ast_subs.py (100%) rename neoexchange/{ingest => astrometrics}/ephem_subs.py (99%) rename neoexchange/{ingest => astrometrics}/sources_subs.py (100%) rename neoexchange/{ingest => astrometrics}/tests/__init__.py (64%) rename neoexchange/{ingest => astrometrics}/tests/test_ast_subs.py (99%) rename neoexchange/{ingest => astrometrics}/tests/test_ephem_subs.py (99%) rename neoexchange/{ingest => astrometrics}/tests/test_source_subs.py (96%) rename neoexchange/{ingest => astrometrics}/time_subs.py (100%) rename neoexchange/{ingest/management => core}/__init__.py (100%) rename neoexchange/{ingest => core}/admin.py (100%) rename neoexchange/{ingest => core}/forms.py (99%) rename neoexchange/{ingest/management/commands => core/management}/__init__.py (100%) rename neoexchange/{ingest/migrations => core/management/commands}/__init__.py (100%) rename neoexchange/{ingest => core}/management/commands/fetch_goldstone_targets.py (100%) rename neoexchange/{ingest => core}/management/commands/runserver.py (100%) rename neoexchange/{ingest => core}/management/commands/update_crossids.py (100%) rename neoexchange/{ingest => core}/management/commands/update_neocp_data.py (100%) create mode 100644 neoexchange/core/migrations/0001_initial.py rename neoexchange/{ingest/templates/ingest/block_list.html => core/migrations/__init__.py} (100%) rename neoexchange/{ingest => core}/models.py (97%) rename neoexchange/{ingest/static/ingest => core/static/core}/css/admin.css (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/css/dashboard.css (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/css/forms.css (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/css/styles.css (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/images/favicon.ico (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/images/icons.svg (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/images/icons_16.png (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/images/icons_16_white.png (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/images/lcogt.png (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/d3.v3.min.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery-1.10.2.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery-ui-1.10.3.min.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.burn.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.cycle2.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.jeditable.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.jumble.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.knob.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.min.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.timepicker.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.typewriter.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.ui.nestedSortable.js (100%) rename neoexchange/{ingest/static/ingest => core/static/core}/js/jquery.ui.pack.js (100%) rename neoexchange/{ingest => core}/static/selectable/js/jquery.dj.selectable.js?v=0.8.0 (100%) rename neoexchange/{ingest => core}/templates/403.html (100%) rename neoexchange/{ingest => core}/templates/404.html (100%) rename neoexchange/{ingest => core}/templates/500.html (100%) rename neoexchange/{ingest/templates/ingest => core/templates}/base.html (88%) create mode 100644 neoexchange/core/templates/core/block_list.html rename neoexchange/{ingest/templates/ingest => core/templates/core}/body_detail.html (96%) rename neoexchange/{ingest/templates/ingest => core/templates/core}/body_list.html (97%) rename neoexchange/{ingest/templates/ingest => core/templates/core}/ephem.html (99%) rename neoexchange/{ingest/templates/ingest => core/templates/core}/home.html (95%) rename neoexchange/{ingest/templates/ingest => core/templates/core}/login.html (98%) rename neoexchange/{ingest/templates/ingest => core/templates/core}/schedule.html (98%) rename neoexchange/{ingest/templates/ingest => core/templates/core}/schedule_confirm.html (98%) create mode 100644 neoexchange/core/tests/__init__.py rename neoexchange/{ingest => core}/tests/test_forms.py (97%) rename neoexchange/{ingest => core}/tests/test_views.py (95%) rename neoexchange/{ingest => core}/views.py (95%) delete mode 100644 neoexchange/ingest/migrations/0001_initial.py delete mode 100644 neoexchange/ingest/migrations/0002_auto__chg_field_body_origin__chg_field_body_epochofel__chg_field_body_.py delete mode 100644 neoexchange/ingest/migrations/0003_auto__add_field_body_abs_mag__add_field_body_slope.py delete mode 100644 neoexchange/ingest/migrations/0004_auto__add_field_proposal_pi__add_field_proposal_tag.py delete mode 100644 neoexchange/ingest/templates/ingest/500.html delete mode 100644 neoexchange/initial_data.json create mode 100644 neoexchange/pytest.ini diff --git a/config/init b/config/init index afab0fe74..ac98c421d 100755 --- a/config/init +++ b/config/init @@ -2,7 +2,7 @@ # Substitute the prefix into the nginx configuration # If any schema changed have happened but not been applied -python /var/www/apps/neoexchange/manage.py syncdb --noinput +python /var/www/apps/neoexchange/manage.py migrate --noinput # Run under supervisord exec /usr/bin/supervisord -n -c /etc/supervisord.conf \ No newline at end of file diff --git a/neoexchange/ingest/__init__.py b/neoexchange/astrometrics/__init__.py similarity index 100% rename from neoexchange/ingest/__init__.py rename to neoexchange/astrometrics/__init__.py diff --git a/neoexchange/ingest/ast_subs.py b/neoexchange/astrometrics/ast_subs.py similarity index 100% rename from neoexchange/ingest/ast_subs.py rename to neoexchange/astrometrics/ast_subs.py diff --git a/neoexchange/ingest/ephem_subs.py b/neoexchange/astrometrics/ephem_subs.py similarity index 99% rename from neoexchange/ingest/ephem_subs.py rename to neoexchange/astrometrics/ephem_subs.py index 98cf22f5d..0d95c3e52 100644 --- a/neoexchange/ingest/ephem_subs.py +++ b/neoexchange/astrometrics/ephem_subs.py @@ -20,7 +20,7 @@ from numpy import array, concatenate, zeros # Local imports -from ingest.time_subs import datetime2mjd_utc, datetime2mjd_tdb, mjd_utc2mjd_tt, ut1_minus_utc, round_datetime +from astrometrics.time_subs import datetime2mjd_utc, datetime2mjd_tdb, mjd_utc2mjd_tt, ut1_minus_utc, round_datetime #from astsubs import mpc_8lineformat import logging diff --git a/neoexchange/ingest/sources_subs.py b/neoexchange/astrometrics/sources_subs.py similarity index 100% rename from neoexchange/ingest/sources_subs.py rename to neoexchange/astrometrics/sources_subs.py diff --git a/neoexchange/ingest/tests/__init__.py b/neoexchange/astrometrics/tests/__init__.py similarity index 64% rename from neoexchange/ingest/tests/__init__.py rename to neoexchange/astrometrics/tests/__init__.py index 5093a70d7..8c9056769 100644 --- a/neoexchange/ingest/tests/__init__.py +++ b/neoexchange/astrometrics/tests/__init__.py @@ -1,5 +1,3 @@ from test_ast_subs import * from test_ephem_subs import * -from test_forms import * from test_source_subs import * -from test_views import * diff --git a/neoexchange/ingest/tests/test_ast_subs.py b/neoexchange/astrometrics/tests/test_ast_subs.py similarity index 99% rename from neoexchange/ingest/tests/test_ast_subs.py rename to neoexchange/astrometrics/tests/test_ast_subs.py index d095f5da9..a82d8a2e1 100644 --- a/neoexchange/ingest/tests/test_ast_subs.py +++ b/neoexchange/astrometrics/tests/test_ast_subs.py @@ -17,7 +17,7 @@ from django.test import TestCase #Import module to test -from ingest.ast_subs import * +from astrometrics.ast_subs import * class TestIntToMutantHexChar(TestCase): diff --git a/neoexchange/ingest/tests/test_ephem_subs.py b/neoexchange/astrometrics/tests/test_ephem_subs.py similarity index 99% rename from neoexchange/ingest/tests/test_ephem_subs.py rename to neoexchange/astrometrics/tests/test_ephem_subs.py index 5ee946185..1103c5883 100644 --- a/neoexchange/ingest/tests/test_ephem_subs.py +++ b/neoexchange/astrometrics/tests/test_ephem_subs.py @@ -20,8 +20,8 @@ from math import radians #Import module to test -from ingest.ephem_subs import * -from ingest.models import Body +from astrometrics.ephem_subs import * +from core.models import Body class TestGetMountLimits(TestCase): diff --git a/neoexchange/ingest/tests/test_source_subs.py b/neoexchange/astrometrics/tests/test_source_subs.py similarity index 96% rename from neoexchange/ingest/tests/test_source_subs.py rename to neoexchange/astrometrics/tests/test_source_subs.py index 05be9f571..215e5fc3e 100644 --- a/neoexchange/ingest/tests/test_source_subs.py +++ b/neoexchange/astrometrics/tests/test_source_subs.py @@ -15,12 +15,12 @@ from django.test import TestCase from django.forms.models import model_to_dict -from ingest.models import Body +from core.models import Body from datetime import datetime, timedelta -from ingest.ephem_subs import determine_darkness_times +from astrometrics.ephem_subs import determine_darkness_times #Import module to test -from ingest.sources_subs import parse_goldstone_chunks, submit_block_to_scheduler +from astrometrics.sources_subs import parse_goldstone_chunks, submit_block_to_scheduler class TestGoldstoneChunkParser(TestCase): diff --git a/neoexchange/ingest/time_subs.py b/neoexchange/astrometrics/time_subs.py similarity index 100% rename from neoexchange/ingest/time_subs.py rename to neoexchange/astrometrics/time_subs.py diff --git a/neoexchange/ingest/management/__init__.py b/neoexchange/core/__init__.py similarity index 100% rename from neoexchange/ingest/management/__init__.py rename to neoexchange/core/__init__.py diff --git a/neoexchange/ingest/admin.py b/neoexchange/core/admin.py similarity index 100% rename from neoexchange/ingest/admin.py rename to neoexchange/core/admin.py diff --git a/neoexchange/ingest/forms.py b/neoexchange/core/forms.py similarity index 99% rename from neoexchange/ingest/forms.py rename to neoexchange/core/forms.py index 316aeb3b2..539d20fc6 100644 --- a/neoexchange/ingest/forms.py +++ b/neoexchange/core/forms.py @@ -1,7 +1,7 @@ from datetime import datetime from django import forms from django.db.models import Q -from ingest.models import Body, Proposal +from .models import Body, Proposal from django.utils.translation import ugettext as _ class EphemQuery(forms.Form): diff --git a/neoexchange/ingest/management/commands/__init__.py b/neoexchange/core/management/__init__.py similarity index 100% rename from neoexchange/ingest/management/commands/__init__.py rename to neoexchange/core/management/__init__.py diff --git a/neoexchange/ingest/migrations/__init__.py b/neoexchange/core/management/commands/__init__.py similarity index 100% rename from neoexchange/ingest/migrations/__init__.py rename to neoexchange/core/management/commands/__init__.py diff --git a/neoexchange/ingest/management/commands/fetch_goldstone_targets.py b/neoexchange/core/management/commands/fetch_goldstone_targets.py similarity index 100% rename from neoexchange/ingest/management/commands/fetch_goldstone_targets.py rename to neoexchange/core/management/commands/fetch_goldstone_targets.py diff --git a/neoexchange/ingest/management/commands/runserver.py b/neoexchange/core/management/commands/runserver.py similarity index 100% rename from neoexchange/ingest/management/commands/runserver.py rename to neoexchange/core/management/commands/runserver.py diff --git a/neoexchange/ingest/management/commands/update_crossids.py b/neoexchange/core/management/commands/update_crossids.py similarity index 100% rename from neoexchange/ingest/management/commands/update_crossids.py rename to neoexchange/core/management/commands/update_crossids.py diff --git a/neoexchange/ingest/management/commands/update_neocp_data.py b/neoexchange/core/management/commands/update_neocp_data.py similarity index 100% rename from neoexchange/ingest/management/commands/update_neocp_data.py rename to neoexchange/core/management/commands/update_neocp_data.py diff --git a/neoexchange/core/migrations/0001_initial.py b/neoexchange/core/migrations/0001_initial.py new file mode 100644 index 000000000..57211499c --- /dev/null +++ b/neoexchange/core/migrations/0001_initial.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Block', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('telclass', models.CharField(default=b'1m0', max_length=3, choices=[(b'1m0', b'1-meter'), (b'2m0', b'2-meter'), (b'0m4', b'0.4-meter')])), + ('site', models.CharField(max_length=3, choices=[(b'ogg', b'Haleakala'), (b'coj', b'Siding Spring'), (b'lsc', b'Cerro Tololo'), (b'elp', b'McDonald'), (b'cpt', b'Sutherland')])), + ('block_start', models.DateTimeField(null=True, blank=True)), + ('block_end', models.DateTimeField(null=True, blank=True)), + ('tracking_number', models.CharField(max_length=10, null=True, blank=True)), + ('when_observed', models.DateTimeField(null=True, blank=True)), + ('active', models.BooleanField(default=False)), + ], + options={ + 'db_table': 'ingest_block', + 'verbose_name': 'Observation Block', + 'verbose_name_plural': 'Observation Blocks', + }, + ), + migrations.CreateModel( + name='Body', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('provisional_name', models.CharField(max_length=15, null=True, verbose_name=b'Provisional MPC designation', blank=True)), + ('provisional_packed', models.CharField(max_length=7, null=True, verbose_name=b'MPC name in packed format', blank=True)), + ('name', models.CharField(max_length=15, null=True, verbose_name=b'Designation', blank=True)), + ('origin', models.CharField(default=b'M', choices=[(b'M', b'Minor Planet Center'), (b'N', b'NASA ARM'), (b'S', b'Spaceguard'), (b'D', b'NEODSYS'), (b'G', b'Goldstone'), (b'A', b'Arecibo')], max_length=1, blank=True, null=True, verbose_name=b'Where did this target come from?')), + ('source_type', models.CharField(blank=True, max_length=1, null=True, verbose_name=b'Type of object', choices=[(b'N', b'NEO'), (b'A', b'Asteroid'), (b'C', b'Comet'), (b'K', b'KBO'), (b'E', b'Centaur'), (b'T', b'Trojan'), (b'U', b'Unknown/NEO Candidate'), (b'X', b'Did not exist'), (b'W', b'Was not interesting')])), + ('elements_type', models.CharField(blank=True, max_length=16, null=True, verbose_name=b'Elements type', choices=[(b'MPC_MINOR_PLANET', b'MPC Minor Planet'), (b'MPC_COMET', b'MPC Comet')])), + ('active', models.BooleanField(default=False, verbose_name=b'Actively following?')), + ('fast_moving', models.BooleanField(default=False, verbose_name=b'Is this object fast?')), + ('urgency', models.IntegerField(help_text=b'how urgent is this?', null=True, blank=True)), + ('epochofel', models.DateTimeField(null=True, verbose_name=b'Epoch of elements', blank=True)), + ('orbinc', models.FloatField(null=True, verbose_name=b'Orbital inclination in deg', blank=True)), + ('longascnode', models.FloatField(null=True, verbose_name=b'Longitude of Ascending Node (deg)', blank=True)), + ('argofperih', models.FloatField(null=True, verbose_name=b'Arg of perihelion (deg)', blank=True)), + ('eccentricity', models.FloatField(null=True, verbose_name=b'Eccentricity', blank=True)), + ('meandist', models.FloatField(help_text=b'for asteroids', null=True, verbose_name=b'Mean distance (AU)', blank=True)), + ('meananom', models.FloatField(help_text=b'for asteroids', null=True, verbose_name=b'Mean Anomaly (deg)', blank=True)), + ('perihdist', models.FloatField(help_text=b'for comets', null=True, verbose_name=b'Perihelion distance (AU)', blank=True)), + ('epochofperih', models.DateTimeField(help_text=b'for comets', null=True, verbose_name=b'Epoch of perihelion', blank=True)), + ('abs_mag', models.FloatField(null=True, verbose_name=b'H - absolute magnitude', blank=True)), + ('slope', models.FloatField(null=True, verbose_name=b'G - slope parameter', blank=True)), + ('ingest', models.DateTimeField(default=django.utils.timezone.now)), + ], + options={ + 'db_table': 'ingest_body', + 'verbose_name': 'Minor Body', + 'verbose_name_plural': 'Minor Bodies', + }, + ), + migrations.CreateModel( + name='Proposal', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('code', models.CharField(max_length=20)), + ('title', models.CharField(max_length=255)), + ('pi', models.CharField(default=b'', max_length=50)), + ('tag', models.CharField(default=b'LCO', max_length=10)), + ], + options={ + 'db_table': 'ingest_proposal', + }, + ), + migrations.CreateModel( + name='Record', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('site', models.CharField(max_length=3, verbose_name=b'3-letter site code')), + ('instrument', models.CharField(max_length=4, verbose_name=b'instrument code')), + ('filter', models.CharField(max_length=15, verbose_name=b'filter class')), + ('filename', models.CharField(max_length=31)), + ('exp', models.FloatField(verbose_name=b'exposure time in seconds')), + ('whentaken', models.DateTimeField()), + ('block', models.ForeignKey(to='core.Block')), + ], + options={ + 'db_table': 'ingest_record', + 'verbose_name': 'Observation Record', + 'verbose_name_plural': 'Observation Records', + }, + ), + migrations.AddField( + model_name='block', + name='body', + field=models.ForeignKey(to='core.Body'), + ), + migrations.AddField( + model_name='block', + name='proposal', + field=models.ForeignKey(to='core.Proposal'), + ), + ] diff --git a/neoexchange/ingest/templates/ingest/block_list.html b/neoexchange/core/migrations/__init__.py similarity index 100% rename from neoexchange/ingest/templates/ingest/block_list.html rename to neoexchange/core/migrations/__init__.py diff --git a/neoexchange/ingest/models.py b/neoexchange/core/models.py similarity index 97% rename from neoexchange/ingest/models.py rename to neoexchange/core/models.py index d65cfe2e0..83b2a1868 100644 --- a/neoexchange/ingest/models.py +++ b/neoexchange/core/models.py @@ -76,6 +76,9 @@ class Proposal(models.Model): pi = models.CharField(max_length=50, default='') tag = models.CharField(max_length=10, default='LCO') + class Meta: + db_table = 'ingest_proposal' + def __unicode__(self): if len(self.title)>=10: title = "%s..." % self.title[0:9] @@ -132,6 +135,7 @@ def old_name(self): class Meta: verbose_name = _('Minor Body') verbose_name_plural = _('Minor Bodies') + db_table = 'ingest_body' def __unicode__(self): if self.active: @@ -159,6 +163,7 @@ class Block(models.Model): class Meta: verbose_name = _('Observation Block') verbose_name_plural = _('Observation Blocks') + db_table = 'ingest_block' def __unicode__(self): pass @@ -178,6 +183,7 @@ class Record(models.Model): class Meta: verbose_name = _('Observation Record') verbose_name_plural = _('Observation Records') + db_table = 'ingest_record' def __unicode__(self): if self.active: diff --git a/neoexchange/ingest/static/ingest/css/admin.css b/neoexchange/core/static/core/css/admin.css similarity index 100% rename from neoexchange/ingest/static/ingest/css/admin.css rename to neoexchange/core/static/core/css/admin.css diff --git a/neoexchange/ingest/static/ingest/css/dashboard.css b/neoexchange/core/static/core/css/dashboard.css similarity index 100% rename from neoexchange/ingest/static/ingest/css/dashboard.css rename to neoexchange/core/static/core/css/dashboard.css diff --git a/neoexchange/ingest/static/ingest/css/forms.css b/neoexchange/core/static/core/css/forms.css similarity index 100% rename from neoexchange/ingest/static/ingest/css/forms.css rename to neoexchange/core/static/core/css/forms.css diff --git a/neoexchange/ingest/static/ingest/css/styles.css b/neoexchange/core/static/core/css/styles.css similarity index 100% rename from neoexchange/ingest/static/ingest/css/styles.css rename to neoexchange/core/static/core/css/styles.css diff --git a/neoexchange/ingest/static/ingest/images/favicon.ico b/neoexchange/core/static/core/images/favicon.ico similarity index 100% rename from neoexchange/ingest/static/ingest/images/favicon.ico rename to neoexchange/core/static/core/images/favicon.ico diff --git a/neoexchange/ingest/static/ingest/images/icons.svg b/neoexchange/core/static/core/images/icons.svg similarity index 100% rename from neoexchange/ingest/static/ingest/images/icons.svg rename to neoexchange/core/static/core/images/icons.svg diff --git a/neoexchange/ingest/static/ingest/images/icons_16.png b/neoexchange/core/static/core/images/icons_16.png similarity index 100% rename from neoexchange/ingest/static/ingest/images/icons_16.png rename to neoexchange/core/static/core/images/icons_16.png diff --git a/neoexchange/ingest/static/ingest/images/icons_16_white.png b/neoexchange/core/static/core/images/icons_16_white.png similarity index 100% rename from neoexchange/ingest/static/ingest/images/icons_16_white.png rename to neoexchange/core/static/core/images/icons_16_white.png diff --git a/neoexchange/ingest/static/ingest/images/lcogt.png b/neoexchange/core/static/core/images/lcogt.png similarity index 100% rename from neoexchange/ingest/static/ingest/images/lcogt.png rename to neoexchange/core/static/core/images/lcogt.png diff --git a/neoexchange/ingest/static/ingest/js/d3.v3.min.js b/neoexchange/core/static/core/js/d3.v3.min.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/d3.v3.min.js rename to neoexchange/core/static/core/js/d3.v3.min.js diff --git a/neoexchange/ingest/static/ingest/js/jquery-1.10.2.js b/neoexchange/core/static/core/js/jquery-1.10.2.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery-1.10.2.js rename to neoexchange/core/static/core/js/jquery-1.10.2.js diff --git a/neoexchange/ingest/static/ingest/js/jquery-ui-1.10.3.min.js b/neoexchange/core/static/core/js/jquery-ui-1.10.3.min.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery-ui-1.10.3.min.js rename to neoexchange/core/static/core/js/jquery-ui-1.10.3.min.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.burn.js b/neoexchange/core/static/core/js/jquery.burn.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.burn.js rename to neoexchange/core/static/core/js/jquery.burn.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.cycle2.js b/neoexchange/core/static/core/js/jquery.cycle2.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.cycle2.js rename to neoexchange/core/static/core/js/jquery.cycle2.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.jeditable.js b/neoexchange/core/static/core/js/jquery.jeditable.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.jeditable.js rename to neoexchange/core/static/core/js/jquery.jeditable.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.jumble.js b/neoexchange/core/static/core/js/jquery.jumble.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.jumble.js rename to neoexchange/core/static/core/js/jquery.jumble.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.knob.js b/neoexchange/core/static/core/js/jquery.knob.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.knob.js rename to neoexchange/core/static/core/js/jquery.knob.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.min.js b/neoexchange/core/static/core/js/jquery.min.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.min.js rename to neoexchange/core/static/core/js/jquery.min.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.timepicker.js b/neoexchange/core/static/core/js/jquery.timepicker.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.timepicker.js rename to neoexchange/core/static/core/js/jquery.timepicker.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.typewriter.js b/neoexchange/core/static/core/js/jquery.typewriter.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.typewriter.js rename to neoexchange/core/static/core/js/jquery.typewriter.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.ui.nestedSortable.js b/neoexchange/core/static/core/js/jquery.ui.nestedSortable.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.ui.nestedSortable.js rename to neoexchange/core/static/core/js/jquery.ui.nestedSortable.js diff --git a/neoexchange/ingest/static/ingest/js/jquery.ui.pack.js b/neoexchange/core/static/core/js/jquery.ui.pack.js similarity index 100% rename from neoexchange/ingest/static/ingest/js/jquery.ui.pack.js rename to neoexchange/core/static/core/js/jquery.ui.pack.js diff --git a/neoexchange/ingest/static/selectable/js/jquery.dj.selectable.js?v=0.8.0 b/neoexchange/core/static/selectable/js/jquery.dj.selectable.js?v=0.8.0 similarity index 100% rename from neoexchange/ingest/static/selectable/js/jquery.dj.selectable.js?v=0.8.0 rename to neoexchange/core/static/selectable/js/jquery.dj.selectable.js?v=0.8.0 diff --git a/neoexchange/ingest/templates/403.html b/neoexchange/core/templates/403.html similarity index 100% rename from neoexchange/ingest/templates/403.html rename to neoexchange/core/templates/403.html diff --git a/neoexchange/ingest/templates/404.html b/neoexchange/core/templates/404.html similarity index 100% rename from neoexchange/ingest/templates/404.html rename to neoexchange/core/templates/404.html diff --git a/neoexchange/ingest/templates/500.html b/neoexchange/core/templates/500.html similarity index 100% rename from neoexchange/ingest/templates/500.html rename to neoexchange/core/templates/500.html diff --git a/neoexchange/ingest/templates/ingest/base.html b/neoexchange/core/templates/base.html similarity index 88% rename from neoexchange/ingest/templates/ingest/base.html rename to neoexchange/core/templates/base.html index 533d9ea05..82db52ddf 100644 --- a/neoexchange/ingest/templates/ingest/base.html +++ b/neoexchange/core/templates/base.html @@ -11,17 +11,17 @@ {% block meta %}{% endblock %} {% block favicon %} - + {% endblock %} {% block css-content %}{% endblock %} - + {% block last-css-content %}{% endblock %} - - + + {% block script-content %}{% endblock %} @@ -38,7 +38,7 @@
    {% endblock %} \ No newline at end of file diff --git a/neoexchange/core/templates/core/logout.html b/neoexchange/core/templates/core/logout.html new file mode 100644 index 000000000..8b3ddbee2 --- /dev/null +++ b/neoexchange/core/templates/core/logout.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} +{% load staticfiles %} + +{% block header %}NEOx Login{% endblock %} +{% block last-css-content %} + +{% endblock %} + + +{% block bodyclass %}page{% endblock %} +{% block main-content %} + +
    + +
    +
    +

    You have been logged out.

    +

    Log in again?

    +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/neoexchange/core/views.py b/neoexchange/core/views.py index 39a02185e..7e8632549 100644 --- a/neoexchange/core/views.py +++ b/neoexchange/core/views.py @@ -13,7 +13,7 @@ GNU General Public License for more details. ''' -from datetime import datetime +from datetime import datetime, timedelta from django.db.models import Q from django.forms.models import model_to_dict from django.contrib.auth.decorators import login_required @@ -46,7 +46,9 @@ def as_view(cls, **initkwargs): def home(request): latest = Body.objects.filter(active=True).latest('ingest') - newest = Body.objects.filter(ingest=latest.ingest,active=True) + max_dt = latest.ingest + min_dt = max_dt - timedelta(minutes=30) + newest = Body.objects.filter(ingest__range=(min_dt,max_dt) ,active=True) params = { 'targets' : Body.objects.filter(active=True).count(), 'blocks' : Block.objects.filter(active=True).count(), diff --git a/neoexchange/neox/settings.py b/neoexchange/neox/settings.py index 2ff7ef3dd..d182497aa 100644 --- a/neoexchange/neox/settings.py +++ b/neoexchange/neox/settings.py @@ -101,6 +101,8 @@ }, ] +LOGIN_REDIRECT_URL = '/' + # GRAPPELLI_INDEX_DASHBOARD = 'neox.dashboard.CustomIndexDashboard' INSTALLED_APPS = ( @@ -122,7 +124,7 @@ 'ORGANIZATION_ID': os.environ.get('NEOX_OPBEAT_ORGID',''), 'APP_ID': os.environ.get('NEOX_OPBEAT_APPID',''), 'SECRET_TOKEN': os.environ.get('NEOX_OPBEAT_TOKEN',''), - 'DEBUG': DEBUG, + 'DEBUG': False, } LOGGING = { diff --git a/neoexchange/neox/urls.py b/neoexchange/neox/urls.py index f7e421f8f..e84a8f89b 100644 --- a/neoexchange/neox/urls.py +++ b/neoexchange/neox/urls.py @@ -17,8 +17,10 @@ from django.contrib.staticfiles import views from django.contrib import admin from django.views.generic import ListView, DetailView +from django.core.urlresolvers import reverse_lazy from core.models import Body, Block from core.views import BodySearchView,BodyDetailView, ScheduleParameters, ephemeris, home +from django.contrib.auth.views import login, logout admin.autodiscover() @@ -33,6 +35,8 @@ url(r'^schedule/(?P\d+)/$', ScheduleParameters.as_view(), name='schedule-body'), # url(r'^schedule/success/$',ScheduleSuccess.as_view(), name='schedule-success'), # url(r'^schedule/$', SchedFormDisplay.as_view(), name='schedule'), + url(r'^accounts/login/$', login, {'template_name': 'core/login.html'}, name='auth_login'), + url(r'^accounts/logout/$', logout, {'template_name': 'core/logout.html'}, name='auth_logout' ), url(r'^grappelli/', include('grappelli.urls')), url(r'^admin/', include(admin.site.urls)), ] From 4756c3572f5788f28e138d914949048f9594fc68 Mon Sep 17 00:00:00 2001 From: zemogle Date: Mon, 29 Jun 2015 18:10:54 +0100 Subject: [PATCH 45/48] Restructuring the docker container for better caching --- Dockerfile | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 96e4fdf94..6b9301d71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,16 @@ RUN yum -y install epel-release \ && yum -y groupinstall "Development Tools" \ && yum -y update +# Setup our python env now so it can be cached +COPY neoexchange/requirements.txt /var/www/apps/neoexchange/requirements.txt +# Install the LCOGT NEO exchange Python required packages +RUN pip install pip==1.3 && pip install uwsgi==2.0.8 \ + && pip install -r /var/www/apps/neoexchange/requirements.txt + +# LCOGT packages which have to be installed after the normal pip install +RUN pip install pyslalib --extra-index-url=http://buildsba.lco.gtn/python/ \ + && pip install rise_set --extra-index-url=http://buildsba.lco.gtn/python/ + # Ensure crond will run on all host operating systems RUN sed -i -e 's/\(session\s*required\s*pam_loginuid.so\)/#\1/' /etc/pam.d/crond @@ -56,13 +66,5 @@ COPY config/init /init # Copy the LCOGT Mezzanine webapp files COPY neoexchange /var/www/apps/neoexchange -# Install the LCOGT NEO exchange Python required packages -RUN pip install pip==1.3 && pip install uwsgi==2.0.8 \ - && pip install -r /var/www/apps/neoexchange/requirements.txt - -# LCOGT packages which have to be installed after the normal pip install -RUN pip install pyslalib --extra-index-url=http://buildsba.lco.gtn/python/ \ - && pip install rise_set --extra-index-url=http://buildsba.lco.gtn/python/ - # Setup the LCOGT NEOx webapp RUN python /var/www/apps/neoexchange/manage.py collectstatic --noinput From 7f76dffbc1e966faeb02cd28e48bc3b3cb5617fa Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Mon, 29 Jun 2015 17:01:36 -0700 Subject: [PATCH 46/48] Shift to using StaticLiveTestServer now we're on django 1.8. Make ScheduleObservations inherit from super class setup method. --- neoexchange/neox/tests/base.py | 4 ++-- neoexchange/neox/tests/test_schedule_observations.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/neoexchange/neox/tests/base.py b/neoexchange/neox/tests/base.py index 7a35a9645..292398f23 100644 --- a/neoexchange/neox/tests/base.py +++ b/neoexchange/neox/tests/base.py @@ -1,8 +1,8 @@ -from django.test import LiveServerTestCase +from django.contrib.staticfiles.testing import StaticLiveServerTestCase from selenium import webdriver from core.models import Body, Proposal -class FunctionalTest(LiveServerTestCase): +class FunctionalTest(StaticLiveServerTestCase): def insert_test_body(self): diff --git a/neoexchange/neox/tests/test_schedule_observations.py b/neoexchange/neox/tests/test_schedule_observations.py index 1607fbed6..f6426af3f 100644 --- a/neoexchange/neox/tests/test_schedule_observations.py +++ b/neoexchange/neox/tests/test_schedule_observations.py @@ -16,6 +16,7 @@ def setUp(self): self.bart.last_name = 'Simpson' self.bart.is_active=1 self.bart.save() + super(ScheduleObservations,self).setUp() def login(self): self.assertTrue(self.client.login(username='bart', password='simpson')) From 558be796853e8c2d4553768673127750f144133d Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Mon, 29 Jun 2015 17:03:33 -0700 Subject: [PATCH 47/48] Allow LCOGT to be a source of asteroids and allow for discoveries. Change default TAG to 'LCOGT' (from 'LCO') --- neoexchange/core/models.py | 26 +++++++++++++++++------- neoexchange/core/tests/test_views.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/neoexchange/core/models.py b/neoexchange/core/models.py index 83b2a1868..7f15427e6 100644 --- a/neoexchange/core/models.py +++ b/neoexchange/core/models.py @@ -27,7 +27,8 @@ ('T','Trojan'), ('U','Unknown/NEO Candidate'), ('X','Did not exist'), - ('W','Was not interesting') + ('W','Was not interesting'), + ('D','Discovery, non NEO'), ) ELEMENTS_TYPES = (('MPC_MINOR_PLANET','MPC Minor Planet'),('MPC_COMET','MPC Comet')) @@ -38,7 +39,8 @@ ('S','Spaceguard'), ('D','NEODSYS'), ('G','Goldstone'), - ('A','Arecibo') + ('A','Arecibo'), + ('L','LCOGT') ) TELESCOPE_CHOICES = ( @@ -74,7 +76,7 @@ class Proposal(models.Model): code = models.CharField(max_length=20) title = models.CharField(max_length=255) pi = models.CharField(max_length=50, default='') - tag = models.CharField(max_length=10, default='LCO') + tag = models.CharField(max_length=10, default='LCOGT') class Meta: db_table = 'ingest_proposal' @@ -111,12 +113,22 @@ class Body(models.Model): ingest = models.DateTimeField(default=now) def epochofel_mjd(self): - t = Time(self.epochofel.isoformat(), format='isot', scale='tt') - return t.mjd + mjd = None + try: + t = Time(self.epochofel.isoformat(), format='isot', scale='tt') + mjd = t.mjd + except: + pass + return mjd def epochofperih_mjd(self): - t = Time(self.epochofperih.isoformat(), format='isot', scale='tt') - return t.mjd + mjd = None + try: + t = Time(self.epochofperih.isoformat(), format='isot', scale='tt') + mjd = t.mjd + except: + pass + return mjd def current_name(self): if self.name: diff --git a/neoexchange/core/tests/test_views.py b/neoexchange/core/tests/test_views.py index 0def91d36..3441dde08 100644 --- a/neoexchange/core/tests/test_views.py +++ b/neoexchange/core/tests/test_views.py @@ -134,6 +134,36 @@ def test_update_MPC_duplicate(self): obj_id ='N007riz' update_MPC_orbit(obj_id) + def test_create_discovered_object(self): + obj_id ='LSCTLF8' + elements = { 'abs_mag' : 16.2, + 'slope' : 0.15, + 'epochofel' : datetime(2015, 6, 23, 0, 0, 0), + 'meananom' : 333.70614, + 'argofperih' : 40.75306, + 'longascnode' : 287.97838, + 'orbinc' : 23.61657, + 'eccentricity': 0.1186953, + 'meandist' : 2.7874893, + 'elements_type': 'MPC_MINOR_PLANET', + 'origin' : 'L', + 'source_type' : 'D', + 'active' : True + } + body, created = Body.objects.get_or_create(provisional_name=obj_id) + # We are creating this object + self.assertEqual(True,created) + resp = save_and_make_revision(body,elements) + # Need to call full_clean() to validate the fields as this is not + # done on save() (called by get_or_create() or save_and_make_revision()) + body.full_clean() + # We are saving all the detailing elements + self.assertEqual(True,resp) + + # Test it came from LCOGT as a discovery + self.assertEqual('L', body.origin) + self.assertEqual('D', body.source_type) + class HomePageTest(TestCase): From 4aeb4e6ea048c0748473d2459ec604fa0b54a377 Mon Sep 17 00:00:00 2001 From: Tim Lister Date: Mon, 29 Jun 2015 17:07:46 -0700 Subject: [PATCH 48/48] Update README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aacfca911..44270efbe 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ NEO Exchange ============ -Portal for scheduling observations of NEOs using LCOGT (Version 1.0) +Portal for scheduling observations of NEOs using LCOGT (Version 1.0.1) Setup ----- @@ -81,4 +81,4 @@ STATIC_ROOT = os.path.abspath(os.path.join(BASE_DIR, '../../static/')) To prepare the local SQLite DB for use, you should follow these steps: 1. `cd neoexchange\neoexchange` -2. Run `python manage.py syncdb`. This is perform migrations as necessary. +2. Run `python manage.py syncdb`. This will perform migrations as necessary.