zmiany mariusza

This commit is contained in:
Jakub Kaniecki 2025-08-31 23:05:53 +02:00
commit 0f586b15cb
241 changed files with 13769 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

81
README.md Normal file
View File

@ -0,0 +1,81 @@
# Izaac 2.1 Project
This project consists of a Django backend and a React/TypeScript frontend. Below are instructions for setting up and running both parts in development mode.
## Prerequisites
- Python 3.x
- Node.js (LTS version recommended)
- npm or yarn
- SQLite (included with Python)
## Backend Setup
1. Navigate to the backend directory:
```bash
cd backend
```
2. Create and activate a virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows use: venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
4. Run database migrations:
```bash
python manage.py migrate
```
5. Start the development server:
```bash
python manage.py runserver
```
The backend will be available at `http://localhost:8000`
## Frontend Setup
1. Navigate to the frontend directory:
```bash
cd frontend
```
2. Install dependencies:
```bash
npm install
# or if you use yarn:
yarn install
```
3. Start the development server:
```bash
npm run dev
# or if you use yarn:
yarn dev
```
The frontend will be available at `http://localhost:5173`
## Development
- Backend API documentation is available at `http://localhost:8000/api/docs/` when the server is running
- Frontend hot-reloading is enabled by default
- Backend hot-reloading is enabled by default with Django's development server
## Project Structure
- `/backend` - Django backend application
- `/frontend` - React/TypeScript frontend application
- `/nginx` - Nginx configuration files
## Additional Notes
- Make sure both backend and frontend servers are running simultaneously for full functionality
- The backend uses SQLite as the database by default
- Frontend is built with Vite and uses Tailwind CSS for styling

106
backend.log Normal file
View File

@ -0,0 +1,106 @@
Watching for file changes with StatReloader
[28/May/2025 18:21:16] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:21:16] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:21:18] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:21:18] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:21:22] "OPTIONS /api/users/login/ HTTP/1.1" 200 0
[28/May/2025 18:21:25] "OPTIONS /api/users/login/ HTTP/1.1" 200 0
[28/May/2025 18:21:28] "OPTIONS /api/users/login/ HTTP/1.1" 200 0
[28/May/2025 18:21:31] "OPTIONS /api/users/login/ HTTP/1.1" 200 0
[28/May/2025 18:21:44] "OPTIONS /api/users/login/ HTTP/1.1" 200 0
[28/May/2025 18:24:59] "POST /api/users/refresh/ HTTP/1.1" 200 483
Unauthorized: /api/users/refresh/
[28/May/2025 18:24:59] "POST /api/users/refresh/ HTTP/1.1" 401 58
[28/May/2025 18:24:59] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:24:59] "POST /api/users/refresh/ HTTP/1.1" 200 483
Unauthorized: /api/users/refresh/
[28/May/2025 18:24:59] "POST /api/users/refresh/ HTTP/1.1" 401 58
[28/May/2025 18:25:07] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:25:07] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:25:12] "OPTIONS /api/users/login/ HTTP/1.1" 200 0
[28/May/2025 18:25:12] "POST /api/users/login/ HTTP/1.1" 200 483
[28/May/2025 18:25:13] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:27:05] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:27:05] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:27:05] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:27:05] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:27:15] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:27:15] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:27:46] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:27:46] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:27:46] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:27:46] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:34:05] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:34:05] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:34:05] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:34:05] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:38:05] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:38:05] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:38:05] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:38:05] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:38:08] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:38:08] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:38:08] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:38:08] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:38:08] "POST /api/users/refresh/ HTTP/1.1" 200 483
Unauthorized: /api/users/refresh/
[28/May/2025 18:38:08] "POST /api/users/refresh/ HTTP/1.1" 401 58
[28/May/2025 18:38:08] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:38:08] "POST /api/users/refresh/ HTTP/1.1" 200 483
Unauthorized: /api/users/refresh/
[28/May/2025 18:38:09] "POST /api/users/refresh/ HTTP/1.1" 401 58
[28/May/2025 18:38:15] "POST /api/users/login/ HTTP/1.1" 200 483
[28/May/2025 18:38:15] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:39:29] "POST /api/users/refresh/ HTTP/1.1" 200 483
Unauthorized: /api/users/refresh/
[28/May/2025 18:39:29] "POST /api/users/refresh/ HTTP/1.1" 401 58
[28/May/2025 18:39:29] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:39:30] "POST /api/users/refresh/ HTTP/1.1" 200 483
Unauthorized: /api/users/refresh/
[28/May/2025 18:39:30] "POST /api/users/refresh/ HTTP/1.1" 401 58
[28/May/2025 18:39:36] "POST /api/users/login/ HTTP/1.1" 200 483
[28/May/2025 18:39:36] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:39:38] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:39:38] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:40:25] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:40:25] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:40:25] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:40:25] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:40:27] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:40:27] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:40:27] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:40:27] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:40:30] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:40:30] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:40:30] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:40:30] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:40:38] "POST /api/users/login/ HTTP/1.1" 200 483
[28/May/2025 18:40:38] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:41:05] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:41:05] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:41:05] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:41:05] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:41:22] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:41:22] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:41:22] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:41:22] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:41:41] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:41:41] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:41:42] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:41:42] "GET /api/content/posts/ HTTP/1.1" 200 1074
[28/May/2025 18:42:12] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:42:12] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:42:12] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:42:12] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:43:37] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:43:37] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:43:37] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:43:37] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:49:14] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:49:14] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:49:14] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:49:14] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:53:49] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:53:49] "POST /api/users/refresh/ HTTP/1.1" 200 483
[28/May/2025 18:53:49] "GET /api/users/me/ HTTP/1.1" 200 101
[28/May/2025 18:53:49] "GET /api/users/me/ HTTP/1.1" 200 101

