Skip to content

Commit ad28baa

Browse files
committed
React added, logbooks in progress (all model, backend, and frontend)
- React added as a separate container - Homepage, Navbar, Login rewrite in react (including auth) - Logbook pages initialized (react) - API logbooks (only which was needed in react pages) - rename of Dockerfiles (because are multiple)
1 parent a3a3f24 commit ad28baa

37 files changed

Lines changed: 4694 additions & 16 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Generated by Django 6.0 on 2026-01-05 17:07
2+
3+
import DOSPORTAL.models
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('DOSPORTAL', '0035_record_time_internal_start'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='record',
16+
name='data_file',
17+
field=models.FileField(blank=True, help_text='Processed spectral file', null=True, upload_to=DOSPORTAL.models.Record.user_directory_path_data, validators=[DOSPORTAL.models._validate_data_file], verbose_name='Log file'),
18+
),
19+
migrations.AlterField(
20+
model_name='record',
21+
name='log_file',
22+
field=models.FileField(blank=True, help_text='Upload recorded data file form your detector', upload_to=DOSPORTAL.models.Record.user_directory_path, validators=[DOSPORTAL.models._validate_log_file], verbose_name='File log'),
23+
),
24+
migrations.AlterField(
25+
model_name='record',
26+
name='metadata_file',
27+
field=models.FileField(blank=True, help_text='Processed metadata file', null=True, upload_to=DOSPORTAL.models.Record.user_directory_path_data, validators=[DOSPORTAL.models._validate_metadata_file], verbose_name='Metadata file'),
28+
),
29+
]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 6.0 on 2026-01-05 17:40
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('DOSPORTAL', '0036_alter_record_data_file_alter_record_log_file_and_more'),
11+
]
12+
13+
operations = [
14+
migrations.AlterModelOptions(
15+
name='detectorlogbook',
16+
options={'ordering': ['-created']},
17+
),
18+
migrations.RemoveField(
19+
model_name='record',
20+
name='data_policy',
21+
),
22+
migrations.AddField(
23+
model_name='detectorlogbook',
24+
name='entry_type',
25+
field=models.CharField(choices=[('reset', 'Reset'), ('sync', 'Sync'), ('maintenance', 'Maintenance'), ('note', 'Note'), ('location_update', 'Location update'), ('calibration', 'Calibration'), ('other', 'Other')], default='note', help_text='Category of the logbook entry.', max_length=30),
26+
),
27+
migrations.AddField(
28+
model_name='detectorlogbook',
29+
name='source',
30+
field=models.CharField(choices=[('web', 'Web'), ('api', 'API'), ('qr', 'QR'), ('auto', 'Automatic'), ('other', 'Other')], default='web', help_text='Origin of the logbook entry.', max_length=20),
31+
),
32+
migrations.AlterField(
33+
model_name='record',
34+
name='belongs',
35+
field=models.ForeignKey(choices=[('PR', 'Private'), ('PU', 'Public'), ('NV', 'Non-public')], default='PU', help_text='Data policy of this record. Field can be overridden depending by setting of the organisation, that owns this record.', max_length=2, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='records_owning', to='DOSPORTAL.organization'),
36+
),
37+
]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 6.0 on 2026-01-06 21:28
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('DOSPORTAL', '0037_alter_detectorlogbook_options_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='detectorlogbook',
15+
name='altitude',
16+
field=models.FloatField(blank=True, help_text='Altitude of the location in meters', null=True, verbose_name='Altitude'),
17+
),
18+
migrations.AddField(
19+
model_name='detectorlogbook',
20+
name='latitude',
21+
field=models.FloatField(blank=True, help_text='GPS latitude of the location', null=True, verbose_name='Latitude'),
22+
),
23+
migrations.AddField(
24+
model_name='detectorlogbook',
25+
name='longitude',
26+
field=models.FloatField(blank=True, help_text='GPS longitude of the location', null=True, verbose_name='Longitude'),
27+
),
28+
]

DOSPORTAL/settings.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@
3030

3131
ALLOWED_HOSTS = ["*", "localhost", "127.0.0.1", "0.0.0.0"]
3232

