From e09f213d6c9c30ebb4740a6eaea0b307865cd336 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Fri, 6 Mar 2009 02:09:26 +0000 Subject: [PATCH] Make the public library browsing page support several features from django admin pages. * Search bar * Pagination * Filters This took grabbing the "ChangeSet" class from django.contrib.admin and slightly modifying, in addition to the css files. To provide the css files I had to add the ability to serve static files from the app level. I followed the django pattern that the internal static pages would only be served by django when debug is true. And because it's hard to split it out, I also added a new field 'hidden' to the library table. This allows hiding libraries from the public library page (which is useful since a number of libraries have a gel isolate pair, which many end users find rather confusing). --- docs/conv_caltech_v0.1_to_htsw.py | 16 +- htsworkflow/frontend/samples/admin.py | 4 +- htsworkflow/frontend/samples/changelist.py | 239 ++++++++++++++++++ htsworkflow/frontend/samples/models.py | 2 + htsworkflow/frontend/samples/views.py | 30 ++- htsworkflow/frontend/settings.py | 9 +- htsworkflow/frontend/static/css/base.css | 14 + .../frontend/static/css/changelists.css | 50 ++++ .../frontend/static/css/click-table.css | 19 ++ htsworkflow/frontend/static/css/global.css | 142 +++++++++++ htsworkflow/frontend/static/css/layout.css | 29 +++ .../frontend/static/img/icon_searchbox.png | Bin 0 -> 667 bytes .../templates/samples/library_index.html | 45 ++-- htsworkflow/frontend/urls.py | 8 + 14 files changed, 566 insertions(+), 41 deletions(-) create mode 100644 htsworkflow/frontend/samples/changelist.py create mode 100644 htsworkflow/frontend/static/css/base.css create mode 100644 htsworkflow/frontend/static/css/changelists.css create mode 100644 htsworkflow/frontend/static/css/click-table.css create mode 100644 htsworkflow/frontend/static/css/global.css create mode 100644 htsworkflow/frontend/static/css/layout.css create mode 100644 htsworkflow/frontend/static/img/icon_searchbox.png diff --git a/docs/conv_caltech_v0.1_to_htsw.py b/docs/conv_caltech_v0.1_to_htsw.py index 2adb036..ed80943 100644 --- a/docs/conv_caltech_v0.1_to_htsw.py +++ b/docs/conv_caltech_v0.1_to_htsw.py @@ -116,6 +116,7 @@ def main(cmdline=None): "library_id" varchar(30) NOT NULL, "library_name" varchar(100) NOT NULL UNIQUE, "library_species_id" integer NOT NULL REFERENCES "samples_species" ("id"), + "hidden" bool NOT NULL, "cell_line_id" integer NOT NULL REFERENCES "samples_cellline" ("id"), "condition_id" integer NOT NULL REFERENCES "samples_condition" ("id"), "antibody_id" integer NULL REFERENCES "samples_antibody" ("id"), @@ -132,27 +133,36 @@ def main(cmdline=None): "avg_lib_size" integer NULL, "notes" text NOT NULL);''') c.execute('''INSERT INTO samples_library - (id,library_id,library_name,library_species_id, experiment_type_id, + (id,library_id,library_name,library_species_id, hidden, experiment_type_id, cell_line_id,condition_id,replicate,made_by,creation_date, made_for,stopping_point,amplified_from_sample_id, undiluted_concentration,ten_nM_dilution,successful_pM, avg_lib_size,notes) -select library_id,library_id,library_name,library_species_id, 1, +select library_id,library_id,library_name,library_species_id, 0, 1, 1, 1, 1, made_by,creation_date, made_for,stopping_point,amplified_from_sample_id, undiluted_concentration,ten_nM_dilution,successful_pM, 0,notes from fctracker_library;'''); + + # mark gel isolates as "hidden" + c.execute('''update samples_library set hidden=1 + where stopping_point = "1A" or stopping_point = "1Ab";'''); + + # get pk for RNA-seq experiment type c.execute('select id from samples_experimenttype where name = "RNA-seq";') rna_seq_id = list(c)[0] + # change everything marked as rnaseq to experiment_type rnaseq c.execute('''update samples_library set experiment_type_id=? where library_id in (select library_id from fctracker_library where RNASeq = 1);''', rna_seq_id) #c.execute('''drop table fctracker_library;''') - # add many to many tables + + # add affiliation linking table c.execute('''CREATE TABLE "samples_library_affiliations" ( "id" integer NOT NULL PRIMARY KEY, "library_id" integer NOT NULL REFERENCES "samples_library" ("id"), "affiliation_id" integer NOT NULL REFERENCES "samples_affiliation" ("id"), UNIQUE ("library_id", "affiliation_id"));''') + # add library to tags linking table c.execute('''CREATE TABLE "samples_library_tags" ( "id" integer NOT NULL PRIMARY KEY, "library_id" integer NOT NULL REFERENCES "samples_library" ("id"), diff --git a/htsworkflow/frontend/samples/admin.py b/htsworkflow/frontend/samples/admin.py index 5967e32..ffdb5d8 100644 --- a/htsworkflow/frontend/samples/admin.py +++ b/htsworkflow/frontend/samples/admin.py @@ -65,9 +65,9 @@ class LibraryOptions(admin.ModelAdmin): fieldsets = ( (None, { 'fields': ( - ('replicate','library_id','library_name'), + ('library_id','library_name','hidden'), ('library_species'), - ('experiment_type'), + ('experiment_type', 'replicate'), ('cell_line','condition','antibody'),) }), ('Creation Information:', { diff --git a/htsworkflow/frontend/samples/changelist.py b/htsworkflow/frontend/samples/changelist.py new file mode 100644 index 0000000..dccba27 --- /dev/null +++ b/htsworkflow/frontend/samples/changelist.py @@ -0,0 +1,239 @@ +""" +Slightly modified version of the django admin component that handles filters and searches +""" +from django.contrib.admin.filterspecs import FilterSpec +from django.contrib.admin.options import IncorrectLookupParameters +from django.core.paginator import Paginator, InvalidPage, EmptyPage +from django.db import models +from django.db.models.query import QuerySet +from django.utils.encoding import smart_str +from django.utils.http import urlencode + +import operator + +MAX_SHOW_ALL_ALLOWED = 20000 + +#change list settings +ALL_VAR = 'all' +ORDER_VAR = 'o' +ORDER_TYPE_VAR = 'ot' +PAGE_VAR = 'p' +SEARCH_VAR = 'q' +IS_POPUP_VAR = 'pop' +ERROR_FLAG = 'e' + +class ChangeList(object): + def __init__(self, request, model, list_filter, search_fields, list_per_page, queryset=None): + self.model = model + self.opts = model._meta + self.lookup_opts = self.opts + if queryset is None: + self.root_query_set = model.objects + else: + self.root_query_set = queryset + self.list_display = [] + self.list_display_links = None + self.list_filter = list_filter + + self.search_fields = search_fields + self.list_select_related = None + self.list_per_page = list_per_page + self.model_admin = None + + try: + self.page_num = int(request.GET.get(PAGE_VAR,'0')) + except ValueError: + self.page_num = 0 + self.show_all = 'all' in request.GET + self.params = dict(request.GET.items()) + if PAGE_VAR in self.params: + del self.params[PAGE_VAR] + if ERROR_FLAG in self.params: + del self.params[ERROR_FLAG] + + self.multi_page = True + self.can_show_all = False + + self.order_field, self.order_type = self.get_ordering() + self.query = request.GET.get(SEARCH_VAR, '') + self.query_set = self.get_query_set() + self.get_results(request) + self.filter_specs, self.has_filters = self.get_filters(request) + + #self.result_count = 'result count' + #self.full_result_count = 'full result count' + + def get_filters(self, request): + filter_specs = [] + if self.list_filter: + filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter] + for f in filter_fields: + spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin) + if spec and spec.has_output(): + filter_specs.append(spec) + return filter_specs, bool(filter_specs) + + def get_query_string(self, new_params=None, remove=None): + if new_params is None: new_params = {} + if remove is None: remove = [] + p = self.params.copy() + for r in remove: + for k in p.keys(): + if k.startswith(r): + del p[k] + for k, v in new_params.items(): + if v is None: + if k in p: + del p[k] + else: + p[k] = v + return '?%s' % urlencode(p) + + def get_results(self, request): + paginator = Paginator(self.query_set, self.list_per_page) + # Get the number of objects, with admin filters applied. + result_count = paginator.count + + # Get the total number of objects, with no admin filters applied. + # Perform a slight optimization: Check to see whether any filters were + # given. If not, use paginator.hits to calculate the number of objects, + # because we've already done paginator.hits and the value is cached. + if not self.query_set.query.where: + full_result_count = result_count + else: + full_result_count = self.root_query_set.count() + + can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED + multi_page = result_count > self.list_per_page + + # Get the list of objects to display on this page. + if (self.show_all and can_show_all) or not multi_page: + result_list = list(self.query_set) + else: + try: + result_list = paginator.page(self.page_num+1).object_list + except InvalidPage: + result_list = () + + self.result_count = result_count + self.full_result_count = full_result_count + self.result_list = result_list + self.can_show_all = can_show_all + self.multi_page = multi_page + self.paginator = paginator + + def get_ordering(self): + lookup_opts, params = self.lookup_opts, self.params + # For ordering, first check the "ordering" parameter in the admin + # options, then check the object's default ordering. If neither of + # those exist, order descending by ID by default. Finally, look for + # manually-specified ordering from the query string. + ordering = lookup_opts.ordering or ['-' + lookup_opts.pk.name] + + if ordering[0].startswith('-'): + order_field, order_type = ordering[0][1:], 'desc' + else: + order_field, order_type = ordering[0], 'asc' + if ORDER_VAR in params: + try: + field_name = self.list_display[int(params[ORDER_VAR])] + try: + f = lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + # See whether field_name is a name of a non-field + # that allows sorting. + try: + if callable(field_name): + attr = field_name + elif hasattr(self.model_admin, field_name): + attr = getattr(self.model_admin, field_name) + else: + attr = getattr(self.model, field_name) + order_field = attr.admin_order_field + except AttributeError: + pass + else: + order_field = f.name + except (IndexError, ValueError): + pass # Invalid ordering specified. Just use the default. + if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'): + order_type = params[ORDER_TYPE_VAR] + return order_field, order_type + + def get_query_set(self): + qs = self.root_query_set + lookup_params = self.params.copy() # a dictionary of the query string + for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): + if i in lookup_params: + del lookup_params[i] + for key, value in lookup_params.items(): + if not isinstance(key, str): + # 'key' will be used as a keyword argument later, so Python + # requires it to be a string. + del lookup_params[key] + lookup_params[smart_str(key)] = value + + # if key ends with __in, split parameter into separate values + if key.endswith('__in'): + lookup_params[key] = value.split(',') + + # Apply lookup parameters from the query string. + try: + qs = qs.filter(**lookup_params) + # Naked except! Because we don't have any other way of validating "params". + # They might be invalid if the keyword arguments are incorrect, or if the + # values are not in the correct type, so we might get FieldError, ValueError, + # ValicationError, or ? from a custom field that raises yet something else + # when handed impossible data. + except Exception, e: + print e + raise IncorrectLookupParameters + + # Use select_related() if one of the list_display options is a field + # with a relationship. + if self.list_select_related: + qs = qs.select_related() + else: + for field_name in self.list_display: + try: + f = self.lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + pass + else: + if isinstance(f.rel, models.ManyToOneRel): + qs = qs.select_related() + break + + # Set ordering. + if self.order_field: + qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field)) + + # Apply keyword searches. + def construct_search(field_name): + if field_name.startswith('^'): + return "%s__istartswith" % field_name[1:] + elif field_name.startswith('='): + return "%s__iexact" % field_name[1:] + elif field_name.startswith('@'): + return "%s__search" % field_name[1:] + else: + return "%s__icontains" % field_name + + if self.search_fields and self.query: + for bit in self.query.split(): + or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.search_fields] + other_qs = QuerySet(self.model) + other_qs.dup_select_related(qs) + other_qs = other_qs.filter(reduce(operator.or_, or_queries)) + qs = qs & other_qs + for field_name in self.search_fields: + if '__' in field_name: + qs = qs.distinct() + break + + if self.opts.one_to_one_field: + qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to) + + return qs + + diff --git a/htsworkflow/frontend/samples/models.py b/htsworkflow/frontend/samples/models.py index a3c27ab..95f6376 100644 --- a/htsworkflow/frontend/samples/models.py +++ b/htsworkflow/frontend/samples/models.py @@ -118,6 +118,8 @@ class Library(models.Model): library_id = models.CharField(max_length=30, db_index=True) library_name = models.CharField(max_length=100, unique=True) library_species = models.ForeignKey(Species) + # new field 2008 Mar 5, alter table samples_library add column "hidden" NOT NULL default 0; + hidden = models.BooleanField() cell_line = models.ForeignKey(Cellline) condition = models.ForeignKey(Condition) antibody = models.ForeignKey(Antibody,blank=True,null=True) diff --git a/htsworkflow/frontend/samples/views.py b/htsworkflow/frontend/samples/views.py index f36a0c3..a6a996c 100644 --- a/htsworkflow/frontend/samples/views.py +++ b/htsworkflow/frontend/samples/views.py @@ -1,4 +1,5 @@ # Create your views here. +from htsworkflow.frontend.samples.changelist import ChangeList from htsworkflow.frontend.samples.models import Library from htsworkflow.frontend.samples.results import get_flowcell_result_dict, parse_flowcell_id from htsworkflow.pipelines.runfolder import load_pipeline_run_xml @@ -8,8 +9,8 @@ from htsworkflow.util import makebed from htsworkflow.util import opener from django.http import HttpResponse +from django.template import RequestContext from django.template.loader import get_template -from django.template import Context import StringIO import logging @@ -17,12 +18,13 @@ import os LANE_LIST = [1,2,3,4,5,6,7,8] -def create_library_list(): +def create_library_context(cl): """ Create a list of libraries that includes how many lanes were run """ - library_list = [] - for lib in Library.objects.order_by('-library_id'): + records = [] + #for lib in library_items.object_list: + for lib in cl.result_list: summary = {} summary['library_id'] = lib.library_id summary['library_name'] = lib.library_name @@ -32,13 +34,23 @@ def create_library_list(): lane = getattr(lib, 'lane_%d_library' % (lane_id,)) lanes_run += len( lane.all() ) summary['lanes_run'] = lanes_run - library_list.append(summary) - return library_list + records.append(summary) + cl.result_count = unicode(cl.paginator._count) + u" libraries" + return {'library_list': records } def library(request): - library_list = create_library_list() + # build changelist + fcl = ChangeList(request, Library, + list_filter=['library_species','affiliations'], + search_fields=['library_id', 'library_name'], + list_per_page=25, + queryset=Library.objects.filter(hidden__exact=0) + ) + + context = { 'cl': fcl} + context.update(create_library_context(fcl)) t = get_template('samples/library_index.html') - c = Context({'library_list': library_list }) + c = RequestContext(request, context) return HttpResponse( t.render(c) ) def library_to_flowcells(request, lib_id): @@ -100,7 +112,7 @@ def library_to_flowcells(request, lib_id): output.append(err) output.append('
') - output.append(t.render(Context({'lane_summary_list': lane_summary_list}))) + output.append(t.render(RequestContext(request, {'lane_summary_list': lane_summary_list}))) output.append('
') if record_count == 0: diff --git a/htsworkflow/frontend/settings.py b/htsworkflow/frontend/settings.py index a988694..01ec35a 100644 --- a/htsworkflow/frontend/settings.py +++ b/htsworkflow/frontend/settings.py @@ -102,11 +102,11 @@ USE_I18N = True # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = '' +MEDIA_ROOT = os.path.abspath(os.path.split(__file__)[0]) + '/static/' # URL that handles the media served from MEDIA_ROOT. # Example: "http://media.lawrence.com" -MEDIA_URL = '' +MEDIA_URL = '/static/' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. @@ -157,12 +157,13 @@ INSTALLED_APPS = ( # Project specific settings -ALLOWED_IPS={'localhost': '127.0.0.1'} +ALLOWED_IPS={'127.0.0.1': '127.0.0.1'} options_to_dict(ALLOWED_IPS, 'allowed_hosts') -ALLOWED_ANALYS_IPS = {'localhost': '127.0.0.1'} +ALLOWED_ANALYS_IPS = {'127.0.0.1': '127.0.0.1'} options_to_dict(ALLOWED_ANALYS_IPS, 'allowed_analysis_hosts') #UPLOADTO_HOME = os.path.abspath('../../uploads') #UPLOADTO_CONFIG_FILE = os.path.join(UPLOADTO_HOME, 'eland_config') #UPLOADTO_ELAND_RESULT_PACKS = os.path.join(UPLOADTO_HOME, 'eland_results') #UPLOADTO_BED_PACKS = os.path.join(UPLOADTO_HOME, 'bed_packs') +RESULT_HOME_DIR='/Users/diane/proj/solexa/results/flowcells' diff --git a/htsworkflow/frontend/static/css/base.css b/htsworkflow/frontend/static/css/base.css new file mode 100644 index 0000000..9760d67 --- /dev/null +++ b/htsworkflow/frontend/static/css/base.css @@ -0,0 +1,14 @@ +/* + DJANGO Admin + by Wilson Miner wilson@lawrence.com +*/ + +/* Block IE 5 */ +@import "null.css?\"\{"; + +/* Import other styles */ +@import url('global.css'); +@import url('layout.css'); + +/* Import patch for IE 6 Windows */ +/*\*/ @import "patch-iewin.css"; /**/ diff --git a/htsworkflow/frontend/static/css/changelists.css b/htsworkflow/frontend/static/css/changelists.css new file mode 100644 index 0000000..4834be4 --- /dev/null +++ b/htsworkflow/frontend/static/css/changelists.css @@ -0,0 +1,50 @@ +@import url('base.css'); + +/* CHANGELISTS */ +#changelist { position:relative; width:100%; } +#changelist table { width:100%; } +.change-list .filtered table { border-right:1px solid #ddd; } +.change-list .filtered { min-height:400px; } +.change-list .filtered { background:white url(../img/admin/changelist-bg.gif) top right repeat-y !important; } +.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:160px !important; width:auto !important; } +.change-list .filtered table tbody th { padding-right:1em; } +#changelist .toplinks { border-bottom:1px solid #ccc !important; } +#changelist .paginator { color:#666; border-top:1px solid #eee; border-bottom:1px solid #eee; background:white url(../img/admin/nav-bg.gif) 0 180% repeat-x; overflow:hidden; } +.change-list .filtered .paginator { border-right:1px solid #ddd; } + +/* CHANGELIST TABLES */ +#changelist table thead th { white-space:nowrap; } +#changelist table tbody td { border-left: 1px solid #ddd; } +#changelist table tfoot { color: #666; } + +/* TOOLBAR */ +#changelist #toolbar { padding:3px; border-bottom:1px solid #ddd; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; color:#666; } +#changelist #toolbar form input { font-size:11px; padding:1px 2px; } +#changelist #toolbar form #searchbar { padding:2px; } +#changelist #changelist-search img { vertical-align:middle; } + +/* FILTER COLUMN */ +#changelist-filter { position:absolute; top:0; right:0; z-index:1000; width:160px; border-left:1px solid #ddd; background:#efefef; margin:0; } +#changelist-filter h2 { font-size:11px; padding:2px 5px; border-bottom:1px solid #ddd; } +#changelist-filter h3 { font-size:12px; margin-bottom:0; } +#changelist-filter ul { padding-left:0;margin-left:10px; } +#changelist-filter li { list-style-type:none; margin-left:0; padding-left:0; } +#changelist-filter a { color:#999; } +#changelist-filter a:hover { color:#036; } +#changelist-filter li.selected { border-left:5px solid #ccc; padding-left:5px;margin-left:-10px; } +#changelist-filter li.selected a { color:#5b80b2 !important; } + +/* DATE DRILLDOWN */ +.change-list ul.toplinks { display:block; background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; border-top:1px solid white; float:left; padding:0 !important; margin:0 !important; width:100%; } +.change-list ul.toplinks li { float: left; width: 9em; padding:3px 6px; font-weight: bold; list-style-type:none; } +.change-list ul.toplinks .date-back a { color:#999; } +.change-list ul.toplinks .date-back a:hover { color:#036; } + +/* PAGINATOR */ +.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; } +.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; } +.paginator a.showall { padding:0 !important; border:none !important; } +.paginator a.showall:hover { color:#036 !important; background:transparent !important; } +.paginator .end { border-width:2px !important; margin-right:6px; } +.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; } +.paginator a:hover { color:white; background:#5b80b2; border-color:#036; } diff --git a/htsworkflow/frontend/static/css/click-table.css b/htsworkflow/frontend/static/css/click-table.css new file mode 100644 index 0000000..1d48412 --- /dev/null +++ b/htsworkflow/frontend/static/css/click-table.css @@ -0,0 +1,19 @@ +table, td { + border-style: solid; +} +table { + border-width: 0 0 1px 1px; + border-spacing: 0; + border-collapse: collapse; +} +thead { + text-align: center; +} +td { + margin: 0; + padding: 4px; + border-width: 1px 1px 0 0; +} +td a { + display: block; +} diff --git a/htsworkflow/frontend/static/css/global.css b/htsworkflow/frontend/static/css/global.css new file mode 100644 index 0000000..7b1a013 --- /dev/null +++ b/htsworkflow/frontend/static/css/global.css @@ -0,0 +1,142 @@ +body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; } + +/* LINKS */ +a:link, a:visited { color: #5b80b2; text-decoration:none; } +a:hover { color: #036; } +a img { border:none; } +a.section:link, a.section:visited { color: white; text-decoration:none; } + +/* GLOBAL DEFAULTS */ +p, ol, ul, dl { margin:.2em 0 .8em 0; } +p { padding:0; line-height:140%; } + +h1,h2,h3,h4,h5 { font-weight:bold; } +h1 { font-size:18px; color:#666; padding:0 6px 0 0; margin:0 0 .2em 0; } +h2 { font-size:16px; margin:1em 0 .5em 0; } +h2.subhead { font-weight:normal;margin-top:0; } +h3 { font-size:14px; margin:.8em 0 .3em 0; color:#666; font-weight:bold; } +h4 { font-size:12px; margin:1em 0 .8em 0; padding-bottom:3px; } +h5 { font-size:10px; margin:1.5em 0 .5em 0; color:#666; text-transform:uppercase; letter-spacing:1px; } + +ul li { list-style-type:square; padding:1px 0; } +ul.plainlist { margin-left:0 !important; } +ul.plainlist li { list-style-type:none; } +li ul { margin-bottom:0; } +li, dt, dd { font-size:11px; line-height:14px; } +dt { font-weight:bold; margin-top:4px; } +dd { margin-left:0; } + +form { margin:0; padding:0; } +fieldset { margin:0; padding:0; } + +blockquote { font-size:11px; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; } +code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:11px; } +pre.literal-block { margin:10px; background:#eee; padding:6px 8px; } +code strong { color:#930; } +hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; } + +/* TEXT STYLES & MODIFIERS */ +.small { font-size:11px; } +.tiny { font-size:10px; } +p.tiny { margin-top:-2px; } +.mini { font-size:9px; } +p.mini { margin-top:-3px; } +.help, p.help { font-size:10px !important; color:#999; } +p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; } +.quiet, a.quiet:link, a.quiet:visited { color:#999 !important;font-weight:normal !important; } +.quiet strong { font-weight:bold !important; } +.float-right { float:right; } +.float-left { float:left; } +.clear { clear:both; } +.align-left { text-align:left; } +.align-right { text-align:right; } +.example { margin:10px 0; padding:5px 10px; background:#efefef; } +.nowrap { white-space:nowrap; } + +/* TABLES */ +table { border-collapse:collapse; border-color:#ccc; } +td, th { font-size:11px; line-height:13px; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; } +th { text-align:left; font-size:12px; font-weight:bold; } +thead th, +tfoot td { color:#666; padding:2px 5px; font-size:11px; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; border-left:1px solid #ddd; border-bottom:1px solid #ddd; } +tfoot td { border-bottom:none; border-top:1px solid #ddd; } +thead th:first-child, +tfoot td:first-child { border-left:none !important; } +thead th.optional { font-weight:normal !important; } +fieldset table { border-right:1px solid #eee; } +tr.row-label td { font-size:9px; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; } +tr.alt { background:#f6f6f6; } +.row1 { background:#EDF3FE; } +.row2 { background:white; } + +/* SORTABLE TABLES */ +thead th a:link, thead th a:visited { color:#666; display:block; } +table thead th.sorted { background-position:bottom left !important; } +table thead th.sorted a { padding-right:13px; } +table thead th.ascending a { background:url(../img/admin/arrow-down.gif) right .4em no-repeat; } +table thead th.descending a { background:url(../img/admin/arrow-up.gif) right .4em no-repeat; } + +/* ORDERABLE TABLES */ +table.orderable tbody tr td:hover { cursor:move; } +table.orderable tbody tr td:first-child { padding-left:14px; background-image:url(../img/admin/nav-bg-grabber.gif); background-repeat:repeat-y; } +table.orderable-initalized .order-cell, body>tr>td.order-cell { display:none; } + +/* FORM DEFAULTS */ +input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; } +textarea { vertical-align:top !important; } +input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; } + +/* FORM BUTTONS */ +.button, input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; border:1px solid #bbb; border-color:#ddd #aaa #aaa #ddd; } +.button:active, input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; } +.button.default, input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; float:right; } +.button.default:active, input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; } + +/* MODULES */ +.module { border:1px solid #ccc; margin-bottom:5px; background:white; } +.module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; } +.module blockquote { margin-left:12px; } +.module ul, .module ol { margin-left:1.5em; } +.module h3 { margin-top:.6em; } +.module h2, .module caption, .inline-group h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x; color:white; } +.module table { border-collapse: collapse; } + +/* MESSAGES & ERRORS */ +ul.messagelist { padding:0 0 5px 0; margin:0; } +ul.messagelist li { font-size:12px; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; } +.errornote { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; } +ul.errorlist { margin:0 !important; padding:0 !important; } +.errorlist li { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:white; background:red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; } +td ul.errorlist { margin:0 !important; padding:0 !important; } +td ul.errorlist li { margin:0 !important; } +.errors { background:#ffc; } +.errors input, .errors select { border:1px solid red; } +div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; } +div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; } +.description { font-size:12px; padding:5px 0 0 12px; } + +/* BREADCRUMBS */ +div.breadcrumbs { background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; padding:2px 8px 3px 8px; font-size:11px; color:#999; border-top:1px solid white; border-bottom:1px solid #ccc; text-align:left; } + +/* ACTION ICONS */ +.addlink { padding-left:12px; background:url(../img/admin/icon_addlink.gif) 0 .2em no-repeat; } +.changelink { padding-left:12px; background:url(../img/admin/icon_changelink.gif) 0 .2em no-repeat; } +.deletelink { padding-left:12px; background:url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat; } +a.deletelink:link, a.deletelink:visited { color:#CC3434; } +a.deletelink:hover { color:#993333; } + +/* OBJECT TOOLS */ +.object-tools { font-size:10px; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; } +.form-row .object-tools { margin-top:5px; margin-bottom:5px; float:none; height:2em; padding-left:3.5em; } +.object-tools li { display:block; float:left; background:url(../img/admin/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; } +.object-tools li:hover { background:url(../img/admin/tool-left_over.gif) 0 0 no-repeat; } +.object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/admin/tool-right.gif) 100% 0 no-repeat; } +.object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat; } +.object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; } +.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; } +.object-tools a.addlink { background:#999 url(../img/admin/tooltag-add.gif) top right no-repeat; padding-right:28px; } +.object-tools a.addlink:hover { background:#5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat; } + +/* OBJECT HISTORY */ +table#change-history { width:100%; } +table#change-history tbody th { width:16em; } diff --git a/htsworkflow/frontend/static/css/layout.css b/htsworkflow/frontend/static/css/layout.css new file mode 100644 index 0000000..17c5286 --- /dev/null +++ b/htsworkflow/frontend/static/css/layout.css @@ -0,0 +1,29 @@ +/* PAGE STRUCTURE */ +#container { position:relative; width:100%; min-width:760px; padding:0; } +#content { margin:10px 15px; } +#header { width:100%; } +#content-main { float:left; width:100%; } +#content-related { float:right; width:18em; position:relative; margin-right:-19em; } +#footer { clear:both; padding:10px; } + +/* COLUMN TYPES */ +.colMS { margin-right:20em !important; } +.colSM { margin-left:20em !important; } +.colSM #content-related { float:left; margin-right:0; margin-left:-19em; } +.colSM #content-main { float:right; } +.popup .colM { width:95%; } +.subcol { float:left; width:46%; margin-right:15px; } +.dashboard #content { width:500px; } + +/* HEADER */ +#header { background:#417690; color:#ffc; overflow:hidden; } +#header a:link, #header a:visited { color:white; } +#header a:hover { text-decoration:underline; } +#branding h1 { padding:0 10px; font-size:18px; margin:8px 0; font-weight:normal; color:#f4f379; } +#branding h2 { padding:0 10px; font-size:14px; margin:-8px 0 8px 0; font-weight:normal; color:#ffc; } +#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; } + +/* SIDEBAR */ +#content-related h3 { font-size:12px; color:#666; margin-bottom:3px; } +#content-related h4 { font-size:11px; } +#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; } \ No newline at end of file diff --git a/htsworkflow/frontend/static/img/icon_searchbox.png b/htsworkflow/frontend/static/img/icon_searchbox.png new file mode 100644 index 0000000000000000000000000000000000000000..8ab579e52653a9f829fe1f9463b73502b6a1d380 GIT binary patch literal 667 zcmV;M0%ZM(P)`~3X;@bK{O@9+Kn{rLF!_xJbl@$pSfP5S!!_4W1n`T6YZ?B3qqPft(K z(9rAa>(gwwA^YilZ@=HrgUS3{FNlEth_IP-BSy@@<=jZ6? z=$e|EaBy(V&CR>JyVcdz;o;$EXlUQx-&9moTU%RKS68&OwCU;T!NJ+t*-}zc&)b92AH zzpJaOZfvlTPVlRhn*D zzYj!ubf8e2}%QyRfsr*SWe%Rb`=>R1ae%6%2~5|TAxeL zzADQysO81>*ZZYCyl6@$BdV$biI?uic&0bzMrFb%Aq1#ZUv-+v+w0VD#)3jAg-{rn zQK{SBuPmhC5$}W{D84q?yXp06=7SUzUxd5@c^_-Jtgg3OcgI0-bjam_F)Z9$Uf=5< z*hz6@lo{ZZu*P|f^TenPFmQjgIBP%6a@>yq0{~})9l-cU)g%A_002ovPDHLkV1oH8 BY;phq literal 0 HcmV?d00001 diff --git a/htsworkflow/frontend/templates/samples/library_index.html b/htsworkflow/frontend/templates/samples/library_index.html index 19e30c7..cb91b69 100644 --- a/htsworkflow/frontend/templates/samples/library_index.html +++ b/htsworkflow/frontend/templates/samples/library_index.html @@ -1,26 +1,24 @@ - +{% load i18n %} +{% load admin_list %} + + + + +
+ {% block search %}{% search_form cl %}{% endblock %} + {% block pagination %}{% pagination cl %}{% endblock %} + + {% block filters %} + {% if cl.has_filters %} +
+

{% trans 'Filter' %}

+ {% for spec in cl.filter_specs %} + {% admin_list_filter cl spec %} + {% endfor %} +
+ {% endif %} + {% endblock %} {% block summary_stats %} @@ -43,4 +41,5 @@ {% endfor %}
+
{% endblock %} diff --git a/htsworkflow/frontend/urls.py b/htsworkflow/frontend/urls.py index ccc31b3..0569182 100644 --- a/htsworkflow/frontend/urls.py +++ b/htsworkflow/frontend/urls.py @@ -8,6 +8,8 @@ admin.autodiscover() #databrowse.site.register(Library) #databrowse.site.register(FlowCell) +from htsworkflow.frontend import settings + urlpatterns = patterns('', # Base: (r'^eland_config/', include('htsworkflow.frontend.eland_config.urls')), @@ -36,3 +38,9 @@ urlpatterns = patterns('', # databrowser #(r'^databrowse/(.*)', databrowse.site.root) ) + +if settings.DEBUG: + urlpatterns += patterns('', + (r'^static/(?P.*)$', 'django.views.static.serve', + {'document_root': settings.MEDIA_ROOT}), + ) -- 2.30.2