BIN
backend/.DS_Store vendored Normal file

Binary file not shown.

12
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
#Ignorowanie plików i katalogów w projekcie backend
# Created by venv; see https://docs.python.org/3/library/venv.html
/bin
/lib
/include
/share
/man
/doc
/html
pyvenv.cfg
/venv

0
backend/README.md Normal file
View File

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
backend/content/admin.py Normal file
View File

@ -0,0 +1,4 @@
#Obsługa panelu administracyjnego dla aplikacji content
from django.contrib import admin
# Register your models here.

7
backend/content/apps.py Normal file
View File

@ -0,0 +1,7 @@
#Ogbługa aplikacji content w Django
from django.apps import AppConfig
# Definicja klasy konfiguracyjnej aplikacji Django dla modułu content.
class ContentConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'content'

View File

@ -0,0 +1,59 @@
# Generated by Django 4.2 on 2025-05-01 18:16
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(unique=True)),
],
),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField(max_length=2000)),
('approved', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='Page',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('content', models.TextField()),
('status', models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending Review'), ('published', 'Published')], default='draft', max_length=10)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('content', models.TextField()),
('status', models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending Review'), ('published', 'Published')], default='draft', max_length=10)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 4.2 on 2025-05-01 18:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('content', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='post',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='post',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.category'),
),
migrations.AddField(
model_name='page',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='page',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.category'),
),
migrations.AddField(
model_name='comment',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='comment',
name='page',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.page'),
),
migrations.AddField(
model_name='comment',
name='post',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.post'),
),
]

View File

