Skip to content

Commit 1b1d156

Browse files
authored
Breeding Pair form validation removes parents when culled (#944)
* breeding pair form should not exclude parent if culled. Fixes #941 * hopefully fixes the tests * fix timezone failing test
1 parent ac92eae commit 1b1d156

File tree

5 files changed

+65
-22
lines changed

5 files changed

+65
-22
lines changed

alyx/jobs/tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_cleanup(self):
6868
"""Test for cleanup action."""
6969
# First run in dry mode, expect submit_delete to not be called
7070
n = self.n_tasks - 10
71-
before_date = (self.base - timedelta(days=n)).date()
71+
before_date = (self.base - timedelta(days=n - .1)).date()
7272
with patch.object(self.command.stdout, 'write') as stdout_mock:
7373
self.command.handle(action='cleanup', before=str(before_date), dry=True)
7474
stdout_mock.assert_called()

alyx/subjects/admin.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import uuid
2+
13
from django import forms
24
from django_admin_listfilter_dropdown.filters import RelatedDropdownFilter
35
from django.contrib import admin
@@ -836,10 +838,12 @@ def queryset(self, request, queryset):
836838
return queryset.all()
837839

838840

839-
def _bp_subjects(line, sex):
841+
def _bp_subjects(line, sex, current_subjects=None):
840842
# All alive subjects of the given sex.
841843
qs = Subject.objects.filter(
842-
sex=sex, responsible_user__is_stock_manager=True, cull__isnull=True)
844+
Q(sex=sex, responsible_user__is_stock_manager=True, cull__isnull=True) |
845+
Q(pk__in=[current_subjects] if isinstance(current_subjects, uuid.UUID) else []),
846+
)
843847
qs = qs.order_by('nickname')
844848
ids = [item.id for item in qs]
845849
if ids:
@@ -876,7 +880,9 @@ def __init__(self, *args, **kwargs):
876880
for w in ('father', 'mother1', 'mother2'):
877881
sex = 'M' if w == 'father' else 'F'
878882
if w in self.fields:
879-
self.fields[w].queryset = _bp_subjects(self.instance.line, sex)
883+
self.fields[w].queryset = _bp_subjects(
884+
self.instance.line, sex, current_subjects=
885+
getattr(getattr(self.instance, w), 'id', None))
880886

881887
def save(self, commit=True):
882888
cage = self.cleaned_data.get('cage')
@@ -1076,7 +1082,7 @@ def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
10761082
return field
10771083
if db_field.name in ('father', 'mother1', 'mother2'):
10781084
sex = 'M' if db_field.name == 'father' else 'F'
1079-
field.queryset = _bp_subjects(obj, sex)
1085+
field.queryset = _bp_subjects(obj, sex, BreedingPair.objects.filter(line=obj).values_list(f'{db_field.name}__pk'))
10801086
return field
10811087

10821088

alyx/subjects/tests_admin.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,45 @@
77

88
from alyx.test_base import setup_admin_subject_user
99
from misc.models import LabMember
10-
from subjects.models import Subject
10+
from subjects.models import Subject, BreedingPair
1111
from actions.models import Cull, CullMethod, CullReason
12-
from subjects.admin import CullForm, SubjectAdmin
12+
from subjects.admin import CullForm, SubjectAdmin, BreedingPairAdminForm
13+
14+
15+
class TestBreedingPairAdmin(TestCase):
16+
fixtures = ['misc.lab.json']
17+
18+
def setUp(self):
19+
self.site = AdminSite()
20+
setup_admin_subject_user(self)
21+
22+
def test_set_end_date_with_culled_subject(self):
23+
lab_member_stock_manager = LabMember.objects.create_user(
24+
username='stock_manager', password='bar123', email='foo@example.com', is_staff=True, is_active=True, is_stock_manager=True)
25+
kwargs_subjects = dict(birth_date=date(2025, 1, 1), lab=self.lab, responsible_user=lab_member_stock_manager)
26+
father = Subject.objects.create(nickname='father', sex='M', **kwargs_subjects)
27+
mother1 = Subject.objects.create(nickname='mother1', sex='F', **kwargs_subjects)
28+
breeding_pair = BreedingPair.objects.create(father=father, mother1=mother1, mother2=None)
29+
# the form is valid as both parents are alive
30+
form_data = {
31+
'json': breeding_pair.json,
32+
'description': breeding_pair.description,
33+
'name': breeding_pair.name,
34+
'line': breeding_pair.line,
35+
'start_date': breeding_pair.start_date,
36+
'end_date': breeding_pair.end_date,
37+
'father': breeding_pair.father,
38+
'mother1': breeding_pair.mother1,
39+
'mother2': breeding_pair.mother2,
40+
}
41+
form_instance = BreedingPairAdminForm(data=form_data, instance=breeding_pair)
42+
self.assertTrue(form_instance.is_valid())
43+
# the form is still valid once the father is culled: it can´t be set on a new breeding pair,
44+
# but it can remain on this one and the form is valid
45+
Cull(subject=father, user=lab_member_stock_manager, date=date(2025, 6, 1))
46+
father.save()
47+
form_instance = BreedingPairAdminForm(data=form_data, instance=breeding_pair)
48+
self.assertTrue(form_instance.is_valid())
1349