33-
CSRF_TRUSTED_ORIGINS = ["https://portal.dos.ust.cz", "https://eurados-demo.dos.ust.cz"]
33+
CSRF_TRUSTED_ORIGINS = [
34+
"https://portal.dos.ust.cz",
35+
"https://eurados-demo.dos.ust.cz",
36+
# Local dev (Vite)
37+
"http://localhost:5173",
38+
"http://127.0.0.1:5173",
39+
"http://frontend:5173",
40+
]
3441

3542
# Application definition
3643

@@ -70,14 +77,13 @@
7077

7178
MIDDLEWARE = [
7279
"django.middleware.security.SecurityMiddleware",
80+
"corsheaders.middleware.CorsMiddleware",
7381
"django.contrib.sessions.middleware.SessionMiddleware",
7482
"django.middleware.common.CommonMiddleware",
7583
"django.middleware.csrf.CsrfViewMiddleware",
7684
"django.contrib.auth.middleware.AuthenticationMiddleware",
7785
"django.contrib.messages.middleware.MessageMiddleware",
7886
"django.middleware.clickjacking.XFrameOptionsMiddleware",
79-
"corsheaders.middleware.CorsMiddleware",
80-
"django.middleware.common.CommonMiddleware",
8187
]
8288

8389
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
@@ -164,7 +170,20 @@
164170
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
165171

166172

167-
# CORS_ORIGIN_ALLOW_ALL = True
173+
# CORS Configuration - React frontend
174+
CORS_ORIGIN_ALLOW_ALL = False
175+
CORS_ALLOWED_ORIGINS = [
176+
"http://localhost:3000",
177+
"http://127.0.0.1:3000",
178+
"http://frontend:3000",
179+
"http://0.0.0.0:3000",
180+
"http://localhost:5173",
181+
"http://127.0.0.1:5173",
182+
"http://frontend:5173",
183+
"http://0.0.0.0:5173",
184+
]
185+
186+
CORS_ALLOW_CREDENTIALS = True
168187