@ -0,0 +1,30 @@
from django.db import migrations
def create_initial_categories(apps, schema_editor):
Category = apps.get_model('content', 'Category')
default_categories = [
{'name': 'General', 'slug': 'general'},
{'name': 'Technology', 'slug': 'technology'},
{'name': 'Science', 'slug': 'science'},
{'name': 'Programming', 'slug': 'programming'},
{'name': 'News', 'slug': 'news'},
]
for category in default_categories:
Category.objects.get_or_create(
name=category['name'],
slug=category['slug']
)
def remove_initial_categories(apps, schema_editor):
Category = apps.get_model('content', 'Category')
Category.objects.filter(slug__in=['general', 'technology', 'science', 'programming', 'news']).delete()
class Migration(migrations.Migration):
dependencies = [
('content', '0001_initial'),
]
operations = [
migrations.RunPython(create_initial_categories, remove_initial_categories),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 4.2 on 2025-05-01 19:05
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('content', '0002_initial'),
]
operations = [
migrations.CreateModel(
name='UploadedImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='uploads/%Y/%m/%d/', validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['jpg', 'jpeg', 'png', 'gif', 'webp'])])),
('title', models.CharField(max_length=255)),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('file_size', models.PositiveIntegerField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-uploaded_at'],
},
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 4.2 on 2025-05-01 19:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('content', '0002_initial_categories'),
('content', '0003_uploadedimage'),
]
operations = [
]

View File

@ -0,0 +1,31 @@
# Generated by Django 4.2 on 2025-05-04 10:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0004_merge_0002_initial_categories_0003_uploadedimage'),
]
operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(unique=True)),
],
),
migrations.AddField(
model_name='page',
name='tags',
field=models.ManyToManyField(blank=True, to='content.tag'),
),
migrations.AddField(
model_name='post',
name='tags',
field=models.ManyToManyField(blank=True, to='content.tag'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2 on 2025-05-10 23:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0005_tag_page_tags_post_tags'),
]
operations = [
migrations.AddField(
model_name='page',
name='slug',
field=models.SlugField(blank=True, unique=True),
),
migrations.AddField(
model_name='post',
name='slug',
field=models.SlugField(blank=True, unique=True),
),
]

View File

@ -0,0 +1,71 @@
# Generated by Django 4.2 on 2025-07-09 20:01
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('content', '0006_page_slug_post_slug'),
]
operations = [
migrations.AddField(
model_name='category',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='category',
name='description',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='category',
name='meta_description',
field=models.CharField(blank=True, help_text='Opis dla wyszukiwarek (SEO)', max_length=160, null=True),
),
migrations.AddField(
model_name='category',
name='parent_category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subcategories', to='content.category'),
),
migrations.AddField(
model_name='page',
name='meta_description',
field=models.CharField(blank=True, help_text='Opis dla wyszukiwarek (SEO)', max_length=160, null=True),
),
migrations.AddField(
model_name='page',
name='meta_image',
field=models.ImageField(blank=True, help_text='Obrazek do podglądu w linkach (np. na Facebooku).', null=True, upload_to='seo/'),
),
migrations.AddField(
model_name='page',
name='meta_title',
field=models.CharField(blank=True, help_text='Tytuł wyświetlany w wyszukiwarkach. Domyślnie będzie użyty title.', max_length=70),
),
migrations.AddField(
model_name='post',
name='meta_description',
field=models.CharField(blank=True, help_text='Opis dla wyszukiwarek (SEO)', max_length=160, null=True),
),
migrations.AddField(
model_name='post',
name='meta_image',
field=models.ImageField(blank=True, help_text='Obrazek do podglądu w linkach (np. na Facebooku).', null=True, upload_to='seo/'),
),
migrations.AddField(
model_name='post',
name='meta_title',
field=models.CharField(blank=True, help_text='Tytuł wyświetlany w wyszukiwarkach. Domyślnie będzie użyty title.', max_length=70),
),
migrations.AlterField(
model_name='category',
name='slug',
field=models.SlugField(blank=True, unique=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2 on 2025-07-10 20:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0007_category_created_at_category_description_and_more'),
]
operations = [
migrations.AlterField(
model_name='page',
name='meta_image',
field=models.URLField(blank=True, help_text='Obrazek do podglądu w linkach (np. na Facebooku)', null=True),
),
migrations.AlterField(
model_name='post',
name='meta_image',
field=models.URLField(blank=True, help_text='Obrazek do podglądu w linkach (np. na Facebooku)', null=True),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 4.2 on 2025-08-07 07:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0008_alter_page_meta_image_alter_post_meta_image'),
]
operations = [
migrations.AddField(
model_name='uploadedimage',
name='category',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='content.category'),
),
migrations.AddField(
model_name='uploadedimage',
name='description',
field=models.TextField(blank=True, help_text='Opis obrazka', null=True),
),
]

View File

112
backend/content/models.py Normal file
View File

@ -0,0 +1,112 @@
#Modele do obsługi treści: takie jak posty, strony, komentarze i przesłane obrazy.
from django.db import models
from users.models import User
from django.conf import settings
from django.utils import timezone
from django.core.validators import FileExtensionValidator
from django.utils.text import slugify
#Obsługa kategorii
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True, blank=True)
description = models.TextField(blank=True, null=True)
parent_category = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, related_name='subcategories')
created_at = models.DateTimeField(auto_now_add=True)
meta_description = models.CharField(max_length=160, blank=True, null=True, help_text="Opis dla wyszukiwarek (SEO)")
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def __str__(self):
return self.name
#Obsługa treści
class AbstractContent(models.Model):
STATUS_CHOICES = (
('draft', 'Draft'),
('pending', 'Pending Review'),
('published', 'Published'),
)
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True, blank=True)
content = models.TextField()
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag', blank=True)
meta_title = models.CharField(max_length=70, blank=True, help_text="Tytuł wyświetlany w wyszukiwarkach. Domyślnie będzie użyty title.")
meta_description = models.CharField(max_length=160, blank=True, null=True, help_text="Opis dla wyszukiwarek (SEO)")
meta_image = models.URLField(blank=True, null=True, help_text="Obrazek do podglądu w linkach (np. na Facebooku)")
class Meta:
abstract = True
def __str__(self):
return self.title
#Obsługa postów i stron
class Post(AbstractContent):
pass
class Page(AbstractContent):
pass
#Obsługa komentarzy
class Comment(models.Model):
content = models.TextField(max_length=2000)
approved = models.BooleanField(default=False)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.CASCADE)
page = models.ForeignKey(Page, null=True, blank=True, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
#Obsługa przesłanych obrazów
class UploadedImage(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
image = models.ImageField(
upload_to='uploads/%Y/%m/%d/',
validators=[FileExtensionValidator(allowed_extensions=['jpg', 'jpeg', 'png', 'gif', 'webp'])]
)
title = models.CharField(max_length=255)
uploaded_at = models.DateTimeField(auto_now_add=True)
file_size = models.PositiveIntegerField() # Size in bytes
category = models.ForeignKey('Category', on_delete=models.CASCADE, default=1)
description = models.TextField(blank=True, null=True, help_text="Opis obrazka")
class Meta:
ordering = ['-uploaded_at']
def __str__(self):
return f"{self.title} - {self.user.username}"
@property
def url(self):
return self.image.url if self.image else None
def save(self, *args, **kwargs):
if not self.file_size and self.image:
self.file_size = self.image.size
super().save(*args, **kwargs)
@classmethod
def get_user_daily_upload_size(cls, user):
today = timezone.now().date()
tomorrow = today + timezone.timedelta(days=1)
return cls.objects.filter(
user=user,
uploaded_at__gte=today,
uploaded_at__lt=tomorrow
).aggregate(total_size=models.Sum('file_size'))['total_size'] or 0
#Obsługa tagów
class Tag(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name

View File

@ -0,0 +1,191 @@
import os
from django.utils.text import slugify
from rest_framework import serializers
from users.models import User
from .models import (
UploadedImage,
Category,
Post,
Comment,
Page,
Tag,
)
# Serialized do tagów
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug']
read_only_fields = ['id']
# Serialized do kategorii
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'description', 'parent_category', 'created_at']
read_only_fields = ['id', 'created_at']
# Serialized do użytkowników
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'first_name', 'last_name']
read_only_fields = ['id']
# Serialized do komentarzy
class CommentSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
class Meta:
model = Comment
fields = ['id', 'content', 'author', 'post', 'page', 'approved', 'created_at']
read_only_fields = ['id', 'created_at', 'author']
# Serialized do postów
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True
)
comments = CommentSerializer(many=True, read_only=True)
tags = TagSerializer(many=True, read_only=True)
tag_ids = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
source='tags',
many=True,
write_only=True,
required=False
)
slug = serializers.SlugField(required=True)
meta_title = serializers.CharField(required=False, allow_blank=True)
meta_description = serializers.CharField(required=False, allow_blank=True)
meta_image = serializers.CharField(required=False, allow_null=True)
class Meta:
model = Post
fields = [
'id', 'title', 'content', 'status', 'author',
'category', 'category_id', 'comments',
'created_at', 'updated_at', 'tags', 'tag_ids', 'slug',
'meta_title', 'meta_description', 'meta_image',]
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
def create(self, validated_data):
if not validated_data.get('slug'):
validated_data['slug'] = slugify(validated_data.get('title', ''))
return super().create(validated_data)
def update(self, instance, validated_data):
if not validated_data.get('slug') and validated_data.get('title'):
validated_data['slug'] = slugify(validated_data['title'])
return super().update(instance, validated_data)
# Serialized do stron
class PageSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True
)
comments = CommentSerializer(many=True, read_only=True)
slug = serializers.SlugField(required=True)
meta_title = serializers.CharField(required=False, allow_blank=True)
meta_description = serializers.CharField(required=False, allow_blank=True)
meta_image = serializers.ImageField(required=False, allow_null=True)
class Meta:
model = Page
fields = [
'id', 'title', 'content', 'status', 'author',
'category', 'category_id', 'comments',
'created_at', 'updated_at', 'slug',
'meta_title', 'meta_description', 'meta_image']
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
# Serialized do przesłanych obrazów
class UploadedImageSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
file_size_display = serializers.SerializerMethodField()
image = serializers.ImageField(required=True)
class Meta:
model = UploadedImage
fields = [
'id',
'title',
'image',
'url',
'uploaded_at',
'file_size',
'file_size_display',
'category',
'description',
]
read_only_fields = ['file_size', 'uploaded_at', 'url']
def get_url(self, obj):
request = self.context.get('request')
if obj.image and hasattr(obj.image, 'url'):
return request.build_absolute_uri(obj.image.url) if request else obj.image.url
return None
def get_file_size_display(self, obj):
print(f"Rozmiar pliku: {obj.file_size}")
size = obj.file_size
if size is None:
return "0 B" # albo return None
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024.0:
return f"{size:.1f} {unit}"
size /= 1024.0
return f"{size:.1f} TB"
def validate_image(self, value):
if not value:
raise serializers.ValidationError("Image file is required.")
return value
url = serializers.SerializerMethodField()
file_size_display = serializers.SerializerMethodField()
image = serializers.ImageField(required=True)
def get_url(self, obj):
request = self.context.get('request')
if obj.image and hasattr(obj.image, 'url'):
return request.build_absolute_uri(obj.image.url) if request else obj.image.url
return None
def get_file_size_display(self, obj):
"""Convert bytes to human readable format"""
for unit in ['B', 'KB', 'MB', 'GB']:
if obj.file_size < 1024.0:
return f"{obj.file_size:.1f} {unit}"
obj.file_size /= 1024.0
return f"{obj.file_size:.1f} TB"
def validate_image(self, value):
if not value:
raise serializers.ValidationError("Image file is required.")
return value
def validate_category(self, value):
if not Category.objects.filter(id=value.id if isinstance(value, Category) else value).exists():
raise serializers.ValidationError("Wybrana kategoria nie istnieje.")
return value

