Skip to content

Commit db6754a

Browse files
authored
feat: OPTIC-1656: Factory boy and core factories (#7116)
Co-authored-by: mcanu <[email protected]>
1 parent 7ac6bc9 commit db6754a

14 files changed

+150
-58
lines changed

Dockerfile

+9-1
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,17 @@ ENV PATH="$VENV_PATH/bin:$PATH"
8787
# Copy dependency files
8888
COPY pyproject.toml poetry.lock README.md ./
8989

90+
# Set a default build argument for including dev dependencies
91+
ARG INCLUDE_DEV=false
92+
9093
# Install dependencies without dev packages
9194
RUN --mount=type=cache,target=$POETRY_CACHE_DIR,sharing=locked \
92-
poetry check --lock && poetry install --no-root --without test --extras uwsgi
95+
poetry check --lock && \
96+
if [ "$INCLUDE_DEV" = "true" ]; then \
97+
poetry install --no-root --extras uwsgi --with test; \
98+
else \
99+
poetry install --no-root --without test --extras uwsgi; \
100+
fi
93101

94102
# Install LS
95103
COPY label_studio label_studio

docker-compose.override.example.yml

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
version: "3.9"
22
services:
33
app:
4+
build:
5+
args:
6+
- INCLUDE_DEV=true
47
env_file:
58
- .env
9+
10+
nginx:
11+
build:
12+
args:
13+
- INCLUDE_DEV=true

label_studio/organizations/functions.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
from projects.models import Project
55

66

7-
def create_organization(title, created_by):
7+
def create_organization(title, created_by, **kwargs):
88
from core.feature_flags import flag_set
99

1010
JWT_ACCESS_TOKEN_ENABLED = flag_set('fflag__feature_develop__prompts__dia_1829_jwt_token_auth')
1111

1212
with transaction.atomic():
13-
org = Organization.objects.create(title=title, created_by=created_by)
13+
org = Organization.objects.create(title=title, created_by=created_by, **kwargs)
1414
OrganizationMember.objects.create(user=created_by, organization=org)
1515
if JWT_ACCESS_TOKEN_ENABLED:
1616
# set auth tokens to new system for new users

label_studio/organizations/models.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ def __str__(self):
9999
return self.title + ', id=' + str(self.pk)
100100

101101
@classmethod
102-
def create_organization(cls, created_by=None, title='Your Organization'):
102+
def create_organization(cls, created_by=None, title='Your Organization', **kwargs):
103103
_create_organization = load_func(settings.CREATE_ORGANIZATION)
104-
return _create_organization(title=title, created_by=created_by)
104+
return _create_organization(title=title, created_by=created_by, **kwargs)
105105

106106
@classmethod
107107
def find_by_user(cls, user, check_deleted=False):

label_studio/organizations/tests/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import factory
2+
from organizations.models import Organization
3+
4+
5+
class OrganizationFactory(factory.django.DjangoModelFactory):
6+
title = factory.Faker('company')
7+
created_by = factory.SubFactory('users.tests.factories.UserFactory', active_organization=None)
8+
9+
class Meta:
10+
model = Organization
11+
12+
@classmethod
13+
def _create(cls, model_class, *args, **kwargs):
14+
return Organization.create_organization(**kwargs)
15+
16+
@factory.post_generation
17+
def created_by_active_organization(self, create, extracted, **kwargs):
18+
if not create or not self.created_by:
19+
return
20+
self.created_by.active_organization = self
21+
self.created_by.save(update_fields=['active_organization'])

label_studio/projects/tests/__init__.py

Whitespace-only changes.
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import factory
2+
from projects.models import Project, ProjectMember
3+
4+
5+
class ProjectFactory(factory.django.DjangoModelFactory):
6+
title = factory.Faker('bs')
7+
description = factory.Faker('paragraph')
8+
organization = factory.SubFactory('organizations.tests.factories.OrganizationFactory')
9+
created_by = factory.SelfAttribute('organization.created_by')
10+
11+
class Meta:
12+
model = Project
13+
14+
@factory.post_generation
15+
def created_by_relationship(self, create, extracted, **kwargs):
16+
if not create or not self.created_by:
17+
return
18+
ProjectMember.objects.create(user=self.created_by, project=self)
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from django.test import TestCase
2+
from django.urls import reverse
3+
from django.utils.http import urlencode
4+
from rest_framework.test import APIClient
5+
from tasks.models import Task
6+
7+
from .factories import ProjectFactory
8+
9+
10+
class TestProjectCountsListAPI(TestCase):
11+
@classmethod
12+
def setUpTestData(cls):
13+
cls.project_1 = ProjectFactory()
14+
cls.project_2 = ProjectFactory(organization=cls.project_1.organization)
15+
Task.objects.create(project=cls.project_1, data={'text': 'Task 1'})
16+
Task.objects.create(project=cls.project_1, data={'text': 'Task 2'})
17+
Task.objects.create(project=cls.project_2, data={'text': 'Task 3'})
18+
19+
def get_url(self, **params):
20+
return f'{reverse("projects:api:project-counts-list")}?{urlencode(params)}'
21+
22+
def test_get_counts(self):
23+
client = APIClient()
24+
client.force_authenticate(user=self.project_1.created_by)
25+
response = client.get(self.get_url(include='id,task_number,finished_task_number,total_predictions_number'))
26+
self.assertEqual(response.status_code, 200)
27+
self.assertEqual(response.json()['count'], 2)
28+
self.assertEqual(
29+
response.json()['results'],
30+
[
31+
{
32+
'id': self.project_1.id,
33+
'task_number': 2,
34+
'finished_task_number': 0,
35+
'total_predictions_number': 0,
36+
},
37+
{
38+
'id': self.project_2.id,
39+
'task_number': 1,
40+
'finished_task_number': 0,
41+
'total_predictions_number': 0,
42+
},
43+
],
44+
)

label_studio/tests/test_project.py

-49
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@
22

33
import pytest
44
from django.db.models.query import QuerySet
5-
from django.test import TestCase
6-
from django.urls import reverse
7-
from django.utils.http import urlencode
8-
from organizations.models import Organization
9-
from projects.models import Project
10-
from rest_framework.test import APIClient
11-
from tasks.models import Task
125
from tests.utils import make_project
136
from users.models import User
147

@@ -47,45 +40,3 @@ def test_project_all_members(business_client):
4740

4841
assert isinstance(members, QuerySet)
4942
assert isinstance(members.first(), User)
50-
51-
52-
class TestProjectCountsListAPI(TestCase):
53-
@classmethod
54-
def setUpTestData(cls):
55-
cls.user = User.objects.create_user(username='testuser', email='[email protected]', password='testpassword')
56-
cls.organization = Organization.objects.create(title='testorganization')
57-
cls.user.active_organization = cls.organization
58-
cls.user.save()
59-
cls.project_1 = Project.objects.create(title='Project 1', organization=cls.organization)
60-
cls.project_2 = Project.objects.create(title='Project 2', organization=cls.organization)
61-
Task.objects.create(project=cls.project_1, data={'text': 'Task 1'})
62-
Task.objects.create(project=cls.project_1, data={'text': 'Task 2'})
63-
Task.objects.create(project=cls.project_2, data={'text': 'Task 3'})
64-
65-
def get_url(self, **params):
66-
return f'{reverse("projects:api:project-counts-list")}?{urlencode(params)}'
67-
68-
def test_get_counts(self):
69-
70-
client = APIClient()
71-
client.force_authenticate(user=self.user)
72-
response = client.get(self.get_url(include='id,task_number,finished_task_number,total_predictions_number'))
73-
self.assertEqual(response.status_code, 200)
74-
self.assertEqual(response.json()['count'], 2)
75-
self.assertEqual(
76-
response.json()['results'],
77-
[
78-
{
79-
'id': self.project_1.id,
80-
'task_number': 2,
81-
'finished_task_number': 0,
82-
'total_predictions_number': 0,
83-
},
84-
{
85-
'id': self.project_2.id,
86-
'task_number': 1,
87-
'finished_task_number': 0,
88-
'total_predictions_number': 0,
89-
},
90-
],
91-
)

label_studio/users/tests/__init__.py

Whitespace-only changes.

label_studio/users/tests/factories.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import factory
2+
from organizations.models import OrganizationMember
3+
from users.models import User
4+
5+
6+
class UserFactory(factory.django.DjangoModelFactory):
7+
email = factory.Faker('email')
8+
first_name = factory.Faker('first_name')
9+
last_name = factory.Faker('last_name')
10+
username = factory.LazyAttribute(lambda u: u.email.split('@')[0])
11+
password = factory.Faker('password')
12+
13+
class Meta:
14+
model = User
15+
16+
@factory.post_generation
17+
def active_organization(self, create, extracted, **kwargs):
18+
if not create or not extracted:
19+
return
20+
self.active_organization = extracted
21+
self.save(update_fields=['active_organization'])
22+
OrganizationMember.objects.create(user=self, organization=extracted)

poetry.lock

+23-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ s3transfer = "0.7.0"
228228
mock = ">=5.1.0"
229229
moto = ">=4.2.6"
230230
requests-mock = "1.12.1"
231+
factory-boy = "^3.3.3"
231232

232233
[tool.poetry.group.build.dependencies]
233234
twine = ">=6.1.0"

0 commit comments

Comments
 (0)