Skip to content

Commit a08d133

Browse files
committed
Merge branch 'idc-test' of https://github.com/ImagingDataCommons/IDC-WebApp into idc-prod-sp
2 parents 93351a8 + b568db3 commit a08d133

File tree

18 files changed

+204
-181
lines changed

18 files changed

+204
-181
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ From there perform the following steps...
2121

2222
## Configuring PyCharm
2323

24-
PyCharm Pro can be used to run your Web Application as a native Django application.
24+
PyCharm Pro can be used to run your Web Application as a native Django application.
25+
26+
**NOTE:** PyCharm 2025 versions no longer include support for remote Python interpreters. Use 2024 or older!
2527

2628
### Setup
2729

@@ -61,6 +63,7 @@ To run your server in PyCharm:
6163
* Kernel header update: `sudo apt-get -y install dkms build-essential linux-headers-$(uname -r)`
6264
* NOTE: you may get a 'package not found' error here; if so, you'll need to look up the current header package for this install and use that instead.
6365
* Guest Additions ISO mounting and installation: https://docs.bitnami.com/virtual-machine/faq/configuration/install-virtualbox-guest-additions/
66+
* NOTE: in newer versions of VirtualBox the CD/DVD drives are added under Basic>Storage
6467
3. Next, set the `shell/python-su.sh` script to executable in the vagrant machine's command line with the command `chmod +x /home/vagrant/www/shell/python-su.sh`
6568
4. You can now click on the Run or Debug icons in the toolbar (upper-right corner of the PyCharm GUI)
6669
* Your server will start and the PyCharm console should show all the logs and output from the system.

etl/etl.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
from idc_collections.models import Program, Collection, Attribute, Attribute_Ranges, \
4444
Attribute_Display_Values, DataSource, DataSourceJoin, DataVersion, DataSetType, \
45-
Attribute_Set_Type, Attribute_Display_Category, ImagingDataCommonsVersion, Attribute_Tooltips
45+
Attribute_Set_Type, Attribute_Display_Category, ImagingDataCommonsVersion, Attribute_Tooltips, Citation
4646
from google_helpers.bigquery.bq_support import BigQuerySupport
4747

4848
from django.contrib.auth.models import User
@@ -319,6 +319,35 @@ def add_source_joins(froms, from_col, tos=None, to_col=None):
319319
DataSourceJoin.objects.bulk_create(src_joins)
320320

321321

