Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion ifcbdb/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,10 @@ class BinManagementActions(Enum):
UNASSIGN_DATASET = "unassign-dataset"

# Metadata column names
BIN_ID_COLUMNS = ['id','pid','lid','bin','bin_id','sample','sample_id','filename']
BIN_ID_COLUMNS = ['id','pid','lid','bin','bin_id','sample','sample_id','filename']

class BinManagementDatasetFilters(Enum):
UNASSIGNED = "__unassigned__"

class BinManagementTeamFilters(Enum):
UNASSIGNED = "__unassigned__"
15 changes: 11 additions & 4 deletions ifcbdb/dashboard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from .tasks import mosaic_coordinates_task
from .mosaic import Mosaic

from common.constants import TeamRoles
from common.constants import TeamRoles, BinManagementDatasetFilters, BinManagementTeamFilters

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -226,7 +226,10 @@ def bin_query(dataset_name=None, start=None, end=None, tags=[],
qs = Timeline(qs).time_range(start, end)

if dataset_name:
qs = qs.filter(datasets__name=dataset_name)
if dataset_name == BinManagementDatasetFilters.UNASSIGNED.value:
qs = qs.filter(datasets=None)
else:
qs = qs.filter(datasets__name=dataset_name)

if tags is not None:
for tag in tags:
Expand All @@ -241,10 +244,14 @@ def bin_query(dataset_name=None, start=None, end=None, tags=[],
if sample_type not in [None, ""]:
qs = qs.filter(sample_type__iexact=sample_type)


if team_names is not None and len(team_names) > 0:
team_ids = list(Team.objects.filter(name__in=team_names).values_list("id", flat=True))
if BinManagementTeamFilters.UNASSIGNED.value in team_names:
qs = qs.filter(team__isnull=True)
else:
team_ids = list(Team.objects.filter(name__in=team_names).values_list("id", flat=True))

qs = qs.filter(team_id__in=team_ids)
qs = qs.filter(team_id__in=team_ids)

return qs

Expand Down
29 changes: 21 additions & 8 deletions ifcbdb/secure/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
Bin, Team, TeamUser, TeamDataset, \
DEFAULT_LATITUDE, DEFAULT_LONGITUDE, DEFAULT_ZOOM_LEVEL, normalize_tag_name
from common import auth
from common.constants import BinManagementActions, TeamRoles
from common.constants import BinManagementActions, TeamRoles, BinManagementDatasetFilters, BinManagementTeamFilters

from common.constants import BinManagementTeamFilters, BinManagementDatasetFilters

MIN_LATITUDE = -90
MAX_LATITUDE = 90
Expand Down Expand Up @@ -412,10 +414,8 @@ class BinSearchForm(forms.Form):
# just the team that's selected (if enabled). This removes the validation logic on those values,
# but that is covered by the validation on the team, since that will restrict all results down
# to just bins associated with a team the user has access to
team = forms.ModelChoiceField(
team = forms.CharField(
required=False,
queryset=Team.objects.none(),
empty_label=" ",
widget=forms.Select(attrs={"class": input_classes}))
dataset = forms.CharField(
required=False,
Expand All @@ -434,14 +434,22 @@ class BinSearchForm(forms.Form):
widget=forms.Select(attrs={"class": input_classes}))

def __init__(self, *args, **kwargs):
user = kwargs.pop("user") if "user" in kwargs else None
self.user = kwargs.pop("user") if "user" in kwargs else None

super().__init__(*args, **kwargs)

is_teams_enabled = waffle.switch_is_active("Teams")
teams = auth.get_associated_teams(user)
teams = auth.get_associated_teams(self.user)

# The teams list is based on what the user has access two. Only superadmins are allowed search for bins not
# yet associated with a team
team_choices = [("", " ")]
if auth.is_admin(self.user):
team_choices.append((BinManagementTeamFilters.UNASSIGNED.value, "Unassigned"))
for team in teams:
team_choices.append((team.pk, team.name))

self.fields["team"].queryset = teams.order_by("name")
self.fields["team"].widget.choices = team_choices
self.fields["dataset"].widget.attrs["disabled"] = is_teams_enabled
self.fields["instrument"].widget.attrs["disabled"] = is_teams_enabled
self.fields["tag"].widget.attrs["disabled"] = is_teams_enabled
Expand All @@ -455,6 +463,11 @@ def clean(self):
start_date = self.cleaned_data.get("start_date")
end_date = self.cleaned_data.get("end_date")

# Prevent non-admins from selecting "Unassigned"
team = self.cleaned_data.get("team")
if team == BinManagementTeamFilters.UNASSIGNED.value and not auth.is_admin(self.user):
raise ValidationError("You do not have permission to search for team-less bins")

# Ensure the user has entered at least one piece of criteria to prevent searching through
# the entire database of bins
values = [value for key, value in cleaned_data.items() if key != "team" and value not in [None, ""]]
Expand All @@ -472,7 +485,7 @@ def build_dataset_choices(bins):
.distinct() \
.values_list("name", flat=True)

return [""] + list(datasets)
return ["", BinManagementDatasetFilters.UNASSIGNED.value] + list(datasets)

@staticmethod
def build_tag_choices(bins):
Expand Down
43 changes: 32 additions & 11 deletions ifcbdb/secure/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from django.http import JsonResponse, Http404, HttpResponseForbidden, HttpResponse, StreamingHttpResponse
from django.shortcuts import render, get_object_or_404, redirect, reverse


import pandas as pd

from dashboard.models import Dataset, Instrument, DataDirectory, Tag, TagEvent, Bin, Comment, AppSettings, Team, \
Expand All @@ -20,8 +19,8 @@

from dashboard.accession import export_metadata
from common import auth
from common.constants import Features, TeamRoles, BinManagementActions, BIN_ID_COLUMNS

from common.constants import Features, TeamRoles, BinManagementActions, BinManagementDatasetFilters, \
BinManagementTeamFilters

from django.core.cache import cache
from celery.result import AsyncResult
Expand Down Expand Up @@ -970,15 +969,21 @@ def bin_management_criteria(request):
return redirect(reverse("secure:index"))

team_id = request.POST.get("team")
team = get_object_or_404(Team, pk=team_id)

# Ensure non-admins have selected a team they are associated with
if not auth.is_admin(request.user):
teams = auth.get_associated_teams(request.user)
if not team in teams:
# Only super-admins can search for bins w/o a team
if team_id == BinManagementTeamFilters.UNASSIGNED.value:
return HttpResponseForbidden()

# Ensure this user has access to the team
matching_teams = auth.get_associated_teams(request.user).filter(id=team_id)
if not matching_teams.exists():
return HttpResponseForbidden()

bins = Bin.objects.filter(team=team)
if team_id == BinManagementTeamFilters.UNASSIGNED.value:
bins = Bin.objects.filter(team__isnull=True)
else:
bins = Bin.objects.filter(team_id=team_id)

datasets = BinSearchForm.build_dataset_choices(bins)
instruments = BinSearchForm.build_instrument_choices(bins)
Expand Down Expand Up @@ -1087,7 +1092,7 @@ def bin_management_execute(request):

def build_bin_query_from_form_data(user, form):
dataset = form.cleaned_data.get("dataset")
team = form.cleaned_data.get("team")
team_value = form.cleaned_data.get("team")
start_date = form.cleaned_data.get("start_date")
end_date = form.cleaned_data.get("end_date")
instrument = form.cleaned_data.get("instrument")
Expand All @@ -1096,6 +1101,10 @@ def build_bin_query_from_form_data(user, form):
tag = form.cleaned_data.get("tag")
tags = [tag] if tag else []

# Pull the selected team from the form data. Only one selection is possible in the UI right now, even though the bin
# query supports a list
team_names = request_get_team_name(team_value)

return bin_management_query(
user,
start=start_date,
Expand All @@ -1105,7 +1114,7 @@ def build_bin_query_from_form_data(user, form):
tags=tags,
cruise=cruise,
sample_type=sample_type,
team_names=[team.name] if team is not None else None)
team_names=team_names)

def update_skip(bin_qs, is_skipped):
total = 0
Expand Down Expand Up @@ -1169,4 +1178,16 @@ def request_get_instrument(instrument_string):
if i is not None and i:
if i.lower().startswith('ifcb'):
i = i[4:]
return int(i)
return int(i)


def request_get_team_name(team_value: str):
if not team_value:
return None

if team_value == BinManagementTeamFilters.UNASSIGNED.value:
return [team_value]

team = Team.objects.filter(pk=int(team_value)).first()

return [team.name] if team else None
4 changes: 3 additions & 1 deletion ifcbdb/templates/secure/bin-management.html
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,9 @@ <h5>Search Results</h5>
dropdown.empty();

$.each(values, function(index, item) {
dropdown.append($('<option></option>').val(item).text(item));
const text = item === "__unassigned__" ? "Unassigned" : item;

dropdown.append($('<option></option>').val(item).text(text));
});

dropdown.val(currentValue);
Expand Down