Skip to content

Commit

Permalink
Merge branch 'master' of github.com:LCOGT/observation-portal into dir…
Browse files Browse the repository at this point in the history
…ect_request_details
  • Loading branch information
Jon Nation committed Jun 7, 2019
2 parents 37387df + 4f1cbb9 commit ffc5a8b
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,3 @@ def handle(self, *args, **options):
token, created = Token.objects.get_or_create(user=user)
token.delete()
Token.objects.create(user=user, key=options['token'])
sys.exit(0)


21 changes: 21 additions & 0 deletions observation_portal/accounts/tests.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from django.test import TestCase
from django.utils import timezone
from django.contrib.auth.models import User
from django.core.management import call_command
from datetime import timedelta
from mixer.backend.django import mixer
from oauth2_provider.models import Application, AccessToken
from unittest.mock import patch
from rest_framework.authtoken.models import Token

from observation_portal.accounts.models import Profile
from observation_portal.proposals.models import Proposal


class TestArchiveBearerToken(TestCase):
Expand Down Expand Up @@ -52,3 +55,21 @@ def test_quota_is_zero(self):
@patch('django.core.cache.cache.get', return_value=([1504903107.1322677, 1504903106.6130717]))
def test_quota_used(self, cache_mock):
self.assertEqual(self.profile.api_quota['used'], 2)


class TestInitCredentialsCommand(TestCase):
def test_credentials_are_setup(self):
self.assertEqual(User.objects.count(), 0)
self.assertEqual(Proposal.objects.count(), 0)
call_command('init_e2e_credentials', '-pMyProposal', '-umy_user', '-tmy_token')

self.assertEqual(User.objects.count(), 1)
user = User.objects.all()[0]
self.assertEqual(user.username, 'my_user')

self.assertEqual(Proposal.objects.count(), 1)
proposal = Proposal.objects.all()[0]
self.assertEqual(proposal.id, 'MyProposal')

token = Token.objects.get(user=user)
self.assertEqual(token.key, 'my_token')
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
from observation_portal.proposals.models import Proposal, Semester, TimeAllocation

import math
import logging

logger = logging.getLogger()


class Command(BaseCommand):
Expand All @@ -32,8 +29,10 @@ def handle(self, *args, **options):
instrument_type_str = options['instrument_type'] or 'All'
semester_str = options['semester']
dry_run_str = 'Dry Run Mode: ' if options['dry_run'] else ''
logger.info(
f"{dry_run_str}Running time accounting for Proposal(s): {proposal_str} and Instrument Type(s): {instrument_type_str} in Semester: {semester_str}")
print(
f"{dry_run_str}Running time accounting for Proposal(s): {proposal_str} and Instrument Type(s): {instrument_type_str} in Semester: {semester_str}",
file=self.stdout
)

if options['proposal']:
proposals = [Proposal.objects.get(id=options['proposal'])]
Expand Down Expand Up @@ -63,9 +62,6 @@ def handle(self, *args, **options):
'configuration_statuses__summary'
).order_by('start').distinct()

if len(observations) == 0:
continue

for observation in observations:
observation_type = observation.request.request_group.observation_type
configuration_time = timedelta(seconds=0)
Expand All @@ -79,20 +75,20 @@ def handle(self, *args, **options):
pass

attempted_time[observation_type] += (configuration_time.total_seconds() / 3600.0)
logger.info(
print(
"Proposal: {}, Instrument Type: {}, Used {} NORMAL hours, {} RAPID_RESPONSE hours, and {} TIME_CRITICAL hours".format(
proposal.id, instrument_type, attempted_time['NORMAL'], attempted_time['RAPID_RESPONSE'],
attempted_time['TIME_CRITICAL']
))
attempted_time['TIME_CRITICAL']), file=self.stdout
)

time_allocation = TimeAllocation.objects.get(proposal=proposal, instrument_type=instrument_type,
semester=semester)
if not math.isclose(time_allocation.std_time_used, attempted_time['NORMAL'], abs_tol=0.0001):
logger.warning("{} is different from existing NORMAL time {}".format(attempted_time['NORMAL'], time_allocation.std_time_used))
print("{} is different from existing NORMAL time {}".format(attempted_time['NORMAL'], time_allocation.std_time_used), file=self.stderr)
if not math.isclose(time_allocation.rr_time_used, attempted_time['RAPID_RESPONSE'], abs_tol=0.0001):
logger.warning("{} is different from existing RAPID_RESPONSE time {}".format(attempted_time['RAPID_RESPONSE'], time_allocation.rr_time_used))
print("{} is different from existing RAPID_RESPONSE time {}".format(attempted_time['RAPID_RESPONSE'], time_allocation.rr_time_used), file=self.stderr)
if not math.isclose(time_allocation.tc_time_used, attempted_time['TIME_CRITICAL'], abs_tol=0.0001):
logger.warning("{} is different from existing TIME_CRITICAL time {}".format(attempted_time['TIME_CRITICAL'], time_allocation.tc_time_used))
print("{} is different from existing TIME_CRITICAL time {}".format(attempted_time['TIME_CRITICAL'], time_allocation.tc_time_used), file=self.stderr)

