django-fixture-utils 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_fixture_utils-0.1.0/PKG-INFO +13 -0
- django_fixture_utils-0.1.0/api/__init__.py +0 -0
- django_fixture_utils-0.1.0/api/admin.py +64 -0
- django_fixture_utils-0.1.0/api/apps.py +9 -0
- django_fixture_utils-0.1.0/api/filters.py +13 -0
- django_fixture_utils-0.1.0/api/migrations/0001_initial.py +84 -0
- django_fixture_utils-0.1.0/api/migrations/0002_alter_laundryroom_room_id_and_more.py +30 -0
- django_fixture_utils-0.1.0/api/migrations/__init__.py +0 -0
- django_fixture_utils-0.1.0/api/models.py +139 -0
- django_fixture_utils-0.1.0/api/serializers.py +94 -0
- django_fixture_utils-0.1.0/api/services.py +85 -0
- django_fixture_utils-0.1.0/api/signals.py +19 -0
- django_fixture_utils-0.1.0/api/tests.py +154 -0
- django_fixture_utils-0.1.0/api/urls.py +20 -0
- django_fixture_utils-0.1.0/api/views.py +199 -0
- django_fixture_utils-0.1.0/config/__init__.py +0 -0
- django_fixture_utils-0.1.0/config/asgi.py +16 -0
- django_fixture_utils-0.1.0/config/exceptions.py +41 -0
- django_fixture_utils-0.1.0/config/logging_utils.py +8 -0
- django_fixture_utils-0.1.0/config/permissions.py +21 -0
- django_fixture_utils-0.1.0/config/response.py +8 -0
- django_fixture_utils-0.1.0/config/settings/__init__.py +0 -0
- django_fixture_utils-0.1.0/config/settings/base.py +134 -0
- django_fixture_utils-0.1.0/config/settings/dev.py +3 -0
- django_fixture_utils-0.1.0/config/settings/prod.py +3 -0
- django_fixture_utils-0.1.0/config/urls.py +33 -0
- django_fixture_utils-0.1.0/config/wsgi.py +16 -0
- django_fixture_utils-0.1.0/django_fixture_utils.egg-info/PKG-INFO +13 -0
- django_fixture_utils-0.1.0/django_fixture_utils.egg-info/SOURCES.txt +45 -0
- django_fixture_utils-0.1.0/django_fixture_utils.egg-info/dependency_links.txt +1 -0
- django_fixture_utils-0.1.0/django_fixture_utils.egg-info/requires.txt +7 -0
- django_fixture_utils-0.1.0/django_fixture_utils.egg-info/top_level.txt +4 -0
- django_fixture_utils-0.1.0/manage.py +22 -0
- django_fixture_utils-0.1.0/pyproject.toml +59 -0
- django_fixture_utils-0.1.0/setup.cfg +4 -0
- django_fixture_utils-0.1.0/users/__init__.py +0 -0
- django_fixture_utils-0.1.0/users/admin.py +6 -0
- django_fixture_utils-0.1.0/users/apps.py +5 -0
- django_fixture_utils-0.1.0/users/authentication.py +5 -0
- django_fixture_utils-0.1.0/users/migrations/0001_initial.py +39 -0
- django_fixture_utils-0.1.0/users/migrations/0002_user_created_at.py +20 -0
- django_fixture_utils-0.1.0/users/migrations/__init__.py +0 -0
- django_fixture_utils-0.1.0/users/models.py +38 -0
- django_fixture_utils-0.1.0/users/serializers.py +26 -0
- django_fixture_utils-0.1.0/users/tests.py +40 -0
- django_fixture_utils-0.1.0/users/urls.py +8 -0
- django_fixture_utils-0.1.0/users/views.py +57 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-fixture-utils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Utilities for Django fixtures, tests and database setup
|
|
5
|
+
Author: Student
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: Django<6,>=5
|
|
8
|
+
Requires-Dist: djangorestframework<4,>=3.15
|
|
9
|
+
Requires-Dist: django-filter<26,>=24
|
|
10
|
+
Requires-Dist: psycopg2-binary<3,>=2.9
|
|
11
|
+
Requires-Dist: python-dotenv<2,>=1
|
|
12
|
+
Requires-Dist: pytest<9,>=8
|
|
13
|
+
Requires-Dist: pytest-django<5,>=4
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from .models import Location, LaundryRoom, WashingMachine, WashOrder
|
|
3
|
+
from .signals import sync_room
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MachineInline(admin.TabularInline):
|
|
7
|
+
model = WashingMachine
|
|
8
|
+
extra = 0
|
|
9
|
+
readonly_fields = ("machine_id", "status_changed_at")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@admin.register(LaundryRoom)
|
|
13
|
+
class LaundryRoomAdmin(admin.ModelAdmin):
|
|
14
|
+
list_display = ("name", "location", "number_free_machines", "created_at")
|
|
15
|
+
list_filter = ("location",)
|
|
16
|
+
search_fields = ("name",)
|
|
17
|
+
readonly_fields = ("number_free_machines",)
|
|
18
|
+
inlines = [MachineInline]
|
|
19
|
+
|
|
20
|
+
def save_formset(self, request, form, formset, change):
|
|
21
|
+
super().save_formset(request, form, formset, change)
|
|
22
|
+
for obj in formset.deleted_objects + formset.saved_objects:
|
|
23
|
+
sync_room(obj.room_id)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@admin.register(WashingMachine)
|
|
27
|
+
class WashingMachineAdmin(admin.ModelAdmin):
|
|
28
|
+
list_display = (
|
|
29
|
+
"machine_id",
|
|
30
|
+
"get_status_display",
|
|
31
|
+
"max_load_kg",
|
|
32
|
+
"room",
|
|
33
|
+
"building",
|
|
34
|
+
"status_changed_at",
|
|
35
|
+
)
|
|
36
|
+
list_filter = ("status", "room")
|
|
37
|
+
readonly_fields = ("status_changed_at",)
|
|
38
|
+
actions = ["to_service"]
|
|
39
|
+
|
|
40
|
+
@admin.action(description="В тех. обслуживание")
|
|
41
|
+
def to_service(self, request, queryset):
|
|
42
|
+
rooms = set(queryset.values_list("room_id", flat=True))
|
|
43
|
+
queryset.update(status="2")
|
|
44
|
+
for r_id in rooms:
|
|
45
|
+
sync_room(r_id)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@admin.register(WashOrder)
|
|
49
|
+
class WashOrderAdmin(admin.ModelAdmin):
|
|
50
|
+
list_display = (
|
|
51
|
+
"title",
|
|
52
|
+
"get_status_display",
|
|
53
|
+
"laundry_weight",
|
|
54
|
+
"user",
|
|
55
|
+
"machine",
|
|
56
|
+
"building",
|
|
57
|
+
"created_at",
|
|
58
|
+
)
|
|
59
|
+
list_filter = ("status", "building")
|
|
60
|
+
search_fields = ("title", "user__email")
|
|
61
|
+
date_hierarchy = "created_at"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
admin.site.register(Location)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import django_filters
|
|
2
|
+
|
|
3
|
+
from .models import WashingMachine
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WashingMachineFilter(django_filters.FilterSet):
|
|
7
|
+
status = django_filters.CharFilter(field_name="status")
|
|
8
|
+
room_id = django_filters.UUIDFilter(field_name="room_id")
|
|
9
|
+
min_load = django_filters.NumberFilter(field_name="max_load_kg", lookup_expr="gte")
|
|
10
|
+
|
|
11
|
+
class Meta:
|
|
12
|
+
model = WashingMachine
|
|
13
|
+
fields = ["status", "room_id", "min_load"]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Generated by Django 6.0.6 on 2026-06-30 07:14
|
|
2
|
+
|
|
3
|
+
import django.core.validators
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
import uuid
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.db import migrations, models
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Migration(migrations.Migration):
|
|
11
|
+
|
|
12
|
+
initial = True
|
|
13
|
+
|
|
14
|
+
dependencies = [
|
|
15
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
operations = [
|
|
19
|
+
migrations.CreateModel(
|
|
20
|
+
name='Location',
|
|
21
|
+
fields=[
|
|
22
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
23
|
+
('title', models.CharField(db_index=True, max_length=125, unique=True)),
|
|
24
|
+
('floor_count', models.IntegerField(validators=[django.core.validators.MinValueValidator(0)])),
|
|
25
|
+
('address', models.CharField(max_length=125)),
|
|
26
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
27
|
+
],
|
|
28
|
+
),
|
|
29
|
+
migrations.CreateModel(
|
|
30
|
+
name='LaundryRoom',
|
|
31
|
+
fields=[
|
|
32
|
+
('room_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
33
|
+
('name', models.CharField(max_length=125, unique=True)),
|
|
34
|
+
('number_free_machines', models.IntegerField(db_index=True, default=0, validators=[django.core.validators.MinValueValidator(0)])),
|
|
35
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
36
|
+
('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.location')),
|
|
37
|
+
],
|
|
38
|
+
),
|
|
39
|
+
migrations.CreateModel(
|
|
40
|
+
name='WashingMachine',
|
|
41
|
+
fields=[
|
|
42
|
+
('machine_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
43
|
+
('max_load_kg', models.IntegerField(validators=[django.core.validators.MinValueValidator(0)])),
|
|
44
|
+
('status', models.CharField(choices=[('0', 'Свободна'), ('1', 'Стирает'), ('2', 'Тех. обслуживание')], db_index=True, default='0', max_length=1)),
|
|
45
|
+
('status_changed_at', models.DateTimeField(auto_now=True)),
|
|
46
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
47
|
+
('building', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='machine_buildings', to='api.location')),
|
|
48
|
+
('pickup_point', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='machine_pickup_points', to='api.location')),
|
|
49
|
+
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.laundryroom')),
|
|
50
|
+
],
|
|
51
|
+
),
|
|
52
|
+
migrations.CreateModel(
|
|
53
|
+
name='WashOrder',
|
|
54
|
+
fields=[
|
|
55
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
56
|
+
('title', models.CharField(max_length=125, unique=True)),
|
|
57
|
+
('laundry_weight', models.IntegerField(validators=[django.core.validators.MinValueValidator(0)])),
|
|
58
|
+
('idempotency_key', models.CharField(blank=True, max_length=256, null=True, unique=True)),
|
|
59
|
+
('status', models.CharField(choices=[('0', 'created'), ('1', 'assigned'), ('2', 'completed'), ('3', 'cancelled')], default='0', max_length=1)),
|
|
60
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
61
|
+
('building', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='order_buildings', to='api.location')),
|
|
62
|
+
('machine', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_machines', to='api.washingmachine')),
|
|
63
|
+
('pickup_point', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='order_pickup_points', to='api.location')),
|
|
64
|
+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
65
|
+
],
|
|
66
|
+
),
|
|
67
|
+
migrations.AddField(
|
|
68
|
+
model_name='washingmachine',
|
|
69
|
+
name='order',
|
|
70
|
+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='machine_orders', to='api.washorder'),
|
|
71
|
+
),
|
|
72
|
+
migrations.AddIndex(
|
|
73
|
+
model_name='laundryroom',
|
|
74
|
+
index=models.Index(fields=['location', 'name'], name='room_location_name_idx'),
|
|
75
|
+
),
|
|
76
|
+
migrations.AddIndex(
|
|
77
|
+
model_name='washorder',
|
|
78
|
+
index=models.Index(fields=['status', 'created_at'], name='order_status_created_idx'),
|
|
79
|
+
),
|
|
80
|
+
migrations.AddIndex(
|
|
81
|
+
model_name='washingmachine',
|
|
82
|
+
index=models.Index(fields=['room', 'status'], name='machine_room_status_idx'),
|
|
83
|
+
),
|
|
84
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Generated by Django 6.0.6 on 2026-06-30 17:04
|
|
2
|
+
|
|
3
|
+
import django.core.validators
|
|
4
|
+
import uuid
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
('api', '0001_initial'),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.AlterField(
|
|
16
|
+
model_name='laundryroom',
|
|
17
|
+
name='room_id',
|
|
18
|
+
field=models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False),
|
|
19
|
+
),
|
|
20
|
+
migrations.AlterField(
|
|
21
|
+
model_name='washingmachine',
|
|
22
|
+
name='max_load_kg',
|
|
23
|
+
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1)]),
|
|
24
|
+
),
|
|
25
|
+
migrations.AlterField(
|
|
26
|
+
model_name='washorder',
|
|
27
|
+
name='laundry_weight',
|
|
28
|
+
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1)]),
|
|
29
|
+
),
|
|
30
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from django.core.validators import MinValueValidator
|
|
3
|
+
from django.contrib.auth import get_user_model
|
|
4
|
+
from rest_framework.exceptions import ValidationError
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
User = get_user_model()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Location(models.Model):
|
|
11
|
+
title = models.CharField(max_length=125, unique=True, db_index=True)
|
|
12
|
+
floor_count = models.IntegerField(validators=[MinValueValidator(0)])
|
|
13
|
+
address = models.CharField(max_length=125)
|
|
14
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
15
|
+
|
|
16
|
+
def __str__(self):
|
|
17
|
+
return self.title
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LaundryRoom(models.Model):
|
|
21
|
+
room_id = models.UUIDField(default=uuid4, primary_key=True)
|
|
22
|
+
name = models.CharField(max_length=125, unique=True)
|
|
23
|
+
location = models.ForeignKey(Location, on_delete=models.CASCADE)
|
|
24
|
+
number_free_machines = models.IntegerField(
|
|
25
|
+
default=0, validators=[MinValueValidator(0)], db_index=True
|
|
26
|
+
)
|
|
27
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
28
|
+
|
|
29
|
+
class Meta:
|
|
30
|
+
indexes = [
|
|
31
|
+
models.Index(fields=["location", "name"], name="room_location_name_idx")
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
def __str__(self):
|
|
35
|
+
return self.name
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class WashingMachine(models.Model):
|
|
39
|
+
class STATUS(models.TextChoices):
|
|
40
|
+
FREE = "0", "Свободна"
|
|
41
|
+
LAUNDRY = "1", "Стирает"
|
|
42
|
+
SERVICE = "2", "Тех. обслуживание"
|
|
43
|
+
|
|
44
|
+
machine_id = models.UUIDField(default=uuid4, primary_key=True, editable=False)
|
|
45
|
+
max_load_kg = models.IntegerField(validators=[MinValueValidator(1)])
|
|
46
|
+
status = models.CharField(
|
|
47
|
+
max_length=1, choices=STATUS.choices, default=STATUS.FREE, db_index=True
|
|
48
|
+
)
|
|
49
|
+
order = models.ForeignKey(
|
|
50
|
+
"WashOrder",
|
|
51
|
+
null=True,
|
|
52
|
+
blank=True,
|
|
53
|
+
on_delete=models.SET_NULL,
|
|
54
|
+
related_name="machine_orders",
|
|
55
|
+
)
|
|
56
|
+
room = models.ForeignKey(LaundryRoom, on_delete=models.CASCADE, db_index=True)
|
|
57
|
+
building = models.ForeignKey(
|
|
58
|
+
Location, on_delete=models.CASCADE, related_name="machine_buildings"
|
|
59
|
+
)
|
|
60
|
+
pickup_point = models.ForeignKey(
|
|
61
|
+
Location, on_delete=models.CASCADE, related_name="machine_pickup_points"
|
|
62
|
+
)
|
|
63
|
+
status_changed_at = models.DateTimeField(auto_now=True)
|
|
64
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
65
|
+
|
|
66
|
+
class Meta:
|
|
67
|
+
indexes = [
|
|
68
|
+
models.Index(fields=["room", "status"], name="machine_room_status_idx")
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
def _set_status(self, new_status, label):
|
|
72
|
+
if self.status == new_status:
|
|
73
|
+
raise ValidationError(f"Статус уже '{label}'")
|
|
74
|
+
self.status = new_status
|
|
75
|
+
self.save(update_fields=["status", "status_changed_at"])
|
|
76
|
+
|
|
77
|
+
def mark_washing(self):
|
|
78
|
+
self._set_status(self.STATUS.LAUNDRY, "Стирает")
|
|
79
|
+
|
|
80
|
+
def mark_free(self):
|
|
81
|
+
self._set_status(self.STATUS.FREE, "Свободна")
|
|
82
|
+
|
|
83
|
+
if self.order_id:
|
|
84
|
+
WashOrder.objects.filter(pk=self.order_id).update(
|
|
85
|
+
status=WashOrder.STATUS.COMPLETED
|
|
86
|
+
)
|
|
87
|
+
self.order = None
|
|
88
|
+
self.save(update_fields=["order"])
|
|
89
|
+
|
|
90
|
+
def mark_service(self):
|
|
91
|
+
self._set_status(self.STATUS.SERVICE, "Тех. обслуживание")
|
|
92
|
+
|
|
93
|
+
def can_take_laundry(self, weight: int) -> bool:
|
|
94
|
+
return self.max_load_kg >= weight
|
|
95
|
+
|
|
96
|
+
def __str__(self):
|
|
97
|
+
return str(self.machine_id)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class WashOrder(models.Model):
|
|
101
|
+
class STATUS(models.TextChoices):
|
|
102
|
+
CREATED = "0", "created"
|
|
103
|
+
ASSIGNED = "1", "assigned"
|
|
104
|
+
COMPLETED = "2", "completed"
|
|
105
|
+
CANCELLED = "3", "cancelled"
|
|
106
|
+
|
|
107
|
+
title = models.CharField(max_length=125, unique=True)
|
|
108
|
+
laundry_weight = models.IntegerField(validators=[MinValueValidator(1)])
|
|
109
|
+
user = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
|
|
110
|
+
machine = models.ForeignKey(
|
|
111
|
+
WashingMachine,
|
|
112
|
+
on_delete=models.SET_NULL,
|
|
113
|
+
null=True,
|
|
114
|
+
blank=True,
|
|
115
|
+
related_name="order_machines",
|
|
116
|
+
)
|
|
117
|
+
building = models.ForeignKey(
|
|
118
|
+
Location, on_delete=models.CASCADE, related_name="order_buildings"
|
|
119
|
+
)
|
|
120
|
+
pickup_point = models.ForeignKey(
|
|
121
|
+
Location, on_delete=models.CASCADE, related_name="order_pickup_points"
|
|
122
|
+
)
|
|
123
|
+
idempotency_key = models.CharField(
|
|
124
|
+
max_length=256, unique=True, null=True, blank=True
|
|
125
|
+
)
|
|
126
|
+
status = models.CharField(
|
|
127
|
+
max_length=1, choices=STATUS.choices, default=STATUS.CREATED
|
|
128
|
+
)
|
|
129
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
130
|
+
|
|
131
|
+
class Meta:
|
|
132
|
+
indexes = [
|
|
133
|
+
models.Index(
|
|
134
|
+
fields=["status", "created_at"], name="order_status_created_idx"
|
|
135
|
+
)
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
def __str__(self):
|
|
139
|
+
return self.title
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
from .models import Location, LaundryRoom, WashingMachine, WashOrder
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def validate_locations(building, pickup_point):
|
|
6
|
+
if building and pickup_point and building.pk == pickup_point.pk:
|
|
7
|
+
raise serializers.ValidationError("building and pickup_point must differ")
|
|
8
|
+
|
|
9
|
+
def validate_id(building_id, pickup_point_id):
|
|
10
|
+
if building_id == pickup_point_id:
|
|
11
|
+
raise serializers.ValidationError("building and pickup_point must differ")
|
|
12
|
+
return True
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LocationSerializer(serializers.ModelSerializer):
|
|
16
|
+
class Meta:
|
|
17
|
+
model = Location
|
|
18
|
+
fields = "__all__"
|
|
19
|
+
read_only_fields = ["id", "created_at"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LaundryRoomSerializer(serializers.ModelSerializer):
|
|
23
|
+
class Meta:
|
|
24
|
+
model = LaundryRoom
|
|
25
|
+
fields = "__all__"
|
|
26
|
+
read_only_fields = ["room_id", "number_free_machines", "created_at"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WashingMachineSerializer(serializers.ModelSerializer):
|
|
30
|
+
status_label = serializers.CharField(source="get_status_display", read_only=True)
|
|
31
|
+
|
|
32
|
+
class Meta:
|
|
33
|
+
model = WashingMachine
|
|
34
|
+
fields = "__all__"
|
|
35
|
+
read_only_fields = ["machine_id", "status", "status_changed_at", "created_at"]
|
|
36
|
+
|
|
37
|
+
def validate(self, attrs):
|
|
38
|
+
building = attrs.get("building") or getattr(self.instance, "building", None)
|
|
39
|
+
pickup = attrs.get("pickup_point") or getattr(
|
|
40
|
+
self.instance, "pickup_point", None
|
|
41
|
+
)
|
|
42
|
+
validate_locations(building, pickup)
|
|
43
|
+
return attrs
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class WashOrderSerializer(serializers.ModelSerializer):
|
|
47
|
+
building_id = serializers.PrimaryKeyRelatedField(
|
|
48
|
+
queryset=Location.objects.all(), source="building", write_only=True
|
|
49
|
+
)
|
|
50
|
+
pickup_point_id = serializers.PrimaryKeyRelatedField(
|
|
51
|
+
queryset=Location.objects.all(), source="pickup_point", write_only=True
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
class Meta:
|
|
55
|
+
model = WashOrder
|
|
56
|
+
fields = "__all__"
|
|
57
|
+
read_only_fields = [
|
|
58
|
+
"id",
|
|
59
|
+
"user",
|
|
60
|
+
"machine",
|
|
61
|
+
"building",
|
|
62
|
+
"pickup_point",
|
|
63
|
+
"idempotency_key",
|
|
64
|
+
"status",
|
|
65
|
+
"created_at",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
def validate(self, attrs):
|
|
69
|
+
validate_locations(attrs.get("building"), attrs.get("pickup_point"))
|
|
70
|
+
weight = attrs.get("laundry_weight")
|
|
71
|
+
if weight is not None and weight <= 0:
|
|
72
|
+
raise serializers.ValidationError(
|
|
73
|
+
{"laundry_weight": ["Must be greater than 0"]}
|
|
74
|
+
)
|
|
75
|
+
return attrs
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class AllocateSerializer(serializers.Serializer):
|
|
79
|
+
laundry_weight = serializers.IntegerField(min_value=1)
|
|
80
|
+
building_id = serializers.IntegerField()
|
|
81
|
+
pickup_point_id = serializers.IntegerField()
|
|
82
|
+
|
|
83
|
+
def validate(self, attrs):
|
|
84
|
+
validate_id(attrs["building_id"], attrs["pickup_point_id"])
|
|
85
|
+
return attrs
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MachineWashingSerializer(serializers.Serializer):
|
|
89
|
+
building_id = serializers.IntegerField()
|
|
90
|
+
pickup_point_id = serializers.IntegerField()
|
|
91
|
+
|
|
92
|
+
def validate(self, attrs):
|
|
93
|
+
validate_id(attrs["building_id"], attrs["pickup_point_id"])
|
|
94
|
+
return attrs
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from django.db import transaction
|
|
2
|
+
from django.shortcuts import get_object_or_404
|
|
3
|
+
from rest_framework.exceptions import ValidationError
|
|
4
|
+
|
|
5
|
+
from config.exceptions import NoFreeMachines
|
|
6
|
+
from config.logging_utils import log_event
|
|
7
|
+
from .models import Location, WashingMachine, WashOrder
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@transaction.atomic
|
|
11
|
+
def allocate_machine(laundry_weight, building_id, pickup_point_id):
|
|
12
|
+
if laundry_weight <= 0:
|
|
13
|
+
raise ValidationError({"laundry_weight": ["Must be greater than 0"]})
|
|
14
|
+
|
|
15
|
+
if building_id == pickup_point_id:
|
|
16
|
+
raise ValidationError(
|
|
17
|
+
{"building_id": ["building and pickup_point must differ"]}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
building = get_object_or_404(Location, pk=building_id)
|
|
21
|
+
pickup_point = get_object_or_404(Location, pk=pickup_point_id)
|
|
22
|
+
|
|
23
|
+
machine = (
|
|
24
|
+
WashingMachine.objects.select_for_update()
|
|
25
|
+
.select_related("room")
|
|
26
|
+
.filter(
|
|
27
|
+
status=WashingMachine.STATUS.FREE,
|
|
28
|
+
max_load_kg__gte=laundry_weight,
|
|
29
|
+
)
|
|
30
|
+
.order_by("-room__number_free_machines", "status_changed_at")
|
|
31
|
+
.first()
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if not machine:
|
|
35
|
+
log_event("allocate_conflict", laundry_weight=laundry_weight)
|
|
36
|
+
raise NoFreeMachines()
|
|
37
|
+
|
|
38
|
+
order = (
|
|
39
|
+
WashOrder.objects.select_for_update()
|
|
40
|
+
.filter(
|
|
41
|
+
status=WashOrder.STATUS.CREATED,
|
|
42
|
+
machine__isnull=True,
|
|
43
|
+
laundry_weight=laundry_weight,
|
|
44
|
+
building_id=building_id,
|
|
45
|
+
pickup_point_id=pickup_point_id,
|
|
46
|
+
)
|
|
47
|
+
.order_by("created_at")
|
|
48
|
+
.first()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
machine.status = WashingMachine.STATUS.LAUNDRY
|
|
52
|
+
machine.building = building
|
|
53
|
+
machine.pickup_point = pickup_point
|
|
54
|
+
|
|
55
|
+
if order:
|
|
56
|
+
machine.order = order
|
|
57
|
+
order.machine = machine
|
|
58
|
+
order.status = WashOrder.STATUS.ASSIGNED
|
|
59
|
+
order.save(update_fields=["machine", "status"])
|
|
60
|
+
machine.save(
|
|
61
|
+
update_fields=[
|
|
62
|
+
"status",
|
|
63
|
+
"status_changed_at",
|
|
64
|
+
"building",
|
|
65
|
+
"pickup_point",
|
|
66
|
+
"order",
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
else:
|
|
70
|
+
machine.save(
|
|
71
|
+
update_fields=[
|
|
72
|
+
"status",
|
|
73
|
+
"status_changed_at",
|
|
74
|
+
"building",
|
|
75
|
+
"pickup_point",
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
log_event(
|
|
80
|
+
"allocate",
|
|
81
|
+
machine_id=str(machine.machine_id),
|
|
82
|
+
order_id=order.id if order else None,
|
|
83
|
+
laundry_weight=laundry_weight,
|
|
84
|
+
)
|
|
85
|
+
return machine, order
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from django.db.models.signals import post_save, post_delete
|
|
2
|
+
from django.dispatch import receiver
|
|
3
|
+
from .models import WashingMachine, LaundryRoom
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sync_room(room_id):
|
|
7
|
+
if room_id:
|
|
8
|
+
cnt = WashingMachine.objects.filter(room_id=room_id, status="0").count()
|
|
9
|
+
LaundryRoom.objects.filter(pk=room_id).update(number_free_machines=cnt)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@receiver(post_save, sender=WashingMachine)
|
|
13
|
+
def machine_save(sender, instance, created, **kwargs):
|
|
14
|
+
sync_room(instance.room_id)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@receiver(post_delete, sender=WashingMachine)
|
|
18
|
+
def machine_del(sender, instance, **kwargs):
|
|
19
|
+
sync_room(instance.room_id)
|