322+
def load_citations(filename):
323+
try:
324+
cites_file = open(filename,"r")
325+
current_cites = [x.doi for x in Citation.objects.all()]
326+
new_cites = []
327+
updated_cites = {}
328+
for line in csv_reader(cites_file):
329+
if "doi, citation" in line:
330+
print("[STATUS] Saw header line during citation load - skipping!")
331+
continue
332+
if line[0] in current_cites:
333+
updated_cites[line[0]] = line[1]
334+
else:
335+
new_cites.append(Citation(doi=line[0], cite=line[1]))
336+
if len(new_cites):
337+
Citation.objects.bulk_create(new_cites)
338+
print("[STATUS] The following {} DOI citations were added: {}".format(len(new_cites), " ".join([x.doi for x in new_cites])))
339+
if len(updated_cites):
340+
to_update = Citation.objects.filter(doi__in=updated_cites.keys())
341+
for upd in to_update:
342+
upd.cite = updated_cites[upd.doi]
343+
Citation.objects.bulk_update(to_update, ["cite"])
344+
print("[STATUS] {} DOI citations were updated.".format(len(updated_cites)))
345+
except Exception as e:
346+
ERRORS_SEEN.append("Error seen while loading citations, check the logs!")
347+
logger.error("[ERROR] While trying to load citations: ")
348+
logger.exception(e)
349+
350+
322351
def load_collections(filename, data_version="8.0"):
323352
try:
324353
collection_file = open(filename, "r")
@@ -428,12 +457,12 @@ def create_solr_params(schema_src, solr_src):
428457
solr_index_strings = []
429458
field_types = ''
430459
add_copy_field = ''
431-
SCHEMA_BASE = '{{field_types}add-field": {fields}{add_copy_field}'
460+
SCHEMA_BASE = '{{{field_types}"add-field": {fields}{add_copy_field}}}'
432461
if len(TOKENIZED_FIELDS):
433462
field_types = '"add-field-type": { "name":"tokenizedText", "class":"solr.TextField", "analyzer" : { "tokenizer": { "name":"nGram" }}}, '
434-
copy_fields = ",".join(['{"source":"{field}","dest":"{field{}_tokenized"}'.format(field) for field in TOKENIZED_FIELDS])
435-
add_copy_field = ', "add-copy-field": [{copy_fields}]'.format(copy_fields)
436-
CORE_CREATE_STRING = "sudo -u solr /opt/bitnami/solr/bin/solr create -c {solr_src} -s 2 -rf 2"
463+
copy_fields = ",".join(['{{"source":"{field}","dest":"{field}_tokenized"}}'.format(field=field) for field in TOKENIZED_FIELDS])
464+
add_copy_field = ', "add-copy-field": [{copy_fields}]'.format(copy_fields=copy_fields)
465+
CORE_CREATE_STRING = "sudo -u solr /opt/bitnami/solr/bin/solr create -c {solr_src}"
437466
SCHEMA_STRING = "curl -u {solr_user}:{solr_pwd} -X POST -H 'Content-type:application/json' --data-binary '{schema}' https://localhost:8983/solr/{solr_src}/schema --cacert solr-ssl.pem"
438467
INDEX_STRING = "curl -u {solr_user}:{solr_pwd} -X POST 'https://localhost:8983/solr/{solr_src}/update?commit=yes{params}' --data-binary @{file_name}.csv -H 'Content-type:application/csv' --cacert solr-ssl.pem"
439468
for field in schema:
@@ -463,7 +492,8 @@ def create_solr_params(schema_src, solr_src):
463492
with open("{}_solr_cmds.txt".format(solr_src.name), "w") as cmd_outfile:
464493
schema_array = SCHEMA_BASE.format(
465494
field_types=field_types,
466-
add_copy_field=add_copy_field
495+
add_copy_field=add_copy_field,
496+
fields=solr_schema
467497
)
468498
params = "&{}".format("&".join(solr_index_strings))
469499
cmd_outfile.write(CORE_CREATE_STRING.format(solr_src=solr_src.name))
@@ -780,7 +810,8 @@ def parse_args():
780810

781811
parser = ArgumentParser()
782812
parser.add_argument('-j', '--config-file', type=str, default='', help='JSON file of version data to update')
783-
parser.add_argument('-c', '--collex-file', type=str, default='', help='CSV data of collections to update/create')
813+
parser.add_argument('-c', '--collex-file', type=str, default='', help='CSV data of citations to update/create')
814+
parser.add_argument('-i', '--cites-file', type=str, default='', help='CSV data of collections to update/create')
784815
parser.add_argument('-d', '--display-vals', type=str, default='', help='CSV data of display values to add/update')
785816
parser.add_argument('-p', '--programs-file', type=str, default='', help='CSV data of programs to add/update')
786817
parser.add_argument('-a', '--attributes-file', type=str, default='', help='CSV data of attributes to add/update')
@@ -820,6 +851,8 @@ def main():
820851
len(args.programs_file) and load_programs(args.programs_file)
821852
# Add/update collections - any new programs must be added first
822853
len(args.collex_file) and load_collections(args.collex_file)
854+
# Add/update any citations
855+
len(args.cites_file) and load_citations(args.cites_file)
823856
# Add/update display values for attributes
824857
if len(args.display_vals):
825858
dvals = load_display_vals(args.display_vals)

idc/demo_views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def cohort_detail(request, cohort_id):
5454
if debug: logger.debug('Called {}'.format(sys._getframe().f_code.co_name))
5555

5656
try:
57-
req = request.GET if request.GET else request.POST
57+
req = request.GET if request.method == 'GET' else request.POST
5858
is_dicofdic = (req.get('is_dicofdic', "False").lower() == "true")
5959
source = req.get('data_source_type', DataSource.SOLR)
6060
fields = json.loads(req.get('fields', '[]'))