4
backend/content/tests.py Normal file
View File

@ -0,0 +1,4 @@
#Obsługa testów dla aplikacji content
from django.test import TestCase
# Create your tests here.

18
backend/content/urls.py Normal file
View File

@ -0,0 +1,18 @@
#Obsługa ścieżek dla aplikacji content.
from rest_framework.routers import DefaultRouter
from django.urls import path, include
from .views import PostViewSet, CommentViewSet, PageViewSet, CategoryViewSet, UploadedImageViewSet, TagViewSet, SearchView
router = DefaultRouter()
router.register(r'posts', PostViewSet, basename='post')
router.register(r'comments', CommentViewSet, basename='comment')
router.register(r'pages', PageViewSet, basename='page')
router.register(r'categories', CategoryViewSet, basename='category')
router.register(r'images', UploadedImageViewSet, basename='image')
router.register(r'tags', TagViewSet, basename='tag')
urlpatterns = [
path('', include(router.urls)),
path('search/', SearchView.as_view(), name='search'),
]

288
backend/content/views.py Normal file
View File

@ -0,0 +1,288 @@
#Obsługa widoków dla aplikacji content.
import os
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import viewsets, status, filters, serializers
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.permissions import (
IsAuthenticated,
IsAuthenticatedOrReadOnly,
AllowAny,
)
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from django_filters.rest_framework import DjangoFilterBackend
from .models import (
Post,
Comment,
Page,
Category,
UploadedImage,
Tag,
)
from .serializers import (
PostSerializer,
CommentSerializer,
PageSerializer,
CategorySerializer,
UploadedImageSerializer,
TagSerializer,
)
from users.permissions import IsAuthorOrReadOnly, IsEditorOrAdmin, IsAdminOrReadOnly
# Obsługa logiczna widoku dla postów
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAdminOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['status', 'author', 'category']
search_fields = ['title', 'content']
ordering_fields = ['created_at', 'updated_at', 'title']
ordering = ['-created_at']
lookup_field = 'slug'
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def get_queryset(self):
queryset = Post.objects.all()
if not self.request.user.is_authenticated:
queryset = queryset.filter(status='published')
elif not self.request.user.role in ['admin', 'moderator']:
queryset = queryset.filter(
status='published'
) | Post.objects.filter(
author=self.request.user
)
return queryset
def perform_update(self, serializer):
print(serializer.validated_data)
return super().perform_update(serializer)
# Obsługa komentarzy do postów i stron
"""
API endpoint for managing comments on posts and pages.
list:
Return a list of all comments. Results can be filtered by:
- post_id
- page_id
- author
- approved status
create:
Create a new comment. Requires authentication.
retrieve:
Return the details of a specific comment by ID.
update:
Update all fields of a specific comment. Requires authentication and appropriate permissions.
partial_update:
Update one or more fields of a specific comment. Requires authentication and appropriate permissions.
destroy:
Delete a specific comment. Requires authentication and appropriate permissions.
"""
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [IsAuthorOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ['post', 'page', 'author', 'approved']
ordering_fields = ['created_at']
ordering = ['-created_at']
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def get_queryset(self):
queryset = Comment.objects.all()
if not self.request.user.is_authenticated:
queryset = queryset.filter(approved=True)
elif not self.request.user.role in ['admin', 'moderator']:
queryset = queryset.filter(
approved=True
) | Comment.objects.filter(
author=self.request.user
)
return queryset
#obsługa widoków dla stron
class PageViewSet(viewsets.ModelViewSet):
queryset = Page.objects.all()
serializer_class = PageSerializer
permission_classes = [IsEditorOrAdmin]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['status', 'author', 'category']
search_fields = ['title', 'content']
ordering_fields = ['created_at', 'updated_at', 'title']
ordering = ['-created_at']
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def get_queryset(self):
queryset = Page.objects.all()
if not self.request.user.is_authenticated:
queryset = queryset.filter(status='published')
elif not self.request.user.role in ['admin', 'moderator']:
queryset = queryset.filter(
status='published'
) | Page.objects.filter(
author=self.request.user
)
return queryset
# Obsługa kategorii
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter]
search_fields = ['name', 'slug']
def get_queryset(self):
return Category.objects.all()
# Obsługa przesyłania obrazów
class UploadedImageViewSet(viewsets.ModelViewSet):
serializer_class = UploadedImageSerializer
parser_classes = (JSONParser, MultiPartParser, FormParser)
permission_classes = [IsAuthenticated]
def get_queryset(self):
return UploadedImage.objects.filter(user=self.request.user)
def get_serializer_context(self):
context = super().get_serializer_context()
context['request'] = self.request
return context
def perform_create(self, serializer):
user = self.request.user
# Admin? zero limitów
if not user.is_staff:
# Limit: 5MB dziennie
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024
current_size = UploadedImage.get_user_daily_upload_size(user)
file_size = self.request.FILES['image'].size
if current_size + file_size > DAILY_UPLOAD_LIMIT:
raise serializers.ValidationError({
'image': 'Daily upload limit (5MB) exceeded. Try again tomorrow.'
})
# Limit 1 plik / minutę
one_minute_ago = timezone.now() - timezone.timedelta(minutes=1)
if UploadedImage.objects.filter(user=user, uploaded_at__gte=one_minute_ago).exists():
raise serializers.ValidationError({
'image': 'Upload limit: 1 image per minute.'
})
serializer.save(user=user, file_size=self.request.FILES['image'].size)
def perform_update(self, serializer):
instance = serializer.instance
if instance.user != self.request.user and not self.request.user.is_staff:
raise serializers.ValidationError("Brak uprawnień do edycji tego pliku.")
serializer.save()
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
if obj.user != request.user and not request.user.is_staff:
return Response({'detail': 'Brak uprawnień do usunięcia.'}, status=403)
# Usuń fizyczny plik z dysku
image_path = obj.image.path
if os.path.isfile(image_path):
os.remove(image_path)
obj.delete()
return Response({'detail': 'Plik usunięty.'}, status=200)
@action(detail=False, methods=['get'])
def quota(self, request):
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024
current_size = UploadedImage.get_user_daily_upload_size(request.user)
remaining = max(0, DAILY_UPLOAD_LIMIT - current_size)
return Response({
'daily_limit': DAILY_UPLOAD_LIMIT,
'used': current_size,
'remaining': remaining,
'daily_limit_display': f"{DAILY_UPLOAD_LIMIT / (1024 * 1024):.1f}MB",
'used_display': f"{current_size / (1024 * 1024):.1f}MB",
'remaining_display': f"{remaining / (1024 * 1024):.1f}MB"
})
# Obsługa tagów
class TagViewSet(viewsets.ModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter]
search_fields = ['name', 'slug']
class SearchView(APIView):
permission_classes = [AllowAny]
def get(self, request):
query = request.GET.get('q', '').strip()
print("🔎 query:", query)
if not query:
return Response([], status=status.HTTP_200_OK)
results = []
# Szukaj w postach
post_matches = Post.objects.filter(
Q(title__icontains=query) | Q(content__icontains=query)
)[:5]
for post in post_matches:
results.append({
'id': post.id,
'title': post.title,
'slug': post.slug,
'type': 'post'
})
# Szukaj w tagach
tag_matches = Tag.objects.filter(
Q(name__icontains=query)
)[:5]
for tag in tag_matches:
results.append({
'id': tag.id,
'title': tag.name,
'slug': tag.slug,
'type': 'tag'
})
# Szukaj w użytkownikach
User = get_user_model()
user_matches = User.objects.filter(
Q(username__icontains=query) | Q(email__icontains=query)
)[:5]
for user in user_matches:
results.append({
'id': user.id,
'title': user.username,
'slug': str(user.id), # lub `username` jeśli masz slug
'type': 'user'
})
return Response(results)

