Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
674 changes: 0 additions & 674 deletions LICENSE

This file was deleted.

16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# jpydzr8-just-4-it
# django_app

## Wymagania
- Python 3.10+
- macOS z zainstalowanym `python3` (np. z Homebrew)

## Szybki start (macOS)
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver
```
Aplikacja będzie dostępna pod adresem: http://127.0.0.1:8000/
18 changes: 18 additions & 0 deletions django_app/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# === Django ===
SECRET_KEY=change_me_in_prod
DEBUG=True
ALLOWED_HOSTS=*
CORS_ALLOW_ALL=True
# CORS_ALLOWED_ORIGINS=https://twoja-domena.pl,https://app.twoja-domena.pl

# === JWT lifetimes ===
JWT_ACCESS_MINUTES=30
JWT_REFRESH_DAYS=7

# === Superuser bootstrap (optional) ===
[email protected]
SUPERUSER_PASSWORD=admin123
SUPERUSER_FULL_NAME=Admin

# Host port mapping (host:container)
PORT=8000
23 changes: 23 additions & 0 deletions django_app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1

WORKDIR /app

# System deps
RUN apt-get update && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/*

# Copy requirements first (better layer caching)
COPY coworking/requirements.txt /app/requirements.txt
RUN pip install --upgrade pip && pip install -r /app/requirements.txt

# Copy app
COPY coworking /app

# Entrypoint
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 8000

CMD ["/entrypoint.sh"]
1 change: 1 addition & 0 deletions django_app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

3 changes: 3 additions & 0 deletions django_app/coworking/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Coworking – Django port

Uruchom zgodnie z instrukcjami w wiadomości.
1 change: 1 addition & 0 deletions django_app/coworking/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

19 changes: 19 additions & 0 deletions django_app/coworking/api/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User, Reservation

@admin.register(User)
class UserAdmin(BaseUserAdmin):
model = User
list_display = ('email', 'full_name', 'is_active', 'is_staff', 'is_superuser', 'created_at')
ordering = ('email',)
search_fields = ('email', 'full_name')
fieldsets = ((None, {'fields': ('email', 'password')}), ('Personal info', {'fields': ('full_name',)}), ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), ('Important dates', {'fields': ('last_login', 'created_at')}))
add_fieldsets = ((None, {'classes': ('wide',), 'fields': ('email', 'password1', 'password2', 'is_staff', 'is_superuser')}),)
filter_horizontal = ('groups', 'user_permissions')

@admin.register(Reservation)
class ReservationAdmin(admin.ModelAdmin):
list_display = ('id', 'seat_id', 'date', 'name', 'email', 'created_at')
list_filter = ('date',)
search_fields = ('id', 'seat_id', 'name', 'email')
21 changes: 21 additions & 0 deletions django_app/coworking/api/jwt_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import check_password
User = get_user_model()

class EmailTokenObtainPairSerializer(TokenObtainPairSerializer):
username_field = 'email'

def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
self.error_messages['no_active_account'] = 'Invalid credentials'
raise self.raise_invalid_user()
if not user.is_active or not check_password(password, user.password):
self.error_messages['no_active_account'] = 'Invalid credentials'
raise self.raise_invalid_user()
data = super().validate({'email': email, 'password': password})
return data
1 change: 1 addition & 0 deletions django_app/coworking/api/management/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions django_app/coworking/api/management/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.contrib.auth import get_user_model
from api.models import Reservation
import sqlite3, os
User = get_user_model()

class Command(BaseCommand):
help = 'Import users and reservations from FastAPI SQLite (tables: users, reservations).'

def add_arguments(self, parser):
parser.add_argument('--path', required=True, help='Path to FastAPI SQLite app.db')
parser.add_argument('--skip-users', action='store_true')
parser.add_argument('--skip-reservations', action='store_true')

@transaction.atomic
def handle(self, *args, **opts):
path = opts['path']
if not os.path.exists(path):
raise CommandError(f'SQLite not found: {path}')
con = sqlite3.connect(path)
cur = con.cursor()
if not opts['skip-users']:
try:
cur.execute('SELECT email, password_hash, full_name, is_active, is_superuser FROM users')
rows = cur.fetchall()
for email, password_hash, full_name, is_active, is_superuser in rows:
user, created = User.objects.get_or_create(email=email, defaults={'full_name': full_name or '', 'is_active': bool(is_active), 'is_superuser': bool(is_superuser), 'is_staff': bool(is_superuser)})
if created:
user.set_unusable_password()
user.save()
self.stdout.write(self.style.SUCCESS(f'Imported/ensured {len(rows)} users (passwords set unusable).'))
except Exception as e:
self.stderr.write(str(e))
raise CommandError('Failed to import users')
if not opts['skip-reservations']:
try:
cur.execute('SELECT id, seat_id, date, name, email, notes, created_at FROM reservations')
rows = cur.fetchall()
for rid, seat_id, date, name, email, notes, created_at in rows:
Reservation.objects.get_or_create(id=rid, defaults={'seat_id': seat_id, 'date': date, 'name': name, 'email': email, 'notes': notes or ''})
self.stdout.write(self.style.SUCCESS(f'Imported {len(rows)} reservations.'))
except Exception as e:
self.stderr.write(str(e))
raise CommandError('Failed to import reservations')
con.close()
7 changes: 7 additions & 0 deletions django_app/coworking/api/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import django.utils.timezone
from django.db import migrations, models

class Migration(migrations.Migration):
initial = True
dependencies = [('auth', '0012_alter_user_first_name_max_length')]
operations = [migrations.CreateModel(name='User', fields=[('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('email', models.EmailField(max_length=254, unique=True)), ('full_name', models.CharField(blank=True, default='', max_length=255)), ('is_active', models.BooleanField(default=True)), ('is_staff', models.BooleanField(default=False)), ('created_at', models.DateTimeField(default=django.utils.timezone.now)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'))], options={'abstract': False}), migrations.CreateModel(name='Reservation', fields=[('id', models.CharField(max_length=64, primary_key=True, serialize=False)), ('seat_id', models.CharField(db_index=True, max_length=64)), ('date', models.CharField(db_index=True, max_length=10)), ('name', models.CharField(max_length=255)), ('email', models.CharField(max_length=255)), ('notes', models.CharField(blank=True, default='', max_length=1024)), ('created_at', models.DateTimeField(default=django.utils.timezone.now))], options={'constraints': [models.UniqueConstraint(fields=('seat_id', 'date'), name='uq_resv_seat_date')]})]
1 change: 1 addition & 0 deletions django_app/coworking/api/migrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

47 changes: 47 additions & 0 deletions django_app/coworking/api/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.utils import timezone

class UserManager(BaseUserManager):

def create_user(self, email, password=None, full_name='', **extra):
if not email:
raise ValueError('Email is required')
email = self.normalize_email(email)
user = self.model(email=email, full_name=full_name, is_active=True, **extra)
if password:
user.set_password(password)
else:
user.set_unusable_password()
user.save(using=self._db)
return user

def create_superuser(self, email, password, full_name='', **extra):
extra.setdefault('is_staff', True)
extra.setdefault('is_superuser', True)
return self.create_user(email, password, full_name=full_name, **extra)

class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
full_name = models.CharField(max_length=255, blank=True, default='')
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(default=timezone.now)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()

def __str__(self):
return self.email

class Reservation(models.Model):
id = models.CharField(primary_key=True, max_length=64)
seat_id = models.CharField(max_length=64, db_index=True)
date = models.CharField(max_length=10, db_index=True)
name = models.CharField(max_length=255)
email = models.CharField(max_length=255)
notes = models.CharField(max_length=1024, blank=True, default='')
created_at = models.DateTimeField(default=timezone.now)

class Meta:
constraints = [models.UniqueConstraint(fields=['seat_id', 'date'], name='uq_resv_seat_date')]
6 changes: 6 additions & 0 deletions django_app/coworking/api/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from rest_framework.permissions import BasePermission

class IsSuperUser(BasePermission):

def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated and request.user.is_superuser)
76 changes: 76 additions & 0 deletions django_app/coworking/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from django.db import IntegrityError
from rest_framework import serializers
from .models import Reservation

class ReservationSerializer(serializers.ModelSerializer):
id = serializers.CharField(read_only=True)

class Meta:
model = Reservation
fields = ['id', 'seat_id', 'date', 'name', 'email', 'notes', 'created_at']
read_only_fields = ['id', 'created_at']

def to_internal_value(self, data):
data = dict(data)
if 'seat_id' not in data:
for alt in ('seatId', 'seat', 'reserveSeatId', 'formSeat'):
if alt in data:
data['seat_id'] = data[alt]
break
if 'date' not in data:
for alt in ('reserveDate', 'dateStr', 'formDate'):
if alt in data:
data['date'] = data[alt]
break
import re
d = str(data.get('date') or '').strip()
if d:
m = re.match('^(\\d{4})[-/](\\d{2})[-/](\\d{2})$', d)
if m:
data['date'] = f'{m.group(1)}-{m.group(2)}-{m.group(3)}'
else:
m2 = re.match('^(\\d{2})[./-](\\d{2})[./-](\\d{4})$', d)
if m2:
data['date'] = f'{m2.group(3)}-{m2.group(2)}-{m2.group(1)}'
if 'name' not in data:
for alt in ('fullName', 'formName'):
if alt in data:
data['name'] = data[alt]
break
if 'email' not in data:
for alt in ('mail', 'formEmail'):
if alt in data:
data['email'] = data[alt]
break
if 'notes' not in data and 'formNotes' in data:
data['notes'] = data['formNotes']
return super().to_internal_value(data)

def validate(self, attrs):
date = (attrs.get('date') or '').strip()
if len(date) != 10:
raise serializers.ValidationError({'date': 'Use YYYY-MM-DD format.'})
if not attrs.get('seat_id'):
raise serializers.ValidationError({'seat_id': 'This field is required.'})
req = self.context.get('request')
if not attrs.get('name'):
user_name = ''
if req and getattr(req, 'user', None) and req.user.is_authenticated:
user_name = getattr(req.user, 'get_full_name', lambda: '')() or getattr(req.user, 'username', '') or getattr(req.user, 'email', '')
attrs['name'] = user_name or 'User'
if not attrs.get('email'):
user_email = ''
if req and getattr(req, 'user', None) and req.user.is_authenticated:
user_email = getattr(req.user, 'email', '')
attrs['email'] = user_email or '[email protected]'
return attrs

def create(self, validated_data):
if not validated_data.get('id'):
sid = validated_data.get('seat_id') or ''
dt = validated_data.get('date') or ''
validated_data['id'] = f'{sid}-{dt}'
try:
return super().create(validated_data)
except IntegrityError:
raise serializers.ValidationError({'detail': 'Seat already reserved for this date.'})
15 changes: 15 additions & 0 deletions django_app/coworking/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.urls import path, include, re_path
from django.shortcuts import redirect
from rest_framework.routers import DefaultRouter
from .views_occupied import SeatsOccupiedView
from .views import CsrfTokenView
from .views import CsrfTokenView, LoginView, RegisterView, LogoutView, MeView, ReservationViewSet, ics_reservation, ics_reservation_cancel
from .views import CsrfTokenView, LoginView, RegisterView, LogoutView, MeView
router = DefaultRouter()
router.trailing_slash = '/?'
router.register('reservations', ReservationViewSet, basename='reservation')

def _reservations_redirect(request):
qs = request.META.get('QUERY_STRING') or ''
return redirect('/api/reservations/' + (f'?{qs}' if qs else ''), permanent=False)
urlpatterns = [path('auth/csrf', CsrfTokenView.as_view(), name='csrf'), path('auth/register', RegisterView.as_view(), name='register'), path('auth/login', LoginView.as_view(), name='login'), path('auth/logout', LogoutView.as_view(), name='logout'), path('auth/me', MeView.as_view(), name='me'), path('seats/occupied', SeatsOccupiedView.as_view(), name='seats_occupied'), path('ics/reservations/<str:pk>.ics', ics_reservation, name='ics_reservation'), path('ics/reservations/<str:pk>/cancel.ics', ics_reservation_cancel, name='ics_reservation_cancel'), re_path('^reservations$', _reservations_redirect), path('', include(router.urls))]
Loading