idc/urls.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@
3030
urlpatterns = [
3131

3232
re_path(r'^$', views.landing_page, name='landing_page'),
33-
re_path(r'^quota/', views.quota_page, name='quota_page'),
33+
re_path(r'^quota/$', views.quota_page, name='quota_page'),
3434
re_path(r'^users/(?P<user_id>\d+)/$', views.user_detail, name='user_detail'),
3535
re_path(r'^users/api/', views_api.user_detail, name='user_detail_api'),
3636

3737
re_path(r'^cohort_detail/(?P<cohort_id>\d+)/$', demo_views.cohort_detail, name='cohort_detail'),
38-
re_path(r'robots.txt', TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name='robots'),
39-
re_path(r'sitemap.xml', TemplateView.as_view(template_name="sitemap.xml", content_type="text/xml"), name='sitemap'),
38+
re_path(r'robots.txt$', TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), name='robots'),
39+
re_path(r'sitemap.xml$', TemplateView.as_view(template_name="sitemap.xml", content_type="text/xml"), name='sitemap'),
4040

4141
re_path(r'^cohorts/', include('cohorts.urls')),
4242
path('admin/', admin.site.urls),
@@ -52,19 +52,19 @@
5252
re_path(r'^tables/', views.populate_tables, name='populate_tables'),
5353
re_path(r'^studymp/', views.studymp, name='studymp'),
5454
re_path(r'^warning/', views.warn_page, name='warn'),
55-
re_path(r'^about/', views.about_page, name='about_page'),
55+
re_path(r'^about/$', views.about_page, name='about_page'),
5656
re_path(r'^dashboard/', views.dashboard_page, name='dashboard'),
5757
re_path(r'^extended_login/$', views.extended_login_view, name='extended_login'),
58-
re_path(r'^privacy/', views.privacy_policy, name='privacy'),
59-
re_path(r'^news/', views.news_page, name='news'),
58+
re_path(r'^privacy/$', views.privacy_policy, name='privacy'),
59+
re_path(r'^news/$', views.news_page, name='news'),
6060
re_path(r'^cart/$', views.cart_page, name='cart'),
6161
re_path(r'^explore/cart/$', views.cart_page, name='get_explore_cart'),
6262
re_path(r'^cart_data/$', views.cart_data, name='get_cart_data'),
63-
re_path(r'^cart_data_stats/$', views.cart_data_stats, name='get_cart_data_stats'),
6463
re_path(r'^series_ids/(?P<patient_id>[A-Za-z0-9\.\-_]+)/$', views.get_series, name='get_series_by_case'),
6564
re_path(r'^series_ids/(?P<patient_id>[A-Za-z0-9\.\-_]+)/(?P<study_uid>[0-9\.]+)/$', views.get_series, name='get_series'),
66-
re_path(r'^collaborators/', views.collaborators, name='collaborators'),
65+
re_path(r'^collaborators/$', views.collaborators, name='collaborators'),
6766
re_path(r'^collections/', include('idc_collections.urls')),
67+
re_path(r'^citations/', views.get_citations, name='get_citations'),
6868
# re_path(r'^share/', include('sharing.urls')),
6969
]
7070

idc/views.py

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@
3232
from django.urls import reverse
3333
from django.contrib import messages
3434
from django.utils.html import escape
35+
from django.utils.encoding import uri_to_iri
3536

3637
from google_helpers.stackdriver import StackDriverLogger
3738
from cohorts.models import Cohort, Cohort_Perms
3839

39-
from idc_collections.models import Program, DataSource, Collection, ImagingDataCommonsVersion, Attribute, Attribute_Tooltips, DataSetType
40+
from idc_collections.models import Program, DataSource, Collection, ImagingDataCommonsVersion, Attribute, Attribute_Tooltips, DataSetType, Citation
4041
from idc_collections.collex_metadata_utils import build_explorer_context, get_collex_metadata, create_file_manifest, get_cart_data_serieslvl, get_cart_data_studylvl, get_table_data_with_cart_data
4142
from allauth.socialaccount.models import SocialAccount
4243
from django.core.exceptions import ObjectDoesNotExist
@@ -76,7 +77,8 @@ def landing_page(request):
7677
"Testicles": "Testis",
7778
"Adrenal Glands": "Adrenal Gland",
7879
"Adrenal": "Adrenal Gland",
79-
"Lymph Node": "Lymph Nodes"
80+
"Lymph Node": "Lymph Nodes",
81+
"Bone Marrow": "Blood"
8082
}
8183

