Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a3ed43a
Add new headers to CSV-files and refactor build_csv_data method (#294)
mortenlyn Jul 18, 2025
af8ae8e
Add possibility to toggle sample status (#297)
mortenlyn Jul 18, 2025
2e19654
Make contact name required (#293)
omfj Jul 18, 2025
5027364
Add a clear all button for the filters (#299)
aastabk Jul 18, 2025
3fe894d
Style analysis and extraction table (#310)
omfj Jul 21, 2025
fa7496b
Mark urgent order as seen (#303)
omfj Jul 21, 2025
3d54e05
Use DateColumn for date rendering (#311)
omfj Jul 21, 2025
7e25f75
Refactor order detail templates and add helper functions for renderin…
mortenlyn Jul 22, 2025
0110a2d
Make dashboard table scrollable (#319)
omfj Jul 22, 2025
d333fc9
Filters ready (#328)
aastabk Jul 22, 2025
6740bf0
Delivery date to deadline (#330)
omfj Jul 22, 2025
404a5c1
All staff can mark as seen (#327)
omfj Jul 22, 2025
5f8f1e4
Add CSV export functionality for sample and label data (#326)
mortenlyn Jul 22, 2025
e43bdf0
Revert to old equipment table (#336)
omfj Jul 22, 2025
fa6da1a
Refactor tabs and add buttons for different CSV downloads (#339)
mortenlyn Jul 22, 2025
41f1b5e
Add missing order_tags load in extractionorder_detail template (#342)
mortenlyn Jul 22, 2025
0352608
Add ruff PIE rules. (#344)
emilte Jul 22, 2025
4a9fcc6
Add ruff TC rules. (#346)
emilte Jul 22, 2025
0ba4d2d
Add genetic project to anl and ext table (#337)
omfj Jul 22, 2025
3bc7b7f
More mypy. (#334)
emilte Jul 22, 2025
712ba35
Add ruff TRY rules. (#348)
emilte Jul 22, 2025
69a7677
Add ruff DJ rules. (#350)
emilte Jul 22, 2025
d2d5979
Added PCR, analysis and output field to samples
aastabk Jul 22, 2025
551ad03
Added PCR, analysis and output for analysis samples.
aastabk Jul 22, 2025
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
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,11 @@ ignore = [
"ANN002", # Too strict for introduction of ANN. # https://docs.astral.sh/ruff/rules/missing-type-args/
"ANN003", # Too strict for introduction of ANN. # https://docs.astral.sh/ruff/rules/missing-type-kwargs/
"ANN204", # Too strict for introduction of ANN. # https://docs.astral.sh/ruff/rules/missing-return-type-special-method/
"ANN401" # Too strict and too many errors atm. Consider enabling later after more typing is added. Search for `Any` to find unknown types. # https://docs.astral.sh/ruff/rules/any-type/
"ANN401", # Too strict and too many errors atm. Consider enabling later after more typing is added. Search for `Any` to find unknown types. # https://docs.astral.sh/ruff/rules/any-type/
"TRY003", # This doesn't seem too bad. # https://docs.astral.sh/ruff/rules/raise-vanilla-args/
"DJ001" # Nullable string fields does is used a lot in this project. Maybe enable later? # https://docs.astral.sh/ruff/rules/django-nullable-model-string-field/
]
select = ["E", "W", "I", "F", "UP", "S", "B", "A", "COM", "LOG", "PTH", "Q", "PL", "RUF", "ANN"]
select = ["E", "W", "I", "F", "UP", "S", "B", "A", "COM", "LOG", "PTH", "Q", "PL", "RUF", "ANN", "PIE", "TC", "TRY", "DJ"]

[tool.ruff.lint.flake8-annotations]
mypy-init-return = true # https://docs.astral.sh/ruff/settings/#lint_flake8-annotations_mypy-init-return
Expand Down
2 changes: 1 addition & 1 deletion src/capps/users/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def report(e: Exception | None, error: Any) -> None:
set_context("oauth error", {"error": str(error)})
capture_exception(e)
except Exception:
logging.error(traceback.format_exc()) # noqa: LOG015
logging.exception(traceback.format_exc()) # noqa: LOG015


class AccountAdapter(DefaultAccountAdapter):
Expand Down
12 changes: 12 additions & 0 deletions src/capps/users/autocomplete.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
from dal import autocomplete
from django.db.models import QuerySet

from .models import User


class UserAutocomplete(autocomplete.Select2QuerySetView):
model = User
search_fields = ["email", "first_name", "last_name"]


class StaffUserAutocomplete(autocomplete.Select2QuerySetView):
model = User
search_fields = ["email", "first_name", "last_name"]

def get_queryset(self) -> QuerySet:
if not self.request.user.is_authenticated:
return User.objects.none()

return User.objects.filter(groups__name="genlab")
2 changes: 2 additions & 0 deletions src/capps/users/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class NotAuthenticated(Exception):
"""User is not authenticated when authentication is required."""
2 changes: 1 addition & 1 deletion src/capps/users/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class UserAdminCreationForm(admin_forms.UserCreationForm):

class Meta(admin_forms.UserCreationForm.Meta): # type: ignore[name-defined]
model = User
fields = ("email",)
fields = ["email"]
field_classes = {"email": EmailField}
error_messages = {
"email": {"unique": _("This email has already been taken.")},
Expand Down
2 changes: 1 addition & 1 deletion src/capps/users/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def _create_user(
user = self.model(email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
return user # type: ignore[return-value]

def create_user(
self: Self,
Expand Down
3 changes: 2 additions & 1 deletion src/capps/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from capps.ui.views import UIDetailView, UIListView, UIUpdateView

from .exceptions import NotAuthenticated
from .filters import UserFilterSet
from .tables import UserTable

Expand All @@ -32,7 +33,7 @@ class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):

def get_success_url(self: Self) -> str:
if not self.request.user.is_authenticated:
raise Exception("User is not authenticated")
raise NotAuthenticated
return self.request.user.get_absolute_url()

def get_object(self, queryset: QuerySet | None = None) -> Any:
Expand Down
9 changes: 8 additions & 1 deletion src/config/autocomplete.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from django.urls import path

from capps.users.autocomplete import UserAutocomplete
from capps.users.autocomplete import StaffUserAutocomplete, UserAutocomplete
from genlab_bestilling.autocomplete import (
AnalysisOrderAutocomplete,
AreaAutocomplete,
EquipmentAutocomplete,
ExtractionOrderAutocomplete,
GenrequestAutocomplete,
IsolationMethodAutocomplete,
LocationAutocomplete,
MarkerAutocomplete,
OrderAutocomplete,
Expand All @@ -25,6 +26,7 @@
path("project/", ProjectAutocomplete.as_view(), name="project"),
path("marker/", MarkerAutocomplete.as_view(), name="marker"),
path("user/", UserAutocomplete.as_view(), name="user"),
path("staff-user/", StaffUserAutocomplete.as_view(), name="staff-user"),
path("genrequest/", GenrequestAutocomplete.as_view(), name="genrequest"),
path("location/", LocationAutocomplete.as_view(), name="location"),
path("order/", OrderAutocomplete.as_view(), name="order"),
Expand All @@ -35,4 +37,9 @@
ExtractionOrderAutocomplete.as_view(),
name="extraction-order",
),
path(
"isolation-method/",
IsolationMethodAutocomplete.as_view(),
name="isolation-method",
),
]
184 changes: 184 additions & 0 deletions src/genlab_bestilling/api/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
SAMPLE_CSV_FIELD_LABELS = {
"genlab_id": "Genlab ID",
"fish_id": "Old Genlab ID",
"guid": "GUID",
"name": "Name",
"species.name": "Species",
"location.name": "Location",
"order": "EXT_order",
"analysis_orders": "ANL_order",
"pop_id": "PopID",
"type.name": "Sample Type",
"gender": "Gender",
"length": "Length",
"weight": "Weight",
"classification": "Classification",
"year": "Year",
"notes": "Notes",
"project": "Project Number",
"isolation_method": "Isolation Method",
"qiagen_number": "Qiagen#",
"is_marked": "Marked",
"is_plucked": "Plucked",
"is_isolated": "Isolated",
"station": "Station",
"placement_in_fridge": "Placement in Fridge",
"delivered_to_lab": "Delivered to Lab",
"fluidigm#": "Fluidigm#",
"laksov_species": "LaksOV + Species",
"species_date": "Species Date",
"pcr": "PCR",
"fluidigm": "Fluidigm",
"output": "Output",
"analysis_note": "Analysis Note",
"rerun": "Rerun",
"rerun_qiagen": "Rerun Qiagen",
"river": "River",
"str_date": "STR Date",
"rerun_date": "Rerun Date",
"watercourse_number": "Watercourse Number",
"under_locality": "Sub-locality",
"county": "County",
"snp_assay_1": "SNP Assay 1",
"snp_assay_2": "SNP Assay 2",
"str_mixab": "STR Mix AB",
"str_mixc": "STR Mix C",
"str_mixy": "STR Mix Y",
"internal_note": "Internal Note",
"bb#": "BB#",
"output_bb": "Output",
"bba2#": "BBA2#",
"output_bba2": "Output",
"mixab": "Mix AB",
"ab_rep#": "AB Rep#",
"mixc": "Mix C",
"c_rep#": "C Rep#",
"re_extracted_date": "Re-extracted Date",
"re_extracted_qiagen#": "Re-extracted Qiagen#",
"weight_g_for_diet_analysis": "Weight (g) for Diet Analysis",
"position": "Position",
}

SAMPLE_CSV_FIELDS_BY_AREA = {
"Akvatisk": [
"name",
"genlab_id",
"fish_id",
"guid",
"order",
"analysis_orders",
"location.name",
"watercourse_number",
"pop_id",
"species.name",
"gender",
"length",
"weight",
"classification",
"year",
"notes",
"project",
"type.name",
"isolation_method",
"qiagen_number",
"fluidigm#",
"laksov_species",
"species_date",
"is_marked",
"is_plucked",
"is_isolated",
"pcr",
"fluidigm",
"output",
"analysis_note",
"rerun",
"rerun_date",
],
"Elvemusling": [
"name",
"genlab_id",
"fish_id",
"guid",
"river",
"location.name",
"under_locality",
"watercourse_number",
"county",
"year",
"station",
"type.name",
"length",
"notes",
"isolation_method",
"qiagen_number",
"position",
"placement_in_fridge",
"is_marked",
"is_plucked",
"is_isolated",
"pcr",
"str_date",
"output",
"analysis_note",
"rerun",
"rerun_date",
],
"Terrestrisk": [
"name",
"genlab_id",
"guid",
"type.name",
"species.name",
"location.name",
"delivered_to_lab",
"order",
"analysis_orders",
"notes",
"snp_assay_1",
"snp_assay_2",
"str_mixab",
"str_mixc",
"str_mixy",
"is_marked",
"is_plucked",
"is_isolated",
"isolation_method",
"qiagen_number",
"internal_note",
"bb#",
"output_bb",
"bba2#",
"output_bba2",
"mixab",
"ab_rep#",
"mixc",
"c_rep#",
"re_extracted_date",
"re_extracted_qiagen#",
"weight_g_for_diet_analysis",
],
"default": [
"genlab_id",
"guid",
"name",
"type.name",
"species.name",
"location.name",
"order",
"analysis_orders",
"notes",
"is_marked",
"is_plucked",
"is_isolated",
"isolation_method",
"qiagen_number",
],
}

LABEL_CSV_FIELDS_BY_AREA = {
"Akvatisk": ["fish_id", "genlab_id", "guid"],
"Elvemusling": ["fish_id", "genlab_id", "guid"],
"Terrestrisk": ["genlab_id", "guid", "name"],
"MiljøDNA": ["genlab_id", "guid", "name", "location.name"],
"default": ["genlab_id", "guid", "name"],
}
Loading