if not options['dry_run']:
# Update the time allocation for this proposal accordingly
Expand Down
59 changes: 59 additions & 0 deletions observation_portal/observations/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from django.core import cache
from dateutil.parser import parse as datetime_parser
from datetime import timedelta
from io import StringIO
from django.core.management import call_command

from observation_portal.requestgroups.models import RequestGroup, Window, Location
from observation_portal.observations.time_accounting import configuration_time_used
Expand Down Expand Up @@ -1080,3 +1082,60 @@ def test_multiple_requests_leads_to_consistent_time_accounting(self):
self.time_allocation.refresh_from_db()
time_used += (config_end - config_start).total_seconds() / 3600.0
self.assertAlmostEqual(self.time_allocation.std_time_used, time_used, 5)


class TestTimeAccountingCommand(TestObservationApiBase):
def setUp(self):
super().setUp()
self.time_allocation = mixer.blend(TimeAllocation, instrument_type='1M0-SCICAM-SBIG', semester=self.semester,
proposal=self.proposal, std_allocation=100, rr_allocation=100,
tc_allocation=100, ipp_time_available=100)

def _add_observation(self, state, time_completed):
observation = Observation.objects.create(request=self.requestgroup.requests.first(), state=state, site='tst', enclosure='domb', telescope='1m0a',
start=datetime(2016,9,5,22,35,39), end=datetime(2016,9,5,23,35,40))
config_status = ConfigurationStatus.objects.create(observation=observation, configuration=self.requestgroup.requests.first().configurations.first(),
state=state, instrument_name='xx03', guide_camera_name='xx03')
Summary.objects.create(configuration_status=config_status, start=datetime(2016,9,5,22,35,39),
end=datetime(2016,9,5,23,35,40), time_completed=time_completed, state=state)
return observation

def test_with_no_obs_command_reports_no_time_used(self):
command_output = StringIO()
command_err = StringIO()
call_command('time_accounting', f'-p{self.proposal.id}', '-i1M0-SCICAM-SBIG', f'-s{self.semester.id}', stdout=command_output, stderr=command_err)
command_out = command_output.getvalue()
self.assertIn('Used 0 NORMAL hours, 0 RAPID_RESPONSE hours, and 0 TIME_CRITICAL hours', command_out)
self.assertNotIn('is different from existing', command_err)
self.time_allocation.refresh_from_db()
self.assertEqual(self.time_allocation.std_time_used, 0)
self.assertEqual(self.time_allocation.rr_time_used, 0)
self.assertEqual(self.time_allocation.tc_time_used, 0)

def test_with_one_obs_command_reports_time_used_and_modifies_time(self):
command_output = StringIO()
command_err = StringIO()
observation = self._add_observation(state='COMPLETED', time_completed=1000)
# reset time used to 0 since creating the observation already modified it
self.time_allocation.std_time_used = 0
self.time_allocation.save()
call_command('time_accounting', f'-p{self.proposal.id}', '-i1M0-SCICAM-SBIG', f'-s{self.semester.id}', stdout=command_output, stderr=command_err)
time_used = (observation.configuration_statuses.first().summary.end - observation.configuration_statuses.first().summary.start).total_seconds() / 3600.0
self.assertIn(f'Used {time_used} NORMAL hours, 0 RAPID_RESPONSE hours, and 0 TIME_CRITICAL hours', command_output.getvalue())
self.assertIn('is different from existing', command_err.getvalue())
self.time_allocation.refresh_from_db()
self.assertAlmostEqual(self.time_allocation.std_time_used, time_used)