1450

1551
class TestSubjectCullForm(TestCase):

deploy/docker/Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ COPY settings-deploy.py /var/www/alyx/alyx/alyx/settings.py
44
COPY settings_lab-deploy.py /var/www/alyx/alyx/alyx/settings_lab.py
55
COPY settings_lab-user.py* /var/www/alyx/alyx/alyx/settings_lab.py
66

7-
ARG alyx_branch=deploy
7+
ARG alyx_branch=dev
8+
ARG iblalyx_branch=deploy
89

910
RUN git -C /var/www/alyx fetch origin ${alyx_branch}
1011
RUN git -C /var/www/alyx checkout -B ${alyx_branch} origin/${alyx_branch}
11-
RUN git -C /home/iblalyx fetch origin ${alyx_branch}
12-
RUN git -C /home/iblalyx checkout -B ${alyx_branch} origin/${alyx_branch}
12+
RUN git -C /home/iblalyx fetch origin ${iblalyx_branch}
13+
RUN git -C /home/iblalyx checkout -B ${iblalyx_branch} origin/${iblalyx_branch}
1314

1415
RUN pip install -r /var/www/alyx/requirements.txt
1516

deploy/docker/Dockerfile_base

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
FROM ubuntu/apache2:latest
1+
FROM ubuntu/apache2:2.4-22.04_beta
22

33
# python unbuffered allows to get real-time logs
4-
ENV PYTHONUNBUFFERED 1
4+
ENV PYTHONUNBUFFERED=1
55
ENV TZ=Europe/London
66
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
77

8-
8+
RUN cat /etc/lsb-release && apt-get update
99
# Install services, packages and perform cleanup
10-
RUN apt-get update && apt-get install -y \
10+
RUN apt-get install -y \
1111
awscli \
1212
bash \
1313
build-essential \
@@ -23,16 +23,16 @@ RUN apt-get update && apt-get install -y \
2323
wget
2424

2525
# The apt-add-repository command is installed by software-properties common above
26-
ARG PYTHON=python3.12
27-
RUN apt-get update && apt-get install -y ${PYTHON} ${PYTHON}-venv
26+
ARG PYTHON=python3.13
27+
RUN apt-add-repository -y ppa:deadsnakes/ppa && apt-get update && apt-get install -y ${PYTHON} ${PYTHON}-venv
2828

2929
# Clean up
3030
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
3131

3232
RUN echo "root:Docker!" | chpasswd
3333

3434
# Alyx application installation
35-
RUN git clone --branch master https://github.com/cortex-lab/alyx.git /var/www/alyx
35+
RUN git clone --branch dev https://github.com/cortex-lab/alyx.git /var/www/alyx
3636
# Best practice for configuring python venv
3737
ENV VIRTUAL_ENV=/var/www/alyx/.venv
3838
RUN ${PYTHON} -m venv ${VIRTUAL_ENV}
@@ -48,11 +48,11 @@ RUN git clone --branch main https://github.com/int-brain-lab/iblalyx.git /home/i
4848
&& pip install --no-cache-dir -r /home/iblalyx/requirements.txt
4949

5050
# Apache ENVs
51-
ENV APACHE_RUN_USER www-data
52-
ENV APACHE_RUN_GROUP www-data
53-
ENV APACHE_LOCK_DIR /var/lock/apache2
54-
ENV APACHE_LOG_DIR /var/log/apache2
55-
ENV APACHE_PID_FILE /var/run/apache2/apache2.pid
51+
ENV APACHE_RUN_USER=www-data
52+
ENV APACHE_RUN_GROUP=www-data
53+
ENV APACHE_LOCK_DIR=/var/lock/apache2
54+
ENV APACHE_LOG_DIR=/var/log/apache2
55+
ENV APACHE_PID_FILE=/var/run/apache2/apache2.pid
5656

5757
RUN mkdir -p ${APACHE_LOG_DIR} \
5858
&& touch ${APACHE_LOG_DIR}/django.log \

0 commit comments

Comments
 (0)