Commit 3121bd2f authored by xaingling's avatar xaingling

update features and fix bug

parent f67fb2cd
......@@ -10,3 +10,10 @@ class CommentBoxAdminBase(admin.ModelAdmin):
instance.user = request.user
formset.save()
class BaseCommentBoxInline(admin.TabularInline):
extra = 0
readonly_fields = ('user', 'created_date', 'modified_date')
fields = ('user', 'created_date', 'modified_date', 'event_time', 'comment')
from django.db import models
class ModelBase(models.Model):
class Meta:
abstract = True
"""
Most of our model has modified date and create date
"""
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
class CommentModelBase(ModelBase):
class Meta:
abstract = True
event_time = models.DateTimeField(null=True, blank=True)
import os
import json
MOCK_JSON_FILE_NAME = "mock_json_data"
JSON_DATA_FILES = ["institution", "manufacturer", "project", "platform_type", "platform", "instrument", "power_type",
"deployment", "sensor", "instrument_on_platform", "sensor_on_instrument"]
def create_mock_database():
from general import models as general
from instruments import models as instruments
from platforms import models as platforms
json_dict_list = import_json_contents()
project_json = json_dict_list["project"]
for x in project_json:
general.Project.objects.create(**filter_out_create_day_modified_day(x))
institution_json = json_dict_list["institution"]
for x in institution_json:
general.Institution.objects.create(**filter_out_create_day_modified_day(x))
manufacturer_json = json_dict_list["manufacturer"]
for x in manufacturer_json:
general.Manufacturer.objects.create(**filter_out_create_day_modified_day(x))
platform_type_json = json_dict_list["platform_type"]
for x in platform_type_json:
platforms.PlatformType.objects.create(**replace_to_id(filter_out_create_day_modified_day(x)))
platform_json = json_dict_list["platform"]
for x in platform_json:
platforms.Platform.objects.create(**replace_to_id(filter_out_create_day_modified_day(x)))
instrument_json = json_dict_list["instrument"]
for x in instrument_json:
instruments.Instrument.objects.create(**replace_to_id(filter_out_create_day_modified_day(x)))
power_type_json = json_dict_list["power_type"]
for x in power_type_json:
platforms.PlatformPowerType.objects.create(**replace_to_id(filter_out_create_day_modified_day(x)))
deployment_json = json_dict_list["deployment"]
for x in deployment_json:
platforms.PlatformDeployment.objects.create(**replace_to_id(filter_out_create_day_modified_day(x)))
sensor_json = json_dict_list["sensor"]
for x in sensor_json:
instruments.Sensor.objects.create(**replace_to_id(filter_out_create_day_modified_day(x)))
instrument_on_platform_json = json_dict_list["instrument_on_platform"]
for x in instrument_on_platform_json:
instruments.InstrumentOnPlatform.objects.create(**replace_to_id(filter_out_create_day_modified_day(x)))
sensor_on_instrument_json = json_dict_list["sensor_on_instrument"]
for x in sensor_on_instrument_json:
instruments.SensorOnInstrument.objects.create(**replace_to_id(filter_out_create_day_modified_day(x)))
return json_dict_list
def import_json_contents():
current = os.path.dirname(os.path.realpath(__file__))
json_folder = os.path.join(current, MOCK_JSON_FILE_NAME)
json_dict = dict()
for x in JSON_DATA_FILES:
with open(os.path.join(json_folder, ".".join([x, "json"]))) as f:
json_dict[x] = json.loads(f.read())["results"]
return json_dict
def filter_out_create_day_modified_day(the_dict):
the_dict.pop("the_dict", None)
the_dict.pop("created_date", None)
the_dict.pop("modified_date", None)
return the_dict
def replace_to_id(the_dict):
for x in JSON_DATA_FILES:
if x in the_dict:
the_dict[x + "_id"] = the_dict.pop(x)
return the_dict
from django.urls import reverse
from django.db.models import Q
from django.urls import get_script_prefix
def make_edit_link(instance):
......@@ -14,12 +15,17 @@ def make_add_link(instance):
return link
def make_server_compatibility_relative_url(url):
the_link = get_script_prefix()[:-1] + url
return the_link
def qs_time_overlap(base_qs, start_time, end_time):
if end_time:
soi_qs_overlap = base_qs.filter(
Q(start_time__lte=start_time, end_time__gte=start_time) | Q(start_time__lte=end_time,
end_time__gte=end_time))
else:
soi_qs_overlap = base_qs.filter(Q(end_time=None) | Q(start_time__lte=start_time))
soi_qs_overlap = base_qs.filter(Q(end_time=None) | Q(start_time__gte=start_time))
return soi_qs_overlap
# Generated by Django 2.2.6 on 2019-11-25 16:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('general', '0002_auto_20190611_1951'),
]
operations = [
migrations.AlterField(
model_name='institution',
name='city',
field=models.CharField(blank=True, max_length=40, null=True),
),
migrations.AlterField(
model_name='institution',
name='country',
field=models.CharField(blank=True, max_length=80, null=True),
),
migrations.AlterField(
model_name='institution',
name='postal_code',
field=models.CharField(blank=True, max_length=20, null=True),
),
migrations.AlterField(
model_name='institution',
name='province',
field=models.CharField(blank=True, max_length=80, null=True),
),
migrations.AlterField(
model_name='institution',
name='street',
field=models.TextField(blank=True, max_length=255, null=True),
),
]
# Generated by Django 2.2.6 on 2019-11-25 16:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('general', '0003_auto_20191125_1652'),
]
operations = [
migrations.AlterField(
model_name='institution',
name='url',
field=models.CharField(blank=True, help_text="The institution's URL", max_length=1000, null=True),
),
]
from django.db import models
from common.model import ModelBase
class Institution(models.Model):
class Institution(ModelBase):
# Name of the institution
name = models.CharField(
max_length=300,
......@@ -10,38 +11,35 @@ class Institution(models.Model):
# re.match(r'(https?(://)?)?(?P<url>.*)', url)
url = models.CharField(
max_length=1000,
help_text="The institution's URL"
help_text="The institution's URL",
blank=True, null=True
)
# Location of the institution
street = models.TextField(max_length=255)
city = models.CharField(max_length=40)
province = models.CharField(max_length=80)
postal_code = models.CharField(max_length=20)
country = models.CharField(max_length=80)
street = models.TextField(max_length=255, blank=True, null=True)
city = models.CharField(max_length=40, blank=True, null=True)
province = models.CharField(max_length=80, blank=True, null=True)
postal_code = models.CharField(max_length=20, blank=True, null=True)
country = models.CharField(max_length=80, blank=True, null=True)
# Contact at the institution
contact_name = models.CharField(max_length=70, blank=True, null=True)
contact_phone = models.CharField(max_length=15, blank=True, null=True)
contact_email = models.CharField(max_length=255, blank=True, null=True)
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
def __str__(self):
return self.name
class Project(models.Model):
class Project(ModelBase):
name = models.CharField(
max_length=1000,
help_text="<b>Example:</b> Collaborative Operations with Mote Marine Laboratory"
)
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
def __str__(self):
return self.name
class Manufacturer(models.Model):
class Manufacturer(ModelBase):
name = models.CharField(max_length=300)
street = models.TextField(max_length=255, blank=True, null=True)
city = models.CharField(max_length=40, blank=True, null=True)
......@@ -52,8 +50,6 @@ class Manufacturer(models.Model):
contact_name = models.CharField(max_length=70, blank=True, null=True)
contact_phone = models.CharField(max_length=15, blank=True, null=True)
contact_email = models.CharField(max_length=255, blank=True, null=True)
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
def __str__(self):
return self.name
import warnings
from django.contrib import admin
from django.forms import ModelForm
from django import forms
from datetime import datetime
from django.db.models import Q
from django.core.exceptions import ObjectDoesNotExist
from instruments.admin_filter import (
SensorOnInstrumentPlatformFilter,
)
from suit.widgets import SuitSplitDateTimeWidget
from instruments.admin_filter import (
InstrumentPlatformNameFilter,
InstrumentOnPlatformSortFilter,
......@@ -33,21 +29,16 @@ from .models import (
SensorOnInstrument,
)
from platforms.models import Platform
from common.admin_common import CommentBoxAdminBase
from common.utilities import make_edit_link, make_add_link
from django.utils.safestring import mark_safe
from common.utilities import qs_time_overlap
class InstrumentOnPlatformForm(ModelForm):
class Meta:
model = InstrumentOnPlatform
fields = '__all__'
widgets = {
'start_time': SuitSplitDateTimeWidget,
'end_time': SuitSplitDateTimeWidget
}
from instruments.model_form import (
InstrumentOnPlatformForm,
SensorForm,
InstrumentForm,
SensorOnInstrumentForm,
InstrumentCommentBoxForm
)
from common.admin_common import BaseCommentBoxInline
class InstrumentOnPlatformAdmin(admin.ModelAdmin):
......@@ -80,25 +71,6 @@ class InstrumentOnPlatformAdmin(admin.ModelAdmin):
admin.site.register(InstrumentOnPlatform, InstrumentOnPlatformAdmin)
class SensorForm(ModelForm):
class Meta:
model = Sensor
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
include_in_output = cleaned_data.get("include_in_output")
long_name = cleaned_data.get("long_name")
if include_in_output and not long_name:
# Only do something if both fields are valid so far.
raise forms.ValidationError(
"Long name must be given when included in output checked"
)
return self.cleaned_data
@admin.register(Sensor)
class SensorAdmin(admin.ModelAdmin):
form = SensorForm
......@@ -111,6 +83,23 @@ class SensorAdmin(admin.ModelAdmin):
SensorPlatformNameFilter,
SensorInstrumentIdentifierFilter,
)
change_form_template = 'admin/custom_sensor_change_form.html'
def change_view(self, request, object_id, form_url='', extra_context=None):
sensor_obj = Sensor.objects.get(id=int(object_id))
instrument_on_platform_qs = SensorOnInstrument.objects.filter(sensor=sensor_obj).order_by(
'start_time').prefetch_related('instrument').prefetch_related('sensor')
objs = list(instrument_on_platform_qs)
for obj in objs:
obj.url_edit_link = make_edit_link(obj)
obj.url_sensor_change = make_edit_link(obj.sensor)
sensor_on_instrument_add_link = make_add_link(SensorOnInstrument)
extra_context = {
"extra_content": objs,
"sensor_on_instrument_add_link": sensor_on_instrument_add_link,
}
return super().change_view(request, object_id, form_url='', extra_context=extra_context)
def delete_queryset(self, request, queryset):
super().delete_queryset(request, queryset)
......@@ -150,46 +139,6 @@ class SensorAdmin(admin.ModelAdmin):
super().save_model(request, obj, form, change)
class InstrumentForm(ModelForm):
class Meta:
model = Instrument
fields = '__all__'
class PlatformForm(ModelForm):
class Meta:
model = Platform
fields = '__all__'
class PlatformInline(admin.StackedInline):
model = InstrumentOnPlatform
exclude = ["comment"]
readonly_fields = ["platform", "start_time", "end_time"]
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.prefetch_related('instrument').prefetch_related('platform')
return qs
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
class InstrumentOnPlatformDisplayItem:
def __init__(self, instance):
self.instrument_on_platform_instance = instance
class SensorOnInstrumentInline(admin.StackedInline):
model = Sensor
min_num = 0
@admin.register(Instrument)
class InstrumentAdmin(admin.ModelAdmin):
readonly_fields = ('created_date', 'modified_date')
......@@ -200,7 +149,7 @@ class InstrumentAdmin(admin.ModelAdmin):
search_fields = ['identifier', 'short_name', 'long_name', 'serial', 'manufacturer__name']
list_display = ('identifier', 'short_name', 'long_name', 'serial', 'manufacturer', 'created_date', 'modified_date')
form = InstrumentForm
change_form_template = 'admin/custom_change_form.html'
change_form_template = 'admin/custom_instrument_change_form.html'
list_per_page = 40
def get_queryset(self, request):
......@@ -235,41 +184,6 @@ class InstrumentAdmin(admin.ModelAdmin):
return super().change_view(request, object_id, form_url='', extra_context=extra_context)
class SensorOnInstrumentForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.initial:
self.fields['sensor'].disabled = True
self.fields['instrument'].disabled = True
class Meta:
model = SensorOnInstrument
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
instrument = cleaned_data.get("instrument")
sensor = cleaned_data.get("sensor")
start_time = cleaned_data.get("start_time")
end_time = cleaned_data.get("end_time")
base_soi_qs = SensorOnInstrument.objects.filter(instrument=instrument, sensor=sensor)
soi_qs_overlap = qs_time_overlap(base_soi_qs, start_time, end_time)
soi_qs_overlap_count = soi_qs_overlap.count()
if soi_qs_overlap_count != 0:
msg = "You can't save this table since it overlap with \n"
for soi in soi_qs_overlap:
msg = msg + "{} {} {}\n".format(soi.instrument.identifier, soi.sensor.identifier,
"<a href=\"{}\">sensor_on_instrument</a>".format(make_edit_link(soi)))
raise forms.ValidationError(
mark_safe(msg)
)
return self.cleaned_data
@admin.register(SensorOnInstrument)
class SensorOnInstrumentAdmin(admin.ModelAdmin):
list_display = ('sensor', 'instrument', 'start_time', 'end_time')
......@@ -306,18 +220,8 @@ class SensorOnInstrumentAdmin(admin.ModelAdmin):
super().save_model(request, obj, form, change)
class InstrumentCommentBoxInline(admin.TabularInline):
class InstrumentCommentBoxInline(BaseCommentBoxInline):
model = InstrumentComment
extra = 0
readonly_fields = ('user', 'created_date', 'modified_date')
fields = ('user', 'created_date', 'modified_date', 'comment')
class InstrumentCommentBoxForm(ModelForm):
class Meta:
model = InstrumentCommentBox
fields = ('instrument',)
class InstrumentCommentBoxAdmin(CommentBoxAdminBase):
......
# Generated by Django 2.2.6 on 2019-11-26 15:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instruments', '0022_auto_20191121_1054'),
]
operations = [
migrations.AddField(
model_name='instrumentcomment',
name='event_time',
field=models.DateTimeField(blank=True, null=True),
),
]
from django.forms import ModelForm
from django import forms
from django.db.models import Q
from suit.widgets import SuitSplitDateTimeWidget
from .models import (
Instrument,
InstrumentOnPlatform,
Sensor,
SensorOnInstrument,
InstrumentCommentBox
)
from common.utilities import make_edit_link
from django.utils.safestring import mark_safe
from common.utilities import qs_time_overlap
class InstrumentOnPlatformForm(ModelForm):
class Meta:
model = InstrumentOnPlatform
fields = '__all__'
widgets = {
'start_time': SuitSplitDateTimeWidget,
'end_time': SuitSplitDateTimeWidget
}
class SensorForm(ModelForm):
class Meta:
model = Sensor
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
include_in_output = cleaned_data.get("include_in_output")
long_name = cleaned_data.get("long_name")
if include_in_output and not long_name:
# Only do something if both fields are valid so far.
raise forms.ValidationError(
"Long name must be given when included in output checked"
)
return self.cleaned_data
class InstrumentForm(ModelForm):
class Meta:
model = Instrument
fields = '__all__'
class SensorOnInstrumentForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.initial:
self.fields['sensor'].disabled = True
self.fields['instrument'].disabled = True
class Meta:
model = SensorOnInstrument
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
instrument = cleaned_data.get("instrument")
sensor = cleaned_data.get("sensor")
start_time = cleaned_data.get("start_time")
end_time = cleaned_data.get("end_time")
base_soi_qs = SensorOnInstrument.objects.filter(instrument=instrument, sensor=sensor)
self_instance = self.instance
soi_qs_overlap = qs_time_overlap(base_soi_qs, start_time, end_time)
the_id = self_instance.id
if the_id:
soi_qs_overlap = soi_qs_overlap.filter(~Q(pk=the_id))
soi_qs_overlap_count = soi_qs_overlap.count()
if soi_qs_overlap_count != 0:
msg = "You can't save this table since it overlap with \n"
for soi in soi_qs_overlap:
msg = msg + "{} {} {}\n".format(soi.instrument.identifier, soi.sensor.identifier,
"<a href=\"{}\">sensor_on_instrument</a>".format(make_edit_link(soi)))
raise forms.ValidationError(
mark_safe(msg)
)
return self.cleaned_data
class InstrumentCommentBoxForm(ModelForm):
class Meta:
model = InstrumentCommentBox
fields = ('instrument',)
from django.db import models
from django.contrib.auth.admin import User
from common.model import CommentModelBase, ModelBase
class Instrument(models.Model):
class Instrument(ModelBase):
identifier = models.CharField(
max_length=300,
help_text="The name used to identify this instrument in the raw data. IE: SATCTD7229, sci_water"
......@@ -35,8 +36,6 @@ class Instrument(models.Model):
blank=True,
help_text="This is a good place to document anything unusual about this instrument's configuration"
)
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
def __str__(self):
return_string = '%s - %s' % (self.identifier, self.short_name)
......@@ -52,27 +51,27 @@ class InstrumentCommentBox(models.Model):
class Meta:
verbose_name = 'Instrument Comment Box'
verbose_name_plural = 'Instrument Comment Boxes'
instrument = models.OneToOneField('Instrument', on_delete=models.PROTECT)
def __str__(self):
return "%s comment box" % (self.instrument)
class InstrumentComment(models.Model):
class InstrumentComment(CommentModelBase):
user = models.ForeignKey(User, on_delete=models.PROTECT)
comment = models.TextField(help_text="Comments")
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
instrument_comment_box = models.ForeignKey(InstrumentCommentBox, on_delete=models.CASCADE)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
def __str__(self):
return "%s" % (self.id)
class InstrumentOnPlatform(models.Model):
class InstrumentOnPlatform(ModelBase):
class Meta:
verbose_name = 'Instrument on Platform History'
verbose_name_plural = 'Instrument on Platform History'
instrument = models.ForeignKey(
Instrument,
help_text="The instrument that was put on a platform",
......@@ -94,14 +93,12 @@ class InstrumentOnPlatform(models.Model):
help_text="The date the instrument was removed from the platform"
)
comment = models.TextField(null=True, blank=True)
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
def __str__(self):
return "%s - %s - %s" % (self.instrument, self.platform, self.start_time)
class Sensor(models.Model):
class Sensor(ModelBase):
identifier = models.CharField(
max_length=300,
help_text="The name used to identify this sensor in the raw data. ie: sci_water_temp"
......@@ -190,17 +187,16 @@ class Sensor(models.Model):
blank=True,
help_text="This is a good place to document anything unusual about this particular sensor. IE: wavelengths for spectral sensors"
)
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
def __str__(self):
return "%s" % (self.identifier)
class SensorOnInstrument(models.Model):
class SensorOnInstrument(ModelBase):
class Meta:
verbose_name = 'Sensor on Instrument History'
verbose_name_plural = 'Sensor on Instrument History'
instrument = models.ForeignKey(
Instrument,
help_text="The instrument that was put on a platform",
......@@ -222,8 +218,6 @@ class SensorOnInstrument(models.Model):
help_text="The date the instrument was removed from the platform"
)
comment = models.TextField(null=True, blank=True)
created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
modified_date = models.DateTimeField(auto_now=True, null=True, blank=True)
def __str__(self):
return "%s - %s - %s" % (self.sensor, self.instrument, self.start_time)
from django.contrib.admin.sites import AdminSite
from django.test import SimpleTestCase, TestCase
from django.test.utils import isolate_apps
from datetime import datetime
from instruments.admin import SensorOnInstrumentAdmin
from instruments.models import Instrument, SensorOnInstrument, Sensor, InstrumentOnPlatform, InstrumentComment, \
InstrumentCommentBox
from instruments.tests.create_mock_sensor_tracker_database import create_mock_database
from instruments.admin import SensorOnInstrumentForm
class InstrumentAdminTests(TestCase):
def setUp(self):
create_mock_database()
self.site = AdminSite()
def test_create_sensor_on_instrument(self):
now = datetime.now()