0
backend/core/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
backend/core/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
backend/core/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'core'

View File

3
backend/core/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

60
backend/core/settings.py Normal file
View File

@ -0,0 +1,60 @@
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'rest_framework_simplejwt.token_blacklist',
'corsheaders',
'django_filters',
'users',
'content',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'JWK_URL': None,
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'JTI_CLAIM': 'jti',
}

3
backend/core/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
backend/core/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

BIN
backend/db.sqlite3 Normal file

Binary file not shown.

BIN
backend/formulas/.DS_Store vendored Normal file

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,3 @@
from .formula import FormulaAdmin
from .symbol import SymbolAdmin
from .category import FormulaCategoryAdmin

View File

@ -0,0 +1,8 @@
from django.contrib import admin
from formulas.models.category import FormulaCategory
@admin.register(FormulaCategory)
class FormulaCategoryAdmin(admin.ModelAdmin):
list_display = ("id", "prefix", "name")
ordering = ("prefix",)
search_fields = ("name",)

View File

@ -0,0 +1,10 @@
from django.contrib import admin
from formulas.models.formula import Formula
@admin.register(Formula)
class FormulaAdmin(admin.ModelAdmin):
list_display = ("formula_id", "title", "code", "revision", "category")
list_filter = ("category", "revision")
search_fields = ("formula_id", "title", "code", "description")
readonly_fields = ("code", "created_at")
ordering = ("code",)