def test_with_one_obs_command_reports_dry_run_doesnt_modify_time(self):
command_output = StringIO()
command_err = StringIO()
observation = self._add_observation(state='COMPLETED', time_completed=1000)
# reset time used to 0 since creating the observation already modified it
self.time_allocation.std_time_used = 0
self.time_allocation.save()
call_command('time_accounting', f'-p{self.proposal.id}', '-i1M0-SCICAM-SBIG', f'-s{self.semester.id}', '-d', stdout=command_output, stderr=command_err)
time_used = (observation.configuration_statuses.first().summary.end - observation.configuration_statuses.first().summary.start).total_seconds() / 3600.0
self.assertIn(f'Used {time_used} NORMAL hours, 0 RAPID_RESPONSE hours, and 0 TIME_CRITICAL hours', command_output.getvalue())
self.assertIn('is different from existing', command_err.getvalue())
self.time_allocation.refresh_from_db()
self.assertEqual(self.time_allocation.std_time_used, 0)
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h4>
Principal Investigator: {{ object.pi.first_name }} {{ object.pi.last_name }}
<a href="mailto:{{ object.pi.email }}">{{ object.pi.email }}</a>
</h4>
<h4>Co Investigators <small><i id="toggleci" class="fa fa-eye"></i></h4>
<h4>Co Investigators <small><i id="toggleci" class="fa fa-eye"></i></small></h4>
{% if not user.profile.simple_interface or user == object.pi %}
<table class="table" id="cilist">
<thead>
Expand All @@ -37,15 +37,15 @@ <h4>Co Investigators <small><i id="toggleci" class="fa fa-eye"></i></h4>
<td>
{% if mem.time_limit < 0 %}No Limit{% else %}{{ mem.time_limit_hours | floatformat:3 }}{% endif %}
{% if user == object.pi %}
&nbsp;<i class="fa fa-pencil memlimit" data-membership="{{ mem.id }}"></i>
<a href="#" class="memlimit"><i class="fas fa-edit" data-membership="{{ mem.id }}"></i></a>
<form method="POST" action="{% url 'proposals:membership-limit' pk=mem.id %}" class="form-inline limitform" style="display: none">
{% csrf_token %}
<div class="form-group">
<input name="time_limit" type="number" class="form-control" style="width: 100px" step="0.001"
<div class="form-group mr-md-2 mb-md-1">
<input name="time_limit" type="number" min="-1" class="form-control" style="width: 90px" step="0.01"
{% if mem.time_limit > 0 %} value="{{ mem.time_limit_hours | floatformat:3 }}" {% endif %} />
</div>
<button type="submit" class="btn btn-default remove-limit">Remove Limit</button>
<button type="submit" class="btn btn-default">Set Limit</button>
<button type="submit" class="btn btn-outline-secondary remove-limit mr-md-2">Remove Limit</button>
<button type="submit" class="btn btn-outline-secondary">Set Limit</button>
</form>
{% endif %}
</td>
Expand Down Expand Up @@ -82,7 +82,7 @@ <h4>Time Allocation</h4>
{% for ta in ta.list %}
<tr class="bg-grey">
<td></td>
<td>{{ ta.instrument_name }}</td>
<td>{{ ta.instrument_type }}</td>
<td colspan="2"></td>
</tr>
<tr>
Expand Down Expand Up @@ -127,7 +127,7 @@ <h4>Time Allocation</h4>
<form method="POST" action="" class="form">
{% csrf_token %}
{% bootstrap_form form=notification_form %}
<button type="submit" class="btn btn-default">Save</button>
<button type="submit" class="btn btn-outline-secondary">Save</button>
</form>
<br/>
<dl>
Expand All @@ -143,11 +143,11 @@ <h4>Time Allocation</h4>
</div>
<form method="POST" action="{% url 'proposals:membership-global' pk=object.id %}" class="form-inline">
{% csrf_token %}
<div class="form-group">
<input name="time_limit" type="number" class="form-control" style="width: 100px" step="0.001" placeholder="Hours"/>
<div class="form-group mr-md-2">
<input name="time_limit" type="number" class="form-control" style="width: 90px" min="-1" step="0.01" placeholder="Hours"/>
</div>
<button type="submit" class="btn btn-default remove-limit">Remove Limit</button>
<button type="submit" class="btn btn-default">Set Global Limit</button>
<button type="submit" class="btn btn-outline-secondary remove-limit mr-md-2">Remove Limit</button>
<button type="submit" class="btn btn-outline-secondary">Set Global Limit</button>
</form>
<br/>
<strong>Invite Co-Investigators</strong>
Expand All @@ -161,11 +161,11 @@ <h4>Time Allocation</h4>
</div>
<form method="POST" action="{% url 'proposals:invite' pk=object.id %}" class="form-inline">
{% csrf_token %}
<div class="form-group">
<div class="form-group mr-md-2">
<label class="sr-only" for="email">Invite a Co-Investigator</label>
<input name="email" class="form-control" placeholder="Email Address(s)"/>
</div>
<button type="submit" class="btn btn-default">Add</button>
<button type="submit" class="btn btn-outline-secondary">Add</button>
</form>
<br/>
<dl>
Expand Down Expand Up @@ -196,7 +196,8 @@ <h4>Time Allocation</h4>
$(this).parent().find('input[name="time_limit"]').val('-1');
});