169188
STATICFILES_DIRS = [
170189
Path(__file__).resolve().parent / "static",

api/serializers.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,46 @@
11
from rest_framework import serializers
2-
from DOSPORTAL.models import measurement, Record, Detector, DetectorLogbook
2+
from DOSPORTAL.models import (
3+
measurement,
4+
Record,
5+
Detector,
6+
DetectorLogbook,
7+
DetectorType,
8+
DetectorManufacturer,
9+
Organization,
10+
User,
11+
)
12+
13+
14+
class DetectorManufacturerSerializer(serializers.ModelSerializer):
15+
class Meta:
16+
model = DetectorManufacturer
17+
fields = ("id", "name", "url")
18+
19+
20+
class DetectorTypeSerializer(serializers.ModelSerializer):
21+
manufacturer = DetectorManufacturerSerializer(read_only=True)
22+
23+
class Meta:
24+
model = DetectorType
25+
fields = ("id", "name", "manufacturer", "url", "description")
26+
27+
28+
class OrganizationSummarySerializer(serializers.ModelSerializer):
29+
class Meta:
30+
model = Organization
31+
fields = ("id", "name", "slug")
32+
33+
34+
class UserSummarySerializer(serializers.ModelSerializer):
35+
class Meta:
36+
model = User
37+
fields = ("id", "username", "first_name", "last_name")
338

439

540
class DetectorSerializer(serializers.ModelSerializer):
41+
type = DetectorTypeSerializer(read_only=True)
42+
owner = OrganizationSummarySerializer(read_only=True)
43+
644
class Meta:
745
model = Detector
846
fields = "__all__"
@@ -17,9 +55,12 @@ class Meta:
1755

1856

1957
class DetectorLogbookSerializer(serializers.ModelSerializer):
58+
author = UserSummarySerializer(read_only=True)
59+
2060
class Meta:
2161
model = DetectorLogbook
2262
fields = "__all__"
63+
read_only_fields = ["id", "author", "created"]
2364

2465

2566
class MeasurementsSerializer(serializers.ModelSerializer):

api/views.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from rest_framework.response import Response
2-
from rest_framework.permissions import AllowAny
2+
from rest_framework.permissions import AllowAny, IsAuthenticated
33
from rest_framework.decorators import api_view, permission_classes
4+
from rest_framework import status
45

56
from django.utils.dateparse import parse_datetime
67
from DOSPORTAL.models import measurement, Record, DetectorLogbook, Detector
@@ -38,15 +39,15 @@ def RecordGet(request):
3839

3940

4041
@api_view(["GET"])
41-
@permission_classes((AllowAny,))
42+
@permission_classes((IsAuthenticated,))
4243
def DetectorGet(request):
43-
items = Detector.objects.all()
44+
items = Detector.objects.select_related("type__manufacturer", "owner").all()
4445
serializer = DetectorSerializer(items, many=True)
4546
return Response(serializer.data)
4647

4748

4849
@api_view(["GET"])
49-
@permission_classes((AllowAny,))
50+
@permission_classes((IsAuthenticated,))
5051
def DetectorLogbookGet(request):
5152
items = DetectorLogbook.objects.select_related("detector", "author").all()
5253

@@ -76,10 +77,32 @@ def DetectorLogbookGet(request):
7677

7778

7879
@api_view(["POST"])
79-
@permission_classes((AllowAny,))
80+
@permission_classes((IsAuthenticated,))
8081
def DetectorLogbookPost(request):
81-
serializer = DetectorLogbookSerializer(data=request.data)
82+
83+
data = dict(request.data)
84+
data["author"] = request.user.id
85+
86+
detector_id = data.get("detector")
87+
if detector_id:
88+
try:
89+
detector = Detector.objects.get(id=detector_id)
90+
user_has_access = (
91+
detector.owner and request.user in detector.owner.users.all()
92+
) or detector.access.filter(users=request.user).exists()
93+
94+
if not user_has_access:
95+
return Response(
96+
{"detail": "Access to the detector denied."},
97+
status=status.HTTP_403_FORBIDDEN,
98+
)
99+
except Detector.DoesNotExist:
100+
return Response(
101+
{"detail": "Detektor not found."}, status=status.HTTP_404_NOT_FOUND
102+
)
103+
104+
serializer = DetectorLogbookSerializer(data=data)
82105
if serializer.is_valid():
83106
serializer.save()
84-
return Response(serializer.data)
85-
return Response(serializer.errors, status=400)
107+
return Response(serializer.data, status=status.HTTP_201_CREATED)
108+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Dockerfile renamed to backend.Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Django backend
2+
13
FROM python:3.13-alpine
24

35
ENV PYTHONDONTWRITEBYTECODE=1

docker-compose.yml

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ services:
3737
inet:
3838
ipv4_address: 10.5.0.5
3939

40-
web:
40+
backend:
4141
build:
4242
context: .
43+
dockerfile: backend.Dockerfile
4344
network: host
4445
# platform: linux/amd64
4546
# command: python manage.py runserver 0.0.0.0:8000
@@ -60,12 +61,14 @@ services:
6061
# default:
6162

6263
worker:
63-
build: .
64+
build:
65+
context: .
66+
dockerfile: backend.Dockerfile
6467
entrypoint: python3 manage.py qcluster
6568
volumes:
6669
- .:/DOSPORTAL
6770
depends_on:
68-
- web
71+
- backend
6972
- db_dosportal
7073
- redis
7174
networks:
@@ -98,3 +101,25 @@ services:
98101
networks:
99102
inet:
100103
ipv4_address: 10.5.0.9
104+
105+
frontend:
106+
build:
107+
context: .
108+
dockerfile: frontend.Dockerfile
109+
ports:
110+
- "5173:5173"
111+
volumes:
112+
- ./frontend:/app/frontend
113+
- /app/frontend/node_modules
114+
environment:
115+
- NODE_ENV=development
116+
depends_on:
117+
- backend
118+
networks:
119+
inet:
120+
ipv4_address: 10.5.0.10
121+
healthcheck:
122+
test: ["CMD", "curl", "-f", "http://localhost:5173"]
123+
interval: 10s
124+
timeout: 5s
125+
retries: 5

frontend.Dockerfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Django frontend
2+
3+
FROM node:20-alpine
4+
5+
WORKDIR /app/frontend
6+
7+
COPY frontend/package*.json ./
8+
9+
RUN npm install
10+
11+
COPY frontend/ ./
12+
13+
COPY frontend/entrypoint.sh /usr/local/bin/frontend-entrypoint.sh
14+
RUN chmod +x /usr/local/bin/frontend-entrypoint.sh
15+
16+
EXPOSE 5173
17+
18+
ENTRYPOINT ["/usr/local/bin/frontend-entrypoint.sh"]

frontend/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_API_URL=http://localhost:8100/api

0 commit comments

Comments
 (0)