Binary file not shown.

View File

@ -0,0 +1,10 @@
from django.contrib import admin
from formulas.models.symbol import Symbol
@admin.register(Symbol)
class SymbolAdmin(admin.ModelAdmin):
list_display = ("symbol_id", "name", "unit", "code", "revision", "category")
list_filter = ("category", "revision")
search_fields = ("symbol_id", "name", "code", "description")
readonly_fields = ("code",)
ordering = ("code",)

6
backend/formulas/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class FormulasConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'formulas'

View File

@ -0,0 +1,65 @@
# Generated by Django 4.2 on 2025-06-22 11:14
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='FormulaCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('prefix', models.PositiveIntegerField(unique=True)),
],
options={
'verbose_name': 'Kategoria wzoru',
'verbose_name_plural': 'Kategorie wzorów',
},
),
migrations.CreateModel(
name='Symbol',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('symbol_id', models.CharField(max_length=10)),
('name', models.CharField(max_length=255)),
('unit', models.CharField(max_length=50)),
('description', models.TextField(blank=True)),
('tags', models.JSONField(blank=True, default=list)),
('revision', models.PositiveIntegerField(default=1)),
('code', models.CharField(editable=False, max_length=10, unique=True)),
('meta', models.TextField(blank=True)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='formulas.formulacategory')),
],
),
migrations.CreateModel(
name='Formula',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('formula_id', models.CharField(max_length=100)),
('title', models.CharField(max_length=255)),
('description', models.TextField(blank=True)),
('meta', models.TextField(blank=True)),
('tags', models.JSONField(default=list)),
('latex', models.TextField()),
('raw_latex', models.TextField()),
('variables', models.JSONField(default=list)),
('calculator', models.CharField(max_length=100)),
('revision', models.PositiveIntegerField(default=1)),
('code', models.CharField(editable=False, max_length=10, unique=True)),
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='formulas.formulacategory')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,30 @@
# Generated by Django 4.2 on 2025-07-13 19:15
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('formulas', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='FavoriteFormula',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('added_at', models.DateTimeField(auto_now_add=True)),
('formula', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='formulas.formula')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Ulubiony wzór',
'verbose_name_plural': 'Ulubione wzory',
'unique_together': {('user', 'formula')},
},
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 4.2 on 2025-07-26 08:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('formulas', '0002_favoriteformula'),
]
operations = [
migrations.RemoveField(
model_name='formula',
name='variables',
),
migrations.AddField(
model_name='formula',
name='symbols',
field=models.ManyToManyField(blank=True, related_name='formulas', to='formulas.symbol'),
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 4.2 on 2025-08-07 07:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('formulas', '0003_remove_formula_variables_formula_symbols'),
]
operations = [
migrations.AlterField(
model_name='formula',
name='code',
field=models.CharField(max_length=10, unique=True),
),
migrations.AlterField(
model_name='formula',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='formula',
name='symbols',
field=models.ManyToManyField(related_name='formulas', to='formulas.symbol'),
),
]

View File

Some files were not shown because too many files have changed in this diff Show More