$('.memlimit').click(function(){
$('.memlimit').click(function(event){
event.preventDefault();
$('.limitform').hide();
$(this).next().first().show();
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ <h4>
<div class="row">
<div class="col-md-12">
<form method="get" action="" class="form-inline pull-right">
{% bootstrap_field filter.form.active %}
{% bootstrap_field filter.form.semester %}
{% bootstrap_button button_type="submit" content="Filter" %}
{% bootstrap_field filter.form.active form_group_class="form-group mr-md-3" %}
{% bootstrap_field filter.form.semester form_group_class="form-group mr-md-3" %}
{% bootstrap_button button_type="submit" content="Filter" button_class="btn-outline-secondary" %}
</form>
<table class="table table-striped">
<thead>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,29 @@
<div class="form form-inline">
<div class="form-group">
<label for="semester" class="control-label">Semester</label>
<select class="form-control" onchange="location = this.value">
<select class="form-control ml-md-1" onchange="location = this.value">
{% for semester in semesters %}
<option value="{% url 'proposals:semester-admin' semester %}"
{% if object == semester %} selected {% endif %}>
{{ semester }}
</option>
{% endfor %}
</select>
<div class="checkbox">
<div class="checkbox ml-md-2">
<label>
<input id="completed" type="checkbox" onclick="selectCompleted()"> Completed
</label>
</div>
</div>
</div>
</div>
</div>
<table id="semester-table" class="table table-striped">
</select>
</select>
<thead>
<tr>
<th>Proposal</th>
<th>Class</th>
<th>Instrument</th>
<th>PI</th>
<th>Priority</th>
<th>Std Alloc</th>
Expand All @@ -63,7 +63,7 @@
<td><a href="{% url 'proposals:detail' ta.proposal.id %}">{{ ta.proposal.id }}</a></td>
<td>{{ ta.proposal.pi.first_name }} {{ ta.proposal.pi.last_name }}</td>
<td>{{ ta.proposal.tac_priority }}</td>
<td>{{ ta.instrument_name }}</td>
<td>{{ ta.instrument_type }}</td>
<td>{{ ta.std_allocation|floatformat }}</td>
<td {% if ta.std_allocation > 0 and ta.std_time_used >= ta.std_allocation %} class="bg-danger" {% endif %}>
{{ ta.std_time_used|floatformat }}
Expand Down Expand Up @@ -93,10 +93,10 @@
</div>
{% endblock %}
{% block extra_javascript %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.12.2/bootstrap-table.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.14.2/bootstrap-table.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js"></script>
<script src="https://cdn.lco.global/script/tableExport.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.12.2/extensions/export/bootstrap-table-export.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.14.2/extensions/export/bootstrap-table-export.min.js"></script>
<script type="text/javascript">
$('#semester-table').bootstrapTable({
sortName: 'proposal',
Expand Down
4 changes: 2 additions & 2 deletions observation_portal/requestgroups/test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2141,7 +2141,7 @@ def setUp(self):

def test_contention_no_auth(self):
response = self.client.get(
reverse('api:contention', kwargs={'instrument_name': '1M0-SCICAM-SBIG'})
reverse('api:contention', kwargs={'instrument_type': '1M0-SCICAM-SBIG'})
)
self.assertNotEqual(response.json()['contention_data'][1]['All Proposals'], 0)
self.assertEqual(response.json()['contention_data'][2]['All Proposals'], 0)
Expand All @@ -2150,7 +2150,7 @@ def test_contention_staff(self):
user = mixer.blend(User, is_staff=True)
self.client.force_login(user)
response = self.client.get(
reverse('api:contention', kwargs={'instrument_name': '1M0-SCICAM-SBIG'})
reverse('api:contention', kwargs={'instrument_type': '1M0-SCICAM-SBIG'})
)
self.assertNotEqual(response.json()['contention_data'][1][self.request.request_group.proposal.id], 0)
self.assertNotIn(self.request.request_group.proposal.id, response.json()['contention_data'][2])
Expand Down
Loading

0 comments on commit ffc5a8b

Please sign in to comment.