8284
skip = [
@@ -91,7 +93,9 @@ def landing_page(request):
9193
"Chest-Abdomen-Pelvis, Leg, TSpine",
9294
"Abdomen, Arm, Bladder, Chest, Head-Neck, Kidney, Leg, Retroperitoneum, Stomach, Uterus",
9395
"Blood, Bone",
94-
"Esophagus, Lung, Pancreas, Thymus"
96+
"Esophagus, Lung, Pancreas, Thymus",
97+
"Esophagus, Head-Neck, Lung, Pancreas, Rectum, Thymus",
98+
"Arm, Bladder, Buttock, Colon, Liver, Myometrium, Pancreas, Rectum, Shoulder, Scapula"
9599
]
96100

97101
for collection in collex:
@@ -363,6 +367,27 @@ def populate_tables(request):
363367
return JsonResponse(response, status=status)
364368

365369

370+
def get_citations(request):
371+
resp = { 'message': 'error', 'citations': None}
372+
try:
373+
req = request.GET if request.method == 'GET' else request.POST
374+
if request.method == 'GET':
375+
dois = [uri_to_iri(x) for x in req.getlist("doi", [])]
376+
else:
377+
body_unicode = request.body.decode('utf-8')
378+
body = json.loads(body_unicode)
379+
dois = body.get("doi", [])
380+
cites = Citation.objects.filter(doi__in=dois)
381+
resp['citations'] = {x.doi: x.cite for x in cites}
382+
code = 200
383+
except Exception as e:
384+
logger.error("[ERROR] While fetching citations: ")
385+
logger.exception(e)
386+
resp['message'] = "There was an error while attempting to fetch these citations."
387+
code = 500
388+
return JsonResponse(resp, status=code)
389+
390+
366391
# Data exploration and cohort creation page
367392
def explore_data_page(request, filter_path=False, path_filters=None):
368393
context = {'request': request}
@@ -374,8 +399,7 @@ def explore_data_page(request, filter_path=False, path_filters=None):
374399
request.session.create()
375400

376401
try:
377-
req = request.GET or request.POST
378-
402+
req = request.GET if request.method == 'GET' else request.POST
379403
is_dicofdic = (req.get('is_dicofdic', "False").lower() == "true")
380404
source = req.get('data_source_type', DataSource.SOLR)
381405
versions = json.loads(req.get('versions', '[]'))
@@ -425,6 +449,7 @@ def explore_data_page(request, filter_path=False, path_filters=None):
425449
collapse_on, is_json, uniques=uniques, totals=totals, with_stats=with_stats, disk_size=disk_size
426450
)
427451

452+
print(context.keys())
428453
if not('totals' in context):
429454
context['totals']={}
430455
if not('PatientID' in context['totals']):
@@ -611,39 +636,10 @@ def cart_page(request):
611636
return render(request, 'collections/cart_list.html', context)
612637

613638

614-
def cart_data_stats(request):
615-
status = 200
616-
response = {}
617-
field_list = ['collection_id', 'PatientID', 'StudyInstanceUID', 'SeriesInstanceUID', 'aws_bucket']
618-
try:
619-
620-
req = request.GET if request.GET else request.POST
621-
current_filters = json.loads(req.get('filters', '{}'))
622-
filtergrp_list = json.loads(req.get('filtergrp_list', '{}'))
623-
aggregate_level = req.get('aggregate_level', 'StudyInstanceUID')
624-
results_level = req.get('results_level', 'StudyInstanceUID')
625-
626-
partitions = json.loads(req.get('partitions', '{}'))
627-
628-
limit = int(req.get('limit', 1000))
629-
offset = int(req.get('offset', 0))
630-
length = int(req.get('length', 100))
631-
mxseries = int(req.get('mxseries',1000))
632-
633-
response = get_cart_and_filterset_stats(current_filters,filtergrp_list, partitions, limit, offset, length, mxseries, results_lvl=results_level)
634-
635-
except Exception as e:
636-
logger.error("[ERROR] While loading cart:")
637-
logger.exception(e)
638-
status = 400
639-
640-
return JsonResponse(response, status=status)
641-
642-
643639
def cart_data(request):
644640
status = 200
645641
response = {}
646-
field_list = ['collection_id', 'PatientID', 'StudyInstanceUID', 'SeriesInstanceUID', 'aws_bucket']
642+
field_list = ['collection_id', 'PatientID', 'StudyInstanceUID', 'SeriesInstanceUID', 'aws_bucket', "source_DOI"]
647643
try:
648644
req = request.GET if request.GET else request.POST
649645
filtergrp_list = json.loads(req.get('filtergrp_list', '{}'))

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cryptography==44.0.2
2-
django==4.2.20
2+
django==4.2.24
33
django-allauth==0.63.1
44
django-anymail[mailgun]
55
django-axes==5.40.0
@@ -23,7 +23,7 @@ pyjwt==2.4.0
2323
pyopenssl==25.0.0
2424
pytz==2025.2
2525
PyYAML==6.0.2
26-
requests==2.32.3
26+
requests==2.32.4
2727
requests-oauthlib==0.7.0
2828
rsa==4.7
2929
simplejson==3.20.1

