• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Clinical-Genomics / cg / 11462014847

22 Oct 2024 02:09PM UTC coverage: 84.454%. First build
11462014847

Pull #3856

github

web-flow
Merge bc1b6b548 into b00bae95c
Pull Request #3856: Hook up ordertype_application creation

18 of 31 new or added lines in 3 files covered. (58.06%)

23268 of 27551 relevant lines covered (84.45%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.29
/cg/server/admin.py
1
"""Module for Flask-Admin views"""
2

3
from gettext import gettext
1✔
4

5
from flask import flash, redirect, request, session, url_for
1✔
6
from flask_admin.actions import action
1✔
7
from flask_admin.contrib.sqla import ModelView
1✔
8
from flask_dance.contrib.google import google
1✔
9
from markupsafe import Markup
1✔
10
from sqlalchemy import inspect
1✔
11
from wtforms.form import Form
1✔
12

13
from cg.constants.constants import NG_UL_SUFFIX, CaseActions, DataDelivery, Workflow
1✔
14
from cg.models.orders.constants import OrderType
1✔
15
from cg.server.ext import db, sample_service
1✔
16
from cg.server.utils import MultiCheckboxField
1✔
17
from cg.store.models import Application
1✔
18
from cg.utils.flask.enum import SelectEnumField
1✔
19

20

21
class BaseView(ModelView):
1✔
22
    """Base for the specific views."""
23

24
    def is_accessible(self):
1✔
25
        user = db.get_user_by_email(email=session.get("user_email"))
×
26
        return bool(google.authorized and user and user.is_admin)
×
27

28
    def inaccessible_callback(self, name, **kwargs):
1✔
29
        # redirect to login page if user doesn't have access
30
        return redirect(url_for("google.login", next=request.url))
×
31

32

33
def view_priority(unused1, unused2, model, unused3):
1✔
34
    """column formatter for priority"""
35
    del unused1, unused2, unused3
×
36
    return Markup("%s" % model.priority.name) if model else ""
×
37

38

39
def view_flow_cell_internal_id(unused1, unused2, model, unused3):
1✔
40
    """column formatter for priority"""
41
    del unused1, unused2, unused3
×
42
    return Markup("%s" % model.device.internal_id)
×
43

44

45
def view_flow_cell_model(unused1, unused2, model, unused3):
1✔
46
    """column formatter for priority"""
47
    del unused1, unused2, unused3
×
48
    return Markup("%s" % model.device.model)
×
49

50

51
def view_smrt_cell_model(unused1, unused2, model, unused3):
1✔
52
    del unused1, unused2, unused3
×
53
    return Markup("%s" % model.device.model)
×
54

55

56
def view_smrt_cell_internal_id(unused1, unused2, model, unused3):
1✔
57
    del unused1, unused2, unused3
×
58
    return Markup("%s" % model.device.internal_id)
×
59

60

61
def view_case_sample_link(unused1, unused2, model, unused3):
1✔
62
    """column formatter to open the case-sample view"""
63

64
    del unused1, unused2, unused3
×
65

66
    return Markup(
×
67
        "<a href='%s'>%s</a>"
68
        % (url_for("casesample.index_view", search=model.internal_id), model.internal_id)
69
    )
70

71

72
def is_external_application(unused1, unused2, model, unused3):
1✔
73
    """column formatter to open this view"""
74
    del unused1, unused2, unused3
×
75
    return model.application_version.application.is_external if model.application_version else ""
×
76

77

78
def view_order_types(unused1, unused2, model, unused3):
1✔
NEW
79
    del unused1, unused2, unused3
×
NEW
80
    order_type_list = "<br>".join([order_type.order_type for order_type in model.order_types])
×
NEW
81
    return (
×
82
        Markup(f'<div style="display: inline-block; min-width: 200px;">{order_type_list}</div>')
83
        if model.order_types
84
        else ""
85
    )
86

87

88
def view_sample_concentration_minimum(unused1, unused2, model, unused3):
1✔
89
    """Column formatter to append unit"""
90
    del unused1, unused2, unused3
×
91
    return (
×
92
        str(model.sample_concentration_minimum) + NG_UL_SUFFIX
93
        if model.sample_concentration_minimum
94
        else None
95
    )
96

97

98
def view_sample_concentration_maximum(unused1, unused2, model, unused3):
1✔
99
    """Column formatter to append unit"""
100
    del unused1, unused2, unused3
×
101
    return (
×
102
        str(model.sample_concentration_maximum) + NG_UL_SUFFIX
103
        if model.sample_concentration_maximum
104
        else None
105
    )
106

107

108
def view_sample_concentration_minimum_cfdna(unused1, unused2, model, unused3):
1✔
109
    """Column formatter to append unit"""
110
    del unused1, unused2, unused3
×
111
    return (
×
112
        str(model.sample_concentration_minimum_cfdna) + NG_UL_SUFFIX
113
        if model.sample_concentration_minimum_cfdna
114
        else None
115
    )
116

117

118
def view_sample_concentration_maximum_cfdna(unused1, unused2, model, unused3):
1✔
119
    """Column formatter to append unit"""
120
    del unused1, unused2, unused3
×
121
    return (
×
122
        str(model.sample_concentration_maximum_cfdna) + NG_UL_SUFFIX
123
        if model.sample_concentration_maximum_cfdna
124
        else None
125
    )
126

127

128
class ApplicationView(BaseView):
1✔
129
    """Admin view for Model.Application"""
130

131
    column_list = [column_name for column_name in inspect(Application).columns] + ["order_types"]
1✔
132

133
    column_editable_list = [
1✔
134
        "description",
135
        "is_accredited",
136
        "target_reads",
137
        "percent_reads_guaranteed",
138
        "comment",
139
        "prep_category",
140
        "sequencing_depth",
141
        "min_sequencing_depth",
142
        "is_external",
143
        "turnaround_time",
144
        "sample_concentration",
145
        "sample_concentration_minimum",
146
        "sample_concentration_maximum",
147
        "sample_concentration_minimum_cfdna",
148
        "sample_concentration_maximum_cfdna",
149
        "priority_processing",
150
        "is_archived",
151
    ]
152
    column_exclude_list = [
1✔
153
        "minimum_order",
154
        "sample_amount",
155
        "sample_volume",
156
        "details",
157
        "limitations",
158
        "created_at",
159
        "updated_at",
160
        "category",
161
    ]
162
    column_formatters = {
1✔
163
        "order_types": view_order_types,
164
        "sample_concentration_minimum": view_sample_concentration_minimum,
165
        "sample_concentration_maximum": view_sample_concentration_maximum,
166
        "sample_concentration_minimum_cfdna": view_sample_concentration_minimum_cfdna,
167
        "sample_concentration_maximum_cfdna": view_sample_concentration_maximum_cfdna,
168
    }
169
    column_filters = ["prep_category", "is_accredited"]
1✔
170
    column_searchable_list = ["tag", "prep_category"]
1✔
171
    form_excluded_columns = ["category", "versions", "order_types"]
1✔
172
    form_extra_fields = {
1✔
173
        "suitable_order_types": MultiCheckboxField(
174
            "Order Types", choices=[(choice, choice.name) for choice in OrderType]
175
        )
176
    }
177

178
    @staticmethod
1✔
179
    def view_application_link(unused1, unused2, model, unused3):
1✔
180
        """column formatter to open this view"""
181
        del unused1, unused2, unused3
×
182
        return (
×
183
            Markup(
184
                "<a href='%s'>%s</a>"
185
                % (
186
                    url_for("application.index_view", search=model.application.tag),
187
                    model.application.tag,
188
                )
189
            )
190
            if model.application
191
            else ""
192
        )
193

194
    def on_model_change(self, form: Form, model: Application, is_created: bool):
1✔
195
        """Override to persist entries to the OrderTypeApplication table."""
NEW
196
        super(ApplicationView, self).on_model_change(form=form, model=model, is_created=is_created)
×
NEW
197
        order_types: list[OrderType] = form["suitable_order_types"].data
×
NEW
198
        db.delete_order_type_applications_by_application_id(model.id)
×
NEW
199
        db.session.add_all(
×
200
            db.link_order_types_to_application(application=model, order_types=order_types)
201
        )
NEW
202
        db.session.commit()
×
203

204
    def edit_form(self, obj=None):
1✔
205
        """Override to prefill the order types according to the current Application entry."""
NEW
206
        form = super(ApplicationView, self).edit_form(obj)
×
207

208
        # Pre-select the existing order types for the application
NEW
209
        if obj and obj.order_types and not form.suitable_order_types.data:
×
NEW
210
            form.suitable_order_types.data = [
×
211
                order_type.order_type for order_type in obj.order_types
212
            ]
213

NEW
214
        return form
×
215

216

217
class ApplicationVersionView(BaseView):
1✔
218
    """Admin view for Model.ApplicationVersion"""
219

220
    column_default_sort = ("valid_from", True)
1✔
221
    column_editable_list = [
1✔
222
        "valid_from",
223
        "price_standard",
224
        "price_priority",
225
        "price_express",
226
        "price_clinical_trials",
227
        "price_research",
228
    ]
229
    column_list = (
1✔
230
        "application",
231
        "version",
232
        "valid_from",
233
        "price_standard",
234
        "price_priority",
235
        "price_express",
236
        "price_clinical_trials",
237
        "price_research",
238
        "comment",
239
    )
240
    column_exclude_list = ["created_at", "updated_at"]
1✔
241
    column_filters = ["version", "application.tag"]
1✔
242
    column_formatters = {"application": ApplicationView.view_application_link}
1✔
243
    column_searchable_list = ["application.tag"]
1✔
244
    edit_modal = True
1✔
245
    create_modal = True
1✔
246
    form_excluded_columns = ["samples", "pools", "microbial_samples"]
1✔
247

248

249
class ApplicationLimitationsView(BaseView):
1✔
250
    """Admin view for Model.ApplicationLimitations."""
251

252
    column_list = (
1✔
253
        "application",
254
        "workflow",
255
        "limitations",
256
        "comment",
257
        "created_at",
258
        "updated_at",
259
    )
260
    column_formatters = {"application": ApplicationView.view_application_link}
1✔
261
    column_filters = ["application.tag", "workflow"]
1✔
262
    column_searchable_list = ["application.tag"]
1✔
263
    column_editable_list = ["comment"]
1✔
264
    form_excluded_columns = ["created_at", "updated_at"]
1✔
265
    form_extra_fields = {"workflow": SelectEnumField(enum_class=Workflow)}
1✔
266
    create_modal = True
1✔
267
    edit_modal = True
1✔
268

269

270
class BedView(BaseView):
1✔
271
    """Admin view for Model.Bed"""
272

273
    column_default_sort = "name"
1✔
274
    column_editable_list = ["comment"]
1✔
275
    column_exclude_list = ["created_at"]
1✔
276
    column_filters = []
1✔
277
    column_searchable_list = ["name"]
1✔
278
    form_excluded_columns = ["created_at", "updated_at"]
1✔
279

280
    @staticmethod
1✔
281
    def view_bed_link(unused1, unused2, model, unused3):
1✔
282
        """column formatter to open this view"""
283
        del unused1, unused2, unused3
×
284
        return (
×
285
            Markup(
286
                "<a href='%s'>%s</a>"
287
                % (url_for("bed.index_view", search=model.bed.name), model.bed.name)
288
            )
289
            if model.bed
290
            else ""
291
        )
292

293

294
class BedVersionView(BaseView):
1✔
295
    """Admin view for Model.BedVersion"""
296

297
    column_default_sort = ("updated_at", True)
1✔
298
    column_editable_list = ["shortname", "filename", "comment", "designer", "checksum"]
1✔
299
    column_exclude_list = ["created_at"]
1✔
300
    form_excluded_columns = ["created_at", "updated_at", "samples"]
1✔
301
    column_filters = []
1✔
302
    column_formatters = {"bed": BedView.view_bed_link}
1✔
303
    column_searchable_list = ["bed.name"]
1✔
304
    edit_modal = True
1✔
305

306

307
class CustomerView(BaseView):
1✔
308
    """Admin view for Model.Customer"""
309

310
    column_editable_list = [
1✔
311
        "collaborations",
312
        "comment",
313
        "delivery_contact",
314
        "lab_contact",
315
        "loqus_upload",
316
        "primary_contact",
317
        "priority",
318
        "return_samples",
319
        "scout_access",
320
    ]
321
    column_list = [
1✔
322
        "internal_id",
323
        "name",
324
        "data_archive_location",
325
        "comment",
326
        "primary_contact",
327
        "delivery_contact",
328
        "lab_contact",
329
        "priority",
330
        "project_account_KI",
331
        "project_account_kth",
332
        "return_samples",
333
        "scout_access",
334
    ]
335
    column_filters = ["priority", "scout_access", "data_archive_location"]
1✔
336
    column_searchable_list = ["internal_id", "name"]
1✔
337
    form_excluded_columns = ["families", "samples", "pools", "orders", "invoices"]
1✔
338

339

340
class CollaborationView(BaseView):
1✔
341
    """Admin view for Model.CustomerGroup"""
342

343
    column_editable_list = ["name"]
1✔
344
    column_filters = []
1✔
345
    column_hide_backrefs = False
1✔
346
    column_list = ("internal_id", "name", "customers")
1✔
347
    column_searchable_list = ["internal_id", "name"]
1✔
348

349

350
class CaseView(BaseView):
1✔
351
    """Admin view for Model.Case"""
352

353
    column_default_sort = ("created_at", True)
1✔
354
    column_editable_list = ["action", "comment"]
1✔
355
    column_exclude_list = ["created_at", "_cohorts", "synopsis"]
1✔
356
    column_filters = [
1✔
357
        "customer.internal_id",
358
        "priority",
359
        "action",
360
        "data_analysis",
361
        "data_delivery",
362
        "tickets",
363
    ]
364
    column_formatters = {
1✔
365
        "internal_id": view_case_sample_link,
366
        "priority": view_priority,
367
    }
368
    column_searchable_list = [
1✔
369
        "internal_id",
370
        "name",
371
        "customer.internal_id",
372
        "tickets",
373
    ]
374
    form_excluded_columns = [
1✔
375
        "analyses",
376
        "_cohorts",
377
        "links",
378
        "synopsis",
379
    ]
380
    form_extra_fields = {
1✔
381
        "data_analysis": SelectEnumField(enum_class=Workflow),
382
        "data_delivery": SelectEnumField(enum_class=DataDelivery),
383
    }
384

385
    @staticmethod
1✔
386
    def view_case_link(unused1, unused2, model, unused3):
1✔
387
        """column formatter to open this view"""
388
        del unused1, unused2, unused3
×
389
        markup = ""
×
390
        if model.case:
×
391
            markup += Markup(
×
392
                " <a href='%s'>%s</a>"
393
                % (url_for("case.index_view", search=model.case.internal_id), model.case)
394
            )
395

396
        return markup
×
397

398
    @action(
1✔
399
        "set_hold",
400
        "Set action to hold",
401
        "Are you sure you want to set the action for selected families to hold?",
402
    )
403
    def action_set_hold(self, ids: list[str]):
1✔
404
        self.set_action_for_cases(action=CaseActions.HOLD, case_entry_ids=ids)
×
405

406
    @action(
1✔
407
        "set_empty",
408
        "Set action to Empty",
409
        "Are you sure you want to set the action for selected families to Empty?",
410
    )
411
    def action_set_empty(self, ids: list[str]):
1✔
412
        self.set_action_for_cases(action=None, case_entry_ids=ids)
×
413

414
    def set_action_for_cases(self, action: CaseActions | None, case_entry_ids: list[str]):
1✔
415
        try:
×
416
            for entry_id in case_entry_ids:
×
417
                case = db.get_case_by_entry_id(entry_id=entry_id)
×
418
                if case:
×
419
                    case.action = action
×
420

421
            db.session.commit()
×
422

423
            num_families = len(case_entry_ids)
×
424
            action_message = (
×
425
                f"Families were set to {action}."
426
                if num_families == 1
427
                else f"{num_families} families were set to {action}."
428
            )
429
            flash(action_message)
×
430

431
        except Exception as ex:
×
432
            if not self.handle_view_exception(ex):
×
433
                raise
×
434

435
            flash(gettext(f"Failed to set case action. {str(ex)}"))
×
436

437

438
class InvoiceView(BaseView):
1✔
439
    """Admin view for Model.Invoice"""
440

441
    column_default_sort = ("created_at", True)
1✔
442
    column_editable_list = ["comment"]
1✔
443
    column_list = (
1✔
444
        "id",
445
        "customer",
446
        "created_at",
447
        "updated_at",
448
        "invoiced_at",
449
        "comment",
450
        "discount",
451
        "price",
452
    )
453
    column_searchable_list = ["customer.internal_id", "customer.name", "id"]
1✔
454

455
    @staticmethod
1✔
456
    def view_invoice_link(unused1, unused2, model, unused3):
1✔
457
        """column formatter to open this view"""
458
        del unused1, unused2, unused3
×
459
        return (
×
460
            Markup(
461
                "<a href='%s'>%s</a>"
462
                % (
463
                    url_for("invoice.index_view", search=model.invoice.id),
464
                    (
465
                        model.invoice.invoiced_at.date()
466
                        if model.invoice.invoiced_at
467
                        else "In progress"
468
                    ),
469
                )
470
            )
471
            if model.invoice
472
            else ""
473
        )
474

475

476
class AnalysisView(BaseView):
1✔
477
    """Admin view for Model.Analysis"""
478

479
    column_default_sort = ("created_at", True)
1✔
480
    column_editable_list = ["is_primary", "comment"]
1✔
481
    column_filters = ["workflow", "workflow_version", "is_primary"]
1✔
482
    column_formatters = {"case": CaseView.view_case_link}
1✔
483
    column_searchable_list = [
1✔
484
        "case.internal_id",
485
        "case.name",
486
    ]
487
    form_extra_fields = {"workflow": SelectEnumField(enum_class=Workflow)}
1✔
488

489

490
class IlluminaFlowCellView(BaseView):
1✔
491
    """Admin view for Model.IlluminaSequencingRun"""
492

493
    column_list = (
1✔
494
        "internal_id",
495
        "model",
496
        "sequencer_type",
497
        "sequencer_name",
498
        "data_availability",
499
        "has_backup",
500
        "total_reads",
501
        "total_undetermined_reads",
502
        "percent_undetermined_reads",
503
        "percent_q30",
504
        "mean_quality_score",
505
        "total_yield",
506
        "yield_q30",
507
        "cycles",
508
        "demultiplexing_software",
509
        "demultiplexing_software_version",
510
        "sequencing_started_at",
511
        "sequencing_completed_at",
512
        "demultiplexing_started_at",
513
        "demultiplexing_completed_at",
514
        "archived_at",
515
    )
516
    column_formatters = {"internal_id": view_flow_cell_internal_id, "model": view_flow_cell_model}
1✔
517
    column_default_sort = ("sequencing_completed_at", True)
1✔
518
    column_filters = ["sequencer_type", "sequencer_name", "data_availability"]
1✔
519
    column_editable_list = ["data_availability"]
1✔
520
    column_searchable_list = ["sequencer_type", "sequencer_name", "device.internal_id"]
1✔
521
    column_sortable_list = [
1✔
522
        ("internal_id", "device.internal_id"),
523
        "sequencer_type",
524
        "sequencer_name",
525
        "data_availability",
526
        "has_backup",
527
        "sequencing_started_at",
528
        "sequencing_completed_at",
529
        "demultiplexing_started_at",
530
        "demultiplexing_completed_at",
531
        "archived_at",
532
    ]
533

534
    @staticmethod
1✔
535
    def view_flow_cell_link(unused1, unused2, model, unused3):
1✔
536
        """column formatter to open this view"""
537
        del unused1, unused2, unused3
×
538
        return (
×
539
            Markup(
540
                "<a href='%s'>%s</a>"
541
                % (
542
                    url_for(
543
                        "illuminasequencingrun.index_view",
544
                        search=model.instrument_run.device.internal_id,
545
                    ),
546
                    model.instrument_run.device.internal_id,
547
                )
548
            )
549
            if model.instrument_run.device
550
            else ""
551
        )
552

553

554
class OrganismView(BaseView):
1✔
555
    """Admin view for Model.Organism"""
556

557
    column_default_sort = ("created_at", True)
1✔
558
    column_editable_list = ["internal_id", "name", "reference_genome", "comment"]
1✔
559
    column_searchable_list = ["internal_id", "name", "reference_genome"]
1✔
560

561

562
class OrderView(BaseView):
1✔
563
    """Admin view for Model.Order"""
564

565
    column_default_sort = ("order_date", True)
1✔
566
    column_editable_list = ["is_open"]
1✔
567
    column_searchable_list = ["id", "ticket_id"]
1✔
568
    column_display_pk = True
1✔
569
    create_modal = True
1✔
570
    edit_modal = True
1✔
571

572

573
class PanelView(BaseView):
1✔
574
    """Admin view for Model.Panel"""
575

576
    column_editable_list = ["current_version", "name"]
1✔
577
    column_filters = ["customer.internal_id"]
1✔
578
    column_searchable_list = ["customer.internal_id", "name", "abbrev"]
1✔
579
    create_modal = True
1✔
580
    edit_modal = True
1✔
581

582

583
class PoolView(BaseView):
1✔
584
    """Admin view for Model.Pool"""
585

586
    column_default_sort = ("created_at", True)
1✔
587
    column_editable_list = ["ticket"]
1✔
588
    column_filters = ["customer.internal_id", "application_version.application"]
1✔
589
    column_formatters = {"invoice": InvoiceView.view_invoice_link}
1✔
590
    column_searchable_list = ["name", "order", "ticket", "customer.internal_id"]
1✔
591

592

593
class SampleView(BaseView):
1✔
594
    """Admin view for Model.Sample"""
595

596
    column_exclude_list = [
1✔
597
        "age_at_sampling",
598
        "_phenotype_groups",
599
        "_phenotype_terms",
600
    ]
601
    column_default_sort = ("created_at", True)
1✔
602
    column_editable_list = [
1✔
603
        "comment",
604
        "downsampled_to",
605
        "is_tumour",
606
        "last_sequenced_at",
607
        "sex",
608
    ]
609
    column_filters = [
1✔
610
        "customer.internal_id",
611
        "priority",
612
        "sex",
613
        "application_version.application",
614
        "capture_kit",
615
    ]
616
    column_formatters = {
1✔
617
        "is_external": is_external_application,
618
        "internal_id": view_case_sample_link,
619
        "invoice": InvoiceView.view_invoice_link,
620
        "priority": view_priority,
621
    }
622
    column_searchable_list = [
1✔
623
        "internal_id",
624
        "name",
625
        "subject_id",
626
        "customer.internal_id",
627
        "original_ticket",
628
    ]
629
    form_excluded_columns = [
1✔
630
        "age_at_sampling",
631
        "deliveries",
632
        "father_links",
633
        "flowcells",
634
        "invoice",
635
        "_phenotype_groups",
636
        "_phenotype_terms",
637
        "links",
638
        "mother_links",
639
    ]
640

641
    @staticmethod
1✔
642
    def view_sample_link(unused1, unused2, model, unused3):
1✔
643
        """column formatter to open this view"""
644
        del unused1, unused2, unused3
×
645
        return (
×
646
            Markup(
647
                "<a href='%s'>%s</a>"
648
                % (url_for("sample.index_view", search=model.sample.internal_id), model.sample)
649
            )
650
            if model.sample
651
            else ""
652
        )
653

654
    @action(
1✔
655
        "cancel_samples",
656
        "Cancel samples",
657
        "Are you sure you want to cancel the selected samples?",
658
    )
659
    def cancel_samples(self, entry_ids: list[str]) -> None:
1✔
660
        user_email: str | None = session.get("user_email")
×
661
        message: str = sample_service.cancel_samples(
×
662
            sample_ids=entry_ids,
663
            user_email=user_email,
664
        )
665
        flash(message)
×
666

667

668
class DeliveryView(BaseView):
1✔
669
    """Admin view for Model.Delivery"""
670

671
    column_default_sort = ("id", True)
1✔
672
    column_filters = ["sample.internal_id"]
1✔
673
    column_formatters = {"sample": SampleView.view_sample_link}
1✔
674
    column_searchable_list = ["sample.internal_id"]
1✔
675
    create_modal = True
1✔
676
    edit_modal = True
1✔
677

678

679
class CaseSampleView(BaseView):
1✔
680
    """Admin view for Model.caseSample"""
681

682
    column_default_sort = ("created_at", True)
1✔
683
    column_editable_list = ["status"]
1✔
684
    column_filters = ["status"]
1✔
685
    column_formatters = {
1✔
686
        "case": CaseView.view_case_link,
687
        "sample": SampleView.view_sample_link,
688
    }
689
    column_searchable_list = ["case.internal_id", "case.name", "sample.internal_id"]
1✔
690
    create_modal = True
1✔
691
    edit_modal = True
1✔
692

693

694
class UserView(BaseView):
1✔
695
    """Admin view for Model.User"""
696

697
    column_default_sort = "name"
1✔
698
    column_editable_list = ["order_portal_login"]
1✔
699
    column_filters = ["is_admin", "order_portal_login", "customers"]
1✔
700
    column_hide_backrefs = False
1✔
701
    column_list = ("name", "email", "is_admin", "order_portal_login", "customers")
1✔
702
    column_searchable_list = ["name", "email"]
1✔
703
    create_modal = True
1✔
704
    edit_modal = True
1✔
705

706

707
class IlluminaSampleSequencingMetricsView(BaseView):
1✔
708
    column_list = [
1✔
709
        "flow_cell",
710
        "sample",
711
        "flow_cell_lane",
712
        "total_reads_in_lane",
713
        "base_passing_q30_percent",
714
        "base_mean_quality_score",
715
        "yield_",
716
        "yield_q30",
717
        "created_at",
718
    ]
719
    column_formatters = {
1✔
720
        "flow_cell": IlluminaFlowCellView.view_flow_cell_link,
721
        "sample": SampleView.view_sample_link,
722
    }
723
    column_searchable_list = ["sample.internal_id", "instrument_run.device.internal_id"]
1✔
724

725

726
class PacbioSmrtCellView(BaseView):
1✔
727
    """Admin view for Model.PacbioSMRTCell"""
728

729
    column_list = (
1✔
730
        "internal_id",
731
        "run_name",
732
        "movie_name",
733
        "well",
734
        "plate",
735
        "hifi_reads",
736
        "hifi_yield",
737
        "hifi_mean_read_length",
738
        "hifi_median_read_quality",
739
        "percent_reads_passing_q30",
740
        "p0_percent",
741
        "p1_percent",
742
        "p2_percent",
743
        "started_at",
744
        "completed_at",
745
        "barcoded_hifi_reads",
746
        "barcoded_hifi_reads_percentage",
747
        "barcoded_hifi_yield",
748
        "barcoded_hifi_yield_percentage",
749
        "barcoded_hifi_mean_read_length",
750
    )
751
    column_formatters = {"internal_id": view_smrt_cell_internal_id, "model": view_smrt_cell_model}
1✔
752
    column_default_sort = ("completed_at", True)
1✔
753
    column_searchable_list = ["device.internal_id", "run_name", "movie_name"]
1✔
754
    column_sortable_list = [
1✔
755
        ("internal_id", "device.internal_id"),
756
        "started_at",
757
        "completed_at",
758
    ]
759

760
    @staticmethod
1✔
761
    def view_smrt_cell_link(unused1, unused2, model, unused3):
1✔
762
        """column formatter to open this view"""
763
        del unused1, unused2, unused3
×
764
        return (
×
765
            Markup(
766
                "<a href='%s'>%s</a>"
767
                % (
768
                    url_for(
769
                        "pacbiosequencingrun.index_view",
770
                        search=model.instrument_run.device.internal_id,
771
                    ),
772
                    model.instrument_run.device.internal_id,
773
                )
774
            )
775
            if model.instrument_run.device
776
            else ""
777
        )
778

779

780
class PacbioSampleRunMetricsView(BaseView):
1✔
781
    column_list = [
1✔
782
        "smrt_cell",
783
        "sample",
784
        "hifi_reads",
785
        "hifi_yield",
786
        "hifi_mean_read_length",
787
        "hifi_median_read_quality",
788
    ]
789
    column_formatters = {
1✔
790
        "smrt_cell": PacbioSmrtCellView.view_smrt_cell_link,
791
        "sample": SampleView.view_sample_link,
792
    }
793
    column_searchable_list = ["sample.internal_id", "instrument_run.device.internal_id"]
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc