zmiany mariusza
This commit is contained in:
commit
0f586b15cb
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
||||
81
README.md
Normal file
81
README.md
Normal 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
106
backend.log
Normal 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
BIN
backend/.DS_Store
vendored
Normal file
Binary file not shown.
12
backend/.gitignore
vendored
Normal file
12
backend/.gitignore
vendored
Normal 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
0
backend/README.md
Normal file
0
backend/content/__init__.py
Normal file
0
backend/content/__init__.py
Normal file
BIN
backend/content/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/content/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/admin.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/admin.cpython-313.pyc
Normal file
BIN
backend/content/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/apps.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/apps.cpython-313.pyc
Normal file
BIN
backend/content/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/models.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/models.cpython-313.pyc
Normal file
BIN
backend/content/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/serializers.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/serializers.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/serializers.cpython-313.pyc
Normal file
BIN
backend/content/__pycache__/serializers.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/urls.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/urls.cpython-313.pyc
Normal file
BIN
backend/content/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/views.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/views.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/content/__pycache__/views.cpython-313.pyc
Normal file
BIN
backend/content/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
4
backend/content/admin.py
Normal file
4
backend/content/admin.py
Normal 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
7
backend/content/apps.py
Normal 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'
|
||||
59
backend/content/migrations/0001_initial.py
Normal file
59
backend/content/migrations/0001_initial.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
||||
53
backend/content/migrations/0002_initial.py
Normal file
53
backend/content/migrations/0002_initial.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
30
backend/content/migrations/0002_initial_categories.py
Normal file
30
backend/content/migrations/0002_initial_categories.py
Normal 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),
|
||||
]
|
||||
31
backend/content/migrations/0003_uploadedimage.py
Normal file
31
backend/content/migrations/0003_uploadedimage.py
Normal 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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -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 = [
|
||||
]
|
||||
31
backend/content/migrations/0005_tag_page_tags_post_tags.py
Normal file
31
backend/content/migrations/0005_tag_page_tags_post_tags.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
23
backend/content/migrations/0006_page_slug_post_slug.py
Normal file
23
backend/content/migrations/0006_page_slug_post_slug.py
Normal 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),
|
||||
),
|
||||
]
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
0
backend/content/migrations/__init__.py
Normal file
0
backend/content/migrations/__init__.py
Normal 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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/content/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/content/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/content/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/content/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
112
backend/content/models.py
Normal file
112
backend/content/models.py
Normal 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
|
||||
191
backend/content/serializers.py
Normal file
191
backend/content/serializers.py
Normal 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
4
backend/content/tests.py
Normal 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
18
backend/content/urls.py
Normal 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
288
backend/content/views.py
Normal 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
0
backend/core/__init__.py
Normal file
BIN
backend/core/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/core/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/core/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/admin.cpython-312.pyc
Normal file
BIN
backend/core/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/admin.cpython-313.pyc
Normal file
BIN
backend/core/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/apps.cpython-312.pyc
Normal file
BIN
backend/core/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/apps.cpython-313.pyc
Normal file
BIN
backend/core/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/models.cpython-312.pyc
Normal file
BIN
backend/core/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/core/__pycache__/models.cpython-313.pyc
Normal file
BIN
backend/core/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
3
backend/core/admin.py
Normal file
3
backend/core/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/core/apps.py
Normal file
6
backend/core/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'core'
|
||||
0
backend/core/migrations/__init__.py
Normal file
0
backend/core/migrations/__init__.py
Normal file
BIN
backend/core/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/core/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/core/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/core/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
3
backend/core/models.py
Normal file
3
backend/core/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
60
backend/core/settings.py
Normal file
60
backend/core/settings.py
Normal 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
3
backend/core/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
backend/core/views.py
Normal file
3
backend/core/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
BIN
backend/db.sqlite3
Normal file
BIN
backend/db.sqlite3
Normal file
Binary file not shown.
BIN
backend/formulas/.DS_Store
vendored
Normal file
BIN
backend/formulas/.DS_Store
vendored
Normal file
Binary file not shown.
0
backend/formulas/__init__.py
Normal file
0
backend/formulas/__init__.py
Normal file
BIN
backend/formulas/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/formulas/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/__pycache__/apps.cpython-312.pyc
Normal file
BIN
backend/formulas/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/__pycache__/permissions.cpython-312.pyc
Normal file
BIN
backend/formulas/__pycache__/permissions.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/__pycache__/urls.cpython-312.pyc
Normal file
BIN
backend/formulas/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
3
backend/formulas/admin/__init__.py
Normal file
3
backend/formulas/admin/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .formula import FormulaAdmin
|
||||
from .symbol import SymbolAdmin
|
||||
from .category import FormulaCategoryAdmin
|
||||
BIN
backend/formulas/admin/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/formulas/admin/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/admin/__pycache__/category.cpython-312.pyc
Normal file
BIN
backend/formulas/admin/__pycache__/category.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/admin/__pycache__/formula.cpython-312.pyc
Normal file
BIN
backend/formulas/admin/__pycache__/formula.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/admin/__pycache__/symbol.cpython-312.pyc
Normal file
BIN
backend/formulas/admin/__pycache__/symbol.cpython-312.pyc
Normal file
Binary file not shown.
8
backend/formulas/admin/category.py
Normal file
8
backend/formulas/admin/category.py
Normal 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",)
|
||||
10
backend/formulas/admin/formula.py
Normal file
10
backend/formulas/admin/formula.py
Normal 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",)
|
||||
BIN
backend/formulas/admin/formulaadmin.zip
Normal file
BIN
backend/formulas/admin/formulaadmin.zip
Normal file
Binary file not shown.
10
backend/formulas/admin/symbol.py
Normal file
10
backend/formulas/admin/symbol.py
Normal 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
6
backend/formulas/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FormulasConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'formulas'
|
||||
65
backend/formulas/migrations/0001_initial.py
Normal file
65
backend/formulas/migrations/0001_initial.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
30
backend/formulas/migrations/0002_favoriteformula.py
Normal file
30
backend/formulas/migrations/0002_favoriteformula.py
Normal 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')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
0
backend/formulas/migrations/__init__.py
Normal file
0
backend/formulas/migrations/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user