shell/vagrant-set-env.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ echo 'export SECURE_LOCAL_PATH=../parentDir/secure_files/idc/' | tee -a /home/va
44
echo 'export DJANGO_SETTINGS_MODULE=idc.settings' | tee -a /home/vagrant/.bash_profile
55
source /home/vagrant/.bash_profile
66
chmod +x /home/vagrant/www/shell/python-su.sh
7+
if [[ ! -d /vagrant ]]; then
8+
sudo ln -s /home/vagrant/www /vagrant
9+
fi

static/css/style.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2555,6 +2555,7 @@ pre.bq-string {
25552555
pre.citations-list {
25562556
white-space: pre-wrap;
25572557
word-break: break-word;
2558+
max-height: 600px;
25582559
}
25592560

25602561
.bq-string-display img {

static/js/citations_modal.js

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ require([
6666

6767
A11y.Core();
6868

69-
var downloadToken = new Date().getTime();
69+
let csrftoken = $.getCookie('csrftoken')
7070

7171
$('#citations-modal').on('show.bs.modal', async function(event) {
7272
let button = $(event.relatedTarget);
@@ -76,36 +76,40 @@ require([
7676
cites_list.html(`
7777
Formatting citation(s)... <i class="fa fa-compass fa-spin"></i>
7878
`);
79-
let citations = [];
80-
await Promise.all(dois.map(async function(cite){
81-
if(!DOI_CACHE[cite]) {
82-
let response = await fetch(`https://doi.org/${cite}`, {
83-
headers: {
84-
"Accept": "text/x-bibliography; style=elsevier-vancouver-no-et-al"
85-
}
79+
let dois_to_get = dois.filter((d) => (DOI_CACHE[d] === null || DOI_CACHE[d] === undefined));
80+
if(dois_to_get.length > 0) {
81+
let resp = null;
82+
if(dois_to_get.join(",").length > 2048) {
83+
resp = await fetch(`${BASE_URL}/citations/`, {
84+
method: "POST",
85+
body: JSON.stringify({
86+
'doi': dois_to_get
87+
}),
88+
headers: {"X-CSRFToken": csrftoken},
89+
"content-type": "application/json"
8690
});
87-
if (!response.ok) {
88-
citations.push(`Encountered an error requesting DOI ${cite}`);
89-
} else {
90-
DOI_CACHE[cite] = await response.text();
91-
}
91+
} else {
92+
let encoded_dois = dois_to_get.map(d => `doi=${encodeURIComponent(d)}`);
93+
resp = await fetch(`${BASE_URL}/citations/?${encoded_dois.join("&")}`);
9294
}
93-
citations.push(DOI_CACHE[cite]);
94-
}));
95+
if(!resp.ok) {
96+
cites_list.html("Failed to retrieve citations!");
97+
throw new Error("Failed to retrieve citations!");
98+
}
99+
let new_cites = await resp.json();
100+
DOI_CACHE = {
101+
...new_cites['citations'],
102+
...DOI_CACHE
103+
};
104+
}
105+
let citations = dois.map(d => DOI_CACHE[d]);
95106
cites_list.html(citations.join("\n\n"));
96107
copy_cites.attr('content', citations.join("\n\n"));
97108
});
98109

99110
$('#citations-modal').on('hide.bs.modal', function() {
100-
101-
});
102-
103-
$('#export-manifest-modal').on('hidden.bs.modal', function() {
104111
$(".citations-list").empty();
105-
});
106-
107-
$('.copy-this').on('click', function(e) {
108-
// copy the entire set
112+
$("#citations-modal .copy-this").attr('content', "");
109113
});
110114

111115
tippy.delegate('#citations-modal', {

0 commit comments

Comments
 (0)