Compare commits
No commits in common. "main" and "zmiany-mariusz" have entirely different histories.
main
...
zmiany-mar
160
README.md
160
README.md
@ -1,81 +1,81 @@
|
|||||||
# Izaac 2.1 Project
|
# 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.
|
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
|
## Prerequisites
|
||||||
|
|
||||||
- Python 3.x
|
- Python 3.x
|
||||||
- Node.js (LTS version recommended)
|
- Node.js (LTS version recommended)
|
||||||
- npm or yarn
|
- npm or yarn
|
||||||
- SQLite (included with Python)
|
- SQLite (included with Python)
|
||||||
|
|
||||||
## Backend Setup
|
## Backend Setup
|
||||||
|
|
||||||
1. Navigate to the backend directory:
|
1. Navigate to the backend directory:
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Create and activate a virtual environment:
|
2. Create and activate a virtual environment:
|
||||||
```bash
|
```bash
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
source venv/bin/activate # On Windows use: venv\Scripts\activate
|
source venv/bin/activate # On Windows use: venv\Scripts\activate
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Install dependencies:
|
3. Install dependencies:
|
||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Run database migrations:
|
4. Run database migrations:
|
||||||
```bash
|
```bash
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Start the development server:
|
5. Start the development server:
|
||||||
```bash
|
```bash
|
||||||
python manage.py runserver
|
python manage.py runserver
|
||||||
```
|
```
|
||||||
|
|
||||||
The backend will be available at `http://localhost:8000`
|
The backend will be available at `http://localhost:8000`
|
||||||
|
|
||||||
## Frontend Setup
|
## Frontend Setup
|
||||||
|
|
||||||
1. Navigate to the frontend directory:
|
1. Navigate to the frontend directory:
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install dependencies:
|
2. Install dependencies:
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
# or if you use yarn:
|
# or if you use yarn:
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Start the development server:
|
3. Start the development server:
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
# or if you use yarn:
|
# or if you use yarn:
|
||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The frontend will be available at `http://localhost:5173`
|
The frontend will be available at `http://localhost:5173`
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
- Backend API documentation is available at `http://localhost:8000/api/docs/` when the server is running
|
- Backend API documentation is available at `http://localhost:8000/api/docs/` when the server is running
|
||||||
- Frontend hot-reloading is enabled by default
|
- Frontend hot-reloading is enabled by default
|
||||||
- Backend hot-reloading is enabled by default with Django's development server
|
- Backend hot-reloading is enabled by default with Django's development server
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
- `/backend` - Django backend application
|
- `/backend` - Django backend application
|
||||||
- `/frontend` - React/TypeScript frontend application
|
- `/frontend` - React/TypeScript frontend application
|
||||||
- `/nginx` - Nginx configuration files
|
- `/nginx` - Nginx configuration files
|
||||||
|
|
||||||
## Additional Notes
|
## Additional Notes
|
||||||
|
|
||||||
- Make sure both backend and frontend servers are running simultaneously for full functionality
|
- Make sure both backend and frontend servers are running simultaneously for full functionality
|
||||||
- The backend uses SQLite as the database by default
|
- The backend uses SQLite as the database by default
|
||||||
- Frontend is built with Vite and uses Tailwind CSS for styling
|
- 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.
21
backend/.gitignore
vendored
21
backend/.gitignore
vendored
@ -1,9 +1,12 @@
|
|||||||
# Created by venv; see https://docs.python.org/3/library/venv.html
|
|
||||||
/bin
|
#Ignorowanie plików i katalogów w projekcie backend
|
||||||
/lib
|
# Created by venv; see https://docs.python.org/3/library/venv.html
|
||||||
/include
|
/bin
|
||||||
/share
|
/lib
|
||||||
/man
|
/include
|
||||||
/doc
|
/share
|
||||||
/html
|
/man
|
||||||
pyvenv.cfg
|
/doc
|
||||||
|
/html
|
||||||
|
pyvenv.cfg
|
||||||
|
/venv
|
||||||
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__/admin.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/admin.cpython-312.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__/models.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/models.cpython-312.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__/urls.cpython-312.pyc
Normal file
BIN
backend/content/__pycache__/urls.cpython-312.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.
@ -1,3 +1,4 @@
|
|||||||
from django.contrib import admin
|
#Obsługa panelu administracyjnego dla aplikacji content
|
||||||
|
from django.contrib import admin
|
||||||
# Register your models here.
|
|
||||||
|
# Register your models here.
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
#Ogbługa aplikacji content w Django
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
class ContentConfig(AppConfig):
|
# Definicja klasy konfiguracyjnej aplikacji Django dla modułu content.
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
class ContentConfig(AppConfig):
|
||||||
name = 'content'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'content'
|
||||||
|
|||||||
@ -1,59 +1,59 @@
|
|||||||
# Generated by Django 4.2 on 2025-05-01 18:16
|
# Generated by Django 4.2 on 2025-05-01 18:16
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Category',
|
name='Category',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=255)),
|
('name', models.CharField(max_length=255)),
|
||||||
('slug', models.SlugField(unique=True)),
|
('slug', models.SlugField(unique=True)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Comment',
|
name='Comment',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('content', models.TextField(max_length=2000)),
|
('content', models.TextField(max_length=2000)),
|
||||||
('approved', models.BooleanField(default=False)),
|
('approved', models.BooleanField(default=False)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Page',
|
name='Page',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('title', models.CharField(max_length=255)),
|
('title', models.CharField(max_length=255)),
|
||||||
('content', models.TextField()),
|
('content', models.TextField()),
|
||||||
('status', models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending Review'), ('published', 'Published')], default='draft', max_length=10)),
|
('status', models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending Review'), ('published', 'Published')], default='draft', max_length=10)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Post',
|
name='Post',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('title', models.CharField(max_length=255)),
|
('title', models.CharField(max_length=255)),
|
||||||
('content', models.TextField()),
|
('content', models.TextField()),
|
||||||
('status', models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending Review'), ('published', 'Published')], default='draft', max_length=10)),
|
('status', models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending Review'), ('published', 'Published')], default='draft', max_length=10)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,53 +1,53 @@
|
|||||||
# Generated by Django 4.2 on 2025-05-01 18:16
|
# Generated by Django 4.2 on 2025-05-01 18:16
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('content', '0001_initial'),
|
('content', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='post',
|
model_name='post',
|
||||||
name='author',
|
name='author',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='post',
|
model_name='post',
|
||||||
name='category',
|
name='category',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.category'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.category'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='page',
|
model_name='page',
|
||||||
name='author',
|
name='author',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='page',
|
model_name='page',
|
||||||
name='category',
|
name='category',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.category'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.category'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='comment',
|
model_name='comment',
|
||||||
name='author',
|
name='author',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='comment',
|
model_name='comment',
|
||||||
name='page',
|
name='page',
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.page'),
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.page'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='comment',
|
model_name='comment',
|
||||||
name='post',
|
name='post',
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.post'),
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.post'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,30 +1,30 @@
|
|||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
def create_initial_categories(apps, schema_editor):
|
def create_initial_categories(apps, schema_editor):
|
||||||
Category = apps.get_model('content', 'Category')
|
Category = apps.get_model('content', 'Category')
|
||||||
default_categories = [
|
default_categories = [
|
||||||
{'name': 'General', 'slug': 'general'},
|
{'name': 'General', 'slug': 'general'},
|
||||||
{'name': 'Technology', 'slug': 'technology'},
|
{'name': 'Technology', 'slug': 'technology'},
|
||||||
{'name': 'Science', 'slug': 'science'},
|
{'name': 'Science', 'slug': 'science'},
|
||||||
{'name': 'Programming', 'slug': 'programming'},
|
{'name': 'Programming', 'slug': 'programming'},
|
||||||
{'name': 'News', 'slug': 'news'},
|
{'name': 'News', 'slug': 'news'},
|
||||||
]
|
]
|
||||||
|
|
||||||
for category in default_categories:
|
for category in default_categories:
|
||||||
Category.objects.get_or_create(
|
Category.objects.get_or_create(
|
||||||
name=category['name'],
|
name=category['name'],
|
||||||
slug=category['slug']
|
slug=category['slug']
|
||||||
)
|
)
|
||||||
|
|
||||||
def remove_initial_categories(apps, schema_editor):
|
def remove_initial_categories(apps, schema_editor):
|
||||||
Category = apps.get_model('content', 'Category')
|
Category = apps.get_model('content', 'Category')
|
||||||
Category.objects.filter(slug__in=['general', 'technology', 'science', 'programming', 'news']).delete()
|
Category.objects.filter(slug__in=['general', 'technology', 'science', 'programming', 'news']).delete()
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('content', '0001_initial'),
|
('content', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(create_initial_categories, remove_initial_categories),
|
migrations.RunPython(create_initial_categories, remove_initial_categories),
|
||||||
]
|
]
|
||||||
@ -1,31 +1,31 @@
|
|||||||
# Generated by Django 4.2 on 2025-05-01 19:05
|
# Generated by Django 4.2 on 2025-05-01 19:05
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('content', '0002_initial'),
|
('content', '0002_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UploadedImage',
|
name='UploadedImage',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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'])])),
|
('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)),
|
('title', models.CharField(max_length=255)),
|
||||||
('uploaded_at', models.DateTimeField(auto_now_add=True)),
|
('uploaded_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('file_size', models.PositiveIntegerField()),
|
('file_size', models.PositiveIntegerField()),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ['-uploaded_at'],
|
'ordering': ['-uploaded_at'],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
# Generated by Django 4.2 on 2025-05-01 19:19
|
# Generated by Django 4.2 on 2025-05-01 19:19
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('content', '0002_initial_categories'),
|
('content', '0002_initial_categories'),
|
||||||
('content', '0003_uploadedimage'),
|
('content', '0003_uploadedimage'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,31 +1,31 @@
|
|||||||
# Generated by Django 4.2 on 2025-05-04 10:54
|
# Generated by Django 4.2 on 2025-05-04 10:54
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('content', '0004_merge_0002_initial_categories_0003_uploadedimage'),
|
('content', '0004_merge_0002_initial_categories_0003_uploadedimage'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Tag',
|
name='Tag',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=255)),
|
('name', models.CharField(max_length=255)),
|
||||||
('slug', models.SlugField(unique=True)),
|
('slug', models.SlugField(unique=True)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='page',
|
model_name='page',
|
||||||
name='tags',
|
name='tags',
|
||||||
field=models.ManyToManyField(blank=True, to='content.tag'),
|
field=models.ManyToManyField(blank=True, to='content.tag'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='post',
|
model_name='post',
|
||||||
name='tags',
|
name='tags',
|
||||||
field=models.ManyToManyField(blank=True, to='content.tag'),
|
field=models.ManyToManyField(blank=True, to='content.tag'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
# Generated by Django 4.2 on 2025-05-10 23:27
|
# Generated by Django 4.2 on 2025-05-10 23:27
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('content', '0005_tag_page_tags_post_tags'),
|
('content', '0005_tag_page_tags_post_tags'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='page',
|
model_name='page',
|
||||||
name='slug',
|
name='slug',
|
||||||
field=models.SlugField(blank=True, unique=True),
|
field=models.SlugField(blank=True, unique=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='post',
|
model_name='post',
|
||||||
name='slug',
|
name='slug',
|
||||||
field=models.SlugField(blank=True, unique=True),
|
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),
|
||||||
|
),
|
||||||
|
]
|
||||||
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.
@ -1,88 +1,112 @@
|
|||||||
from django.db import models
|
#Modele do obsługi treści: takie jak posty, strony, komentarze i przesłane obrazy.
|
||||||
from users.models import User
|
|
||||||
from django.conf import settings
|
from django.db import models
|
||||||
from django.utils import timezone
|
from users.models import User
|
||||||
from django.core.validators import FileExtensionValidator
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
class Category(models.Model):
|
from django.core.validators import FileExtensionValidator
|
||||||
name = models.CharField(max_length=255)
|
from django.utils.text import slugify
|
||||||
slug = models.SlugField(unique=True)
|
|
||||||
|
#Obsługa kategorii
|
||||||
def __str__(self):
|
class Category(models.Model):
|
||||||
return self.name
|
name = models.CharField(max_length=255)
|
||||||
|
slug = models.SlugField(unique=True, blank=True)
|
||||||
class AbstractContent(models.Model):
|
description = models.TextField(blank=True, null=True)
|
||||||
STATUS_CHOICES = (
|
parent_category = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, related_name='subcategories')
|
||||||
('draft', 'Draft'),
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
('pending', 'Pending Review'),
|
meta_description = models.CharField(max_length=160, blank=True, null=True, help_text="Opis dla wyszukiwarek (SEO)")
|
||||||
('published', 'Published'),
|
|
||||||
)
|
def save(self, *args, **kwargs):
|
||||||
title = models.CharField(max_length=255)
|
if not self.slug:
|
||||||
slug = models.SlugField(unique=True, blank=True)
|
self.slug = slugify(self.name)
|
||||||
content = models.TextField()
|
super().save(*args, **kwargs)
|
||||||
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
|
|
||||||
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
def __str__(self):
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
return self.name
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
category = models.ForeignKey('Category', on_delete=models.CASCADE)
|
#Obsługa treści
|
||||||
tags = models.ManyToManyField('Tag', blank=True)
|
class AbstractContent(models.Model):
|
||||||
class Meta:
|
STATUS_CHOICES = (
|
||||||
abstract = True
|
('draft', 'Draft'),
|
||||||
|
('pending', 'Pending Review'),
|
||||||
def __str__(self):
|
('published', 'Published'),
|
||||||
return self.title
|
)
|
||||||
|
title = models.CharField(max_length=255)
|
||||||
class Post(AbstractContent):
|
slug = models.SlugField(unique=True, blank=True)
|
||||||
pass
|
content = models.TextField()
|
||||||
|
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
|
||||||
class Page(AbstractContent):
|
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
pass
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
class Comment(models.Model):
|
category = models.ForeignKey('Category', on_delete=models.CASCADE)
|
||||||
content = models.TextField(max_length=2000)
|
tags = models.ManyToManyField('Tag', blank=True)
|
||||||
approved = models.BooleanField(default=False)
|
meta_title = models.CharField(max_length=70, blank=True, help_text="Tytuł wyświetlany w wyszukiwarkach. Domyślnie będzie użyty title.")
|
||||||
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
meta_description = models.CharField(max_length=160, blank=True, null=True, help_text="Opis dla wyszukiwarek (SEO)")
|
||||||
post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.CASCADE)
|
meta_image = models.URLField(blank=True, null=True, help_text="Obrazek do podglądu w linkach (np. na Facebooku)")
|
||||||
page = models.ForeignKey(Page, null=True, blank=True, on_delete=models.CASCADE)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
class Meta:
|
||||||
|
abstract = True
|
||||||
class UploadedImage(models.Model):
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
def __str__(self):
|
||||||
image = models.ImageField(
|
return self.title
|
||||||
upload_to='uploads/%Y/%m/%d/',
|
|
||||||
validators=[FileExtensionValidator(allowed_extensions=['jpg', 'jpeg', 'png', 'gif', 'webp'])]
|
#Obsługa postów i stron
|
||||||
)
|
class Post(AbstractContent):
|
||||||
title = models.CharField(max_length=255)
|
pass
|
||||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
file_size = models.PositiveIntegerField() # Size in bytes
|
class Page(AbstractContent):
|
||||||
|
pass
|
||||||
class Meta:
|
|
||||||
ordering = ['-uploaded_at']
|
#Obsługa komentarzy
|
||||||
|
class Comment(models.Model):
|
||||||
def __str__(self):
|
content = models.TextField(max_length=2000)
|
||||||
return f"{self.title} - {self.user.username}"
|
approved = models.BooleanField(default=False)
|
||||||
|
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
@property
|
post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
def url(self):
|
page = models.ForeignKey(Page, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
return self.image.url if self.image else None
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
#Obsługa przesłanych obrazów
|
||||||
if not self.file_size and self.image:
|
class UploadedImage(models.Model):
|
||||||
self.file_size = self.image.size
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
super().save(*args, **kwargs)
|
image = models.ImageField(
|
||||||
|
upload_to='uploads/%Y/%m/%d/',
|
||||||
@classmethod
|
validators=[FileExtensionValidator(allowed_extensions=['jpg', 'jpeg', 'png', 'gif', 'webp'])]
|
||||||
def get_user_daily_upload_size(cls, user):
|
)
|
||||||
today = timezone.now().date()
|
title = models.CharField(max_length=255)
|
||||||
tomorrow = today + timezone.timedelta(days=1)
|
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||||
return cls.objects.filter(
|
file_size = models.PositiveIntegerField() # Size in bytes
|
||||||
user=user,
|
category = models.ForeignKey('Category', on_delete=models.CASCADE, default=1)
|
||||||
uploaded_at__gte=today,
|
description = models.TextField(blank=True, null=True, help_text="Opis obrazka")
|
||||||
uploaded_at__lt=tomorrow
|
|
||||||
).aggregate(total_size=models.Sum('file_size'))['total_size'] or 0
|
class Meta:
|
||||||
|
ordering = ['-uploaded_at']
|
||||||
class Tag(models.Model):
|
|
||||||
name = models.CharField(max_length=255)
|
def __str__(self):
|
||||||
slug = models.SlugField(unique=True)
|
return f"{self.title} - {self.user.username}"
|
||||||
def __str__(self):
|
|
||||||
|
@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
|
return self.name
|
||||||
@ -1,115 +1,191 @@
|
|||||||
from rest_framework import serializers
|
import os
|
||||||
from .models import Post, Comment, Page, Category, UploadedImage, Tag
|
|
||||||
from users.models import User
|
from django.utils.text import slugify
|
||||||
|
from rest_framework import serializers
|
||||||
class TagSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
from users.models import User
|
||||||
model = Tag
|
from .models import (
|
||||||
fields = ['id', 'name', 'slug']
|
UploadedImage,
|
||||||
read_only_fields = ['id']
|
Category,
|
||||||
|
Post,
|
||||||
class CategorySerializer(serializers.ModelSerializer):
|
Comment,
|
||||||
class Meta:
|
Page,
|
||||||
model = Category
|
Tag,
|
||||||
fields = ['id', 'name', 'slug']
|
)
|
||||||
read_only_fields = ['id']
|
|
||||||
|
# Serialized do tagów
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = Tag
|
||||||
fields = ['id', 'username', 'first_name', 'last_name']
|
fields = ['id', 'name', 'slug']
|
||||||
read_only_fields = ['id']
|
read_only_fields = ['id']
|
||||||
|
|
||||||
class CommentSerializer(serializers.ModelSerializer):
|
# Serialized do kategorii
|
||||||
author = UserSerializer(read_only=True)
|
class CategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
class Meta:
|
model = Category
|
||||||
model = Comment
|
fields = ['id', 'name', 'slug', 'description', 'parent_category', 'created_at']
|
||||||
fields = ['id', 'content', 'author', 'post', 'page', 'approved', 'created_at']
|
read_only_fields = ['id', 'created_at']
|
||||||
read_only_fields = ['id', 'created_at', 'author']
|
|
||||||
|
# Serialized do użytkowników
|
||||||
class PostSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
author = UserSerializer(read_only=True)
|
class Meta:
|
||||||
category = CategorySerializer(read_only=True)
|
model = User
|
||||||
category_id = serializers.PrimaryKeyRelatedField(
|
fields = ['id', 'username', 'first_name', 'last_name']
|
||||||
queryset=Category.objects.all(),
|
read_only_fields = ['id']
|
||||||
source='category',
|
|
||||||
write_only=True
|
# Serialized do komentarzy
|
||||||
)
|
class CommentSerializer(serializers.ModelSerializer):
|
||||||
comments = CommentSerializer(many=True, read_only=True)
|
author = UserSerializer(read_only=True)
|
||||||
tags = TagSerializer(many=True, read_only=True)
|
|
||||||
tag_ids = serializers.PrimaryKeyRelatedField(
|
class Meta:
|
||||||
queryset=Tag.objects.all(),
|
model = Comment
|
||||||
source='tags',
|
fields = ['id', 'content', 'author', 'post', 'page', 'approved', 'created_at']
|
||||||
many=True,
|
read_only_fields = ['id', 'created_at', 'author']
|
||||||
write_only=True,
|
|
||||||
required=False
|
# Serialized do postów
|
||||||
)
|
class PostSerializer(serializers.ModelSerializer):
|
||||||
slug = serializers.SlugField(required=True)
|
author = UserSerializer(read_only=True)
|
||||||
|
category = CategorySerializer(read_only=True)
|
||||||
class Meta:
|
category_id = serializers.PrimaryKeyRelatedField(
|
||||||
model = Post
|
queryset=Category.objects.all(),
|
||||||
fields = [
|
source='category',
|
||||||
'id', 'title', 'content', 'status', 'author',
|
write_only=True
|
||||||
'category', 'category_id', 'comments',
|
)
|
||||||
'created_at', 'updated_at', 'tags', 'tag_ids', 'slug'
|
comments = CommentSerializer(many=True, read_only=True)
|
||||||
]
|
tags = TagSerializer(many=True, read_only=True)
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
|
tag_ids = serializers.PrimaryKeyRelatedField(
|
||||||
lookup_field = 'slug'
|
queryset=Tag.objects.all(),
|
||||||
extra_kwargs = {
|
source='tags',
|
||||||
'url': {'lookup_field': 'slug'}
|
many=True,
|
||||||
}
|
write_only=True,
|
||||||
|
required=False
|
||||||
class PageSerializer(serializers.ModelSerializer):
|
)
|
||||||
author = UserSerializer(read_only=True)
|
slug = serializers.SlugField(required=True)
|
||||||
category = CategorySerializer(read_only=True)
|
|
||||||
category_id = serializers.PrimaryKeyRelatedField(
|
meta_title = serializers.CharField(required=False, allow_blank=True)
|
||||||
queryset=Category.objects.all(),
|
meta_description = serializers.CharField(required=False, allow_blank=True)
|
||||||
source='category',
|
meta_image = serializers.CharField(required=False, allow_null=True)
|
||||||
write_only=True
|
|
||||||
)
|
class Meta:
|
||||||
comments = CommentSerializer(many=True, read_only=True)
|
model = Post
|
||||||
slug = serializers.SlugField(required=True)
|
fields = [
|
||||||
class Meta:
|
'id', 'title', 'content', 'status', 'author',
|
||||||
model = Page
|
'category', 'category_id', 'comments',
|
||||||
fields = [
|
'created_at', 'updated_at', 'tags', 'tag_ids', 'slug',
|
||||||
'id', 'title', 'content', 'status', 'author',
|
'meta_title', 'meta_description', 'meta_image',]
|
||||||
'category', 'category_id', 'comments',
|
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
|
||||||
'created_at', 'updated_at', 'slug'
|
lookup_field = 'slug'
|
||||||
]
|
extra_kwargs = {
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
|
'url': {'lookup_field': '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', ''))
|
||||||
class UploadedImageSerializer(serializers.ModelSerializer):
|
return super().create(validated_data)
|
||||||
url = serializers.SerializerMethodField()
|
|
||||||
file_size_display = serializers.SerializerMethodField()
|
def update(self, instance, validated_data):
|
||||||
image = serializers.ImageField(required=True)
|
if not validated_data.get('slug') and validated_data.get('title'):
|
||||||
|
validated_data['slug'] = slugify(validated_data['title'])
|
||||||
class Meta:
|
return super().update(instance, validated_data)
|
||||||
model = UploadedImage
|
|
||||||
fields = ['id', 'title', 'image', 'url', 'uploaded_at', 'file_size', 'file_size_display']
|
# Serialized do stron
|
||||||
read_only_fields = ['file_size', 'uploaded_at', 'url']
|
class PageSerializer(serializers.ModelSerializer):
|
||||||
|
author = UserSerializer(read_only=True)
|
||||||
def get_url(self, obj):
|
category = CategorySerializer(read_only=True)
|
||||||
request = self.context.get('request')
|
category_id = serializers.PrimaryKeyRelatedField(
|
||||||
if obj.image and hasattr(obj.image, 'url'):
|
queryset=Category.objects.all(),
|
||||||
return request.build_absolute_uri(obj.image.url) if request else obj.image.url
|
source='category',
|
||||||
return None
|
write_only=True
|
||||||
|
)
|
||||||
def get_file_size_display(self, obj):
|
comments = CommentSerializer(many=True, read_only=True)
|
||||||
"""Convert bytes to human readable format"""
|
slug = serializers.SlugField(required=True)
|
||||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
||||||
if obj.file_size < 1024.0:
|
meta_title = serializers.CharField(required=False, allow_blank=True)
|
||||||
return f"{obj.file_size:.1f} {unit}"
|
meta_description = serializers.CharField(required=False, allow_blank=True)
|
||||||
obj.file_size /= 1024.0
|
meta_image = serializers.ImageField(required=False, allow_null=True)
|
||||||
return f"{obj.file_size:.1f} TB"
|
|
||||||
|
class Meta:
|
||||||
def validate_image(self, value):
|
model = Page
|
||||||
if not value:
|
fields = [
|
||||||
raise serializers.ValidationError("Image file is required.")
|
'id', 'title', 'content', 'status', 'author',
|
||||||
return value
|
'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
|
||||||
@ -1,3 +1,4 @@
|
|||||||
from django.test import TestCase
|
#Obsługa testów dla aplikacji content
|
||||||
|
from django.test import TestCase
|
||||||
# Create your tests here.
|
|
||||||
|
# Create your tests here.
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
from rest_framework.routers import DefaultRouter
|
#Obsługa ścieżek dla aplikacji content.
|
||||||
from django.urls import path, include
|
|
||||||
from .views import PostViewSet, CommentViewSet, PageViewSet, CategoryViewSet, UploadedImageViewSet, TagViewSet
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from django.urls import path, include
|
||||||
router = DefaultRouter()
|
from .views import PostViewSet, CommentViewSet, PageViewSet, CategoryViewSet, UploadedImageViewSet, TagViewSet, SearchView
|
||||||
router.register(r'posts', PostViewSet, basename='post')
|
|
||||||
router.register(r'comments', CommentViewSet, basename='comment')
|
router = DefaultRouter()
|
||||||
router.register(r'pages', PageViewSet, basename='page')
|
router.register(r'posts', PostViewSet, basename='post')
|
||||||
router.register(r'categories', CategoryViewSet, basename='category')
|
router.register(r'comments', CommentViewSet, basename='comment')
|
||||||
router.register(r'images', UploadedImageViewSet, basename='image')
|
router.register(r'pages', PageViewSet, basename='page')
|
||||||
router.register(r'tags', TagViewSet, basename='tag')
|
router.register(r'categories', CategoryViewSet, basename='category')
|
||||||
urlpatterns = [
|
router.register(r'images', UploadedImageViewSet, basename='image')
|
||||||
path('', include(router.urls)),
|
router.register(r'tags', TagViewSet, basename='tag')
|
||||||
]
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
path('search/', SearchView.as_view(), name='search'),
|
||||||
|
]
|
||||||
|
|||||||
@ -1,202 +1,288 @@
|
|||||||
from rest_framework import viewsets, status, filters
|
#Obsługa widoków dla aplikacji content.
|
||||||
from rest_framework.response import Response
|
import os
|
||||||
from rest_framework.decorators import action
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django.conf import settings
|
||||||
from .models import Post, Comment, Page, Category, UploadedImage, Tag
|
from django.contrib.auth import get_user_model
|
||||||
from .serializers import PostSerializer, CommentSerializer, PageSerializer, CategorySerializer, UploadedImageSerializer, TagSerializer
|
from django.db.models import Q
|
||||||
from users.permissions import IsAuthorOrReadOnly, IsEditorOrAdmin
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
from django.utils import timezone
|
||||||
from rest_framework.parsers import MultiPartParser, FormParser
|
|
||||||
from django.conf import settings
|
from rest_framework import viewsets, status, filters, serializers
|
||||||
from rest_framework import serializers
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
class PostViewSet(viewsets.ModelViewSet):
|
from rest_framework.decorators import action
|
||||||
"""
|
from rest_framework.permissions import (
|
||||||
API endpoint for managing blog posts.
|
IsAuthenticated,
|
||||||
|
IsAuthenticatedOrReadOnly,
|
||||||
list:
|
AllowAny,
|
||||||
Return a list of all posts. Results can be filtered by:
|
)
|
||||||
- status (draft/pending/published)
|
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
||||||
- author
|
|
||||||
- category
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
- created_at (date range)
|
|
||||||
|
from .models import (
|
||||||
create:
|
Post,
|
||||||
Create a new post. Requires authentication and appropriate permissions.
|
Comment,
|
||||||
|
Page,
|
||||||
retrieve:
|
Category,
|
||||||
Return the details of a specific post by ID.
|
UploadedImage,
|
||||||
|
Tag,
|
||||||
update:
|
)
|
||||||
Update all fields of a specific post. Requires authentication and appropriate permissions.
|
from .serializers import (
|
||||||
|
PostSerializer,
|
||||||
partial_update:
|
CommentSerializer,
|
||||||
Update one or more fields of a specific post. Requires authentication and appropriate permissions.
|
PageSerializer,
|
||||||
|
CategorySerializer,
|
||||||
destroy:
|
UploadedImageSerializer,
|
||||||
Delete a specific post. Requires authentication and appropriate permissions.
|
TagSerializer,
|
||||||
"""
|
)
|
||||||
queryset = Post.objects.all()
|
from users.permissions import IsAuthorOrReadOnly, IsEditorOrAdmin, IsAdminOrReadOnly
|
||||||
serializer_class = PostSerializer
|
|
||||||
permission_classes = [IsAuthorOrReadOnly]
|
# Obsługa logiczna widoku dla postów
|
||||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
class PostViewSet(viewsets.ModelViewSet):
|
||||||
filterset_fields = ['status', 'author', 'category']
|
queryset = Post.objects.all()
|
||||||
search_fields = ['title', 'content']
|
serializer_class = PostSerializer
|
||||||
ordering_fields = ['created_at', 'updated_at', 'title']
|
permission_classes = [IsAdminOrReadOnly]
|
||||||
ordering = ['-created_at']
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||||
lookup_field = 'slug'
|
filterset_fields = ['status', 'author', 'category']
|
||||||
|
search_fields = ['title', 'content']
|
||||||
def perform_create(self, serializer):
|
ordering_fields = ['created_at', 'updated_at', 'title']
|
||||||
serializer.save(author=self.request.user)
|
ordering = ['-created_at']
|
||||||
|
lookup_field = 'slug'
|
||||||
def get_queryset(self):
|
|
||||||
queryset = Post.objects.all()
|
def perform_create(self, serializer):
|
||||||
if not self.request.user.is_authenticated:
|
serializer.save(author=self.request.user)
|
||||||
queryset = queryset.filter(status='published')
|
|
||||||
elif not self.request.user.role in ['admin', 'moderator']:
|
def get_queryset(self):
|
||||||
queryset = queryset.filter(
|
queryset = Post.objects.all()
|
||||||
status='published'
|
if not self.request.user.is_authenticated:
|
||||||
) | Post.objects.filter(
|
queryset = queryset.filter(status='published')
|
||||||
author=self.request.user
|
elif not self.request.user.role in ['admin', 'moderator']:
|
||||||
)
|
queryset = queryset.filter(
|
||||||
return queryset
|
status='published'
|
||||||
def perform_update(self, serializer):
|
) | Post.objects.filter(
|
||||||
print(serializer.validated_data)
|
author=self.request.user
|
||||||
return super().perform_update(serializer)
|
)
|
||||||
class CommentViewSet(viewsets.ModelViewSet):
|
return queryset
|
||||||
"""
|
def perform_update(self, serializer):
|
||||||
API endpoint for managing comments on posts and pages.
|
print(serializer.validated_data)
|
||||||
|
return super().perform_update(serializer)
|
||||||
list:
|
|
||||||
Return a list of all comments. Results can be filtered by:
|
# Obsługa komentarzy do postów i stron
|
||||||
- post_id
|
"""
|
||||||
- page_id
|
API endpoint for managing comments on posts and pages.
|
||||||
- author
|
|
||||||
- approved status
|
list:
|
||||||
|
Return a list of all comments. Results can be filtered by:
|
||||||
create:
|
- post_id
|
||||||
Create a new comment. Requires authentication.
|
- page_id
|
||||||
|
- author
|
||||||
retrieve:
|
- approved status
|
||||||
Return the details of a specific comment by ID.
|
|
||||||
|
create:
|
||||||
update:
|
Create a new comment. Requires authentication.
|
||||||
Update all fields of a specific comment. Requires authentication and appropriate permissions.
|
|
||||||
|
retrieve:
|
||||||
partial_update:
|
Return the details of a specific comment by ID.
|
||||||
Update one or more fields of a specific comment. Requires authentication and appropriate permissions.
|
|
||||||
|
update:
|
||||||
destroy:
|
Update all fields of a specific comment. Requires authentication and appropriate permissions.
|
||||||
Delete a specific comment. Requires authentication and appropriate permissions.
|
|
||||||
"""
|
partial_update:
|
||||||
queryset = Comment.objects.all()
|
Update one or more fields of a specific comment. Requires authentication and appropriate permissions.
|
||||||
serializer_class = CommentSerializer
|
|
||||||
permission_classes = [IsAuthorOrReadOnly]
|
destroy:
|
||||||
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
|
Delete a specific comment. Requires authentication and appropriate permissions.
|
||||||
filterset_fields = ['post', 'page', 'author', 'approved']
|
"""
|
||||||
ordering_fields = ['created_at']
|
class CommentViewSet(viewsets.ModelViewSet):
|
||||||
ordering = ['-created_at']
|
queryset = Comment.objects.all()
|
||||||
|
serializer_class = CommentSerializer
|
||||||
def perform_create(self, serializer):
|
permission_classes = [IsAuthorOrReadOnly]
|
||||||
serializer.save(author=self.request.user)
|
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
|
||||||
|
filterset_fields = ['post', 'page', 'author', 'approved']
|
||||||
def get_queryset(self):
|
ordering_fields = ['created_at']
|
||||||
queryset = Comment.objects.all()
|
ordering = ['-created_at']
|
||||||
if not self.request.user.is_authenticated:
|
|
||||||
queryset = queryset.filter(approved=True)
|
def perform_create(self, serializer):
|
||||||
elif not self.request.user.role in ['admin', 'moderator']:
|
serializer.save(author=self.request.user)
|
||||||
queryset = queryset.filter(
|
|
||||||
approved=True
|
def get_queryset(self):
|
||||||
) | Comment.objects.filter(
|
queryset = Comment.objects.all()
|
||||||
author=self.request.user
|
if not self.request.user.is_authenticated:
|
||||||
)
|
queryset = queryset.filter(approved=True)
|
||||||
return queryset
|
elif not self.request.user.role in ['admin', 'moderator']:
|
||||||
|
queryset = queryset.filter(
|
||||||
class PageViewSet(viewsets.ModelViewSet):
|
approved=True
|
||||||
queryset = Page.objects.all()
|
) | Comment.objects.filter(
|
||||||
serializer_class = PageSerializer
|
author=self.request.user
|
||||||
permission_classes = [IsEditorOrAdmin]
|
)
|
||||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
return queryset
|
||||||
filterset_fields = ['status', 'author', 'category']
|
|
||||||
search_fields = ['title', 'content']
|
#obsługa widoków dla stron
|
||||||
ordering_fields = ['created_at', 'updated_at', 'title']
|
class PageViewSet(viewsets.ModelViewSet):
|
||||||
ordering = ['-created_at']
|
queryset = Page.objects.all()
|
||||||
|
serializer_class = PageSerializer
|
||||||
def perform_create(self, serializer):
|
permission_classes = [IsEditorOrAdmin]
|
||||||
serializer.save(author=self.request.user)
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||||
|
filterset_fields = ['status', 'author', 'category']
|
||||||
def get_queryset(self):
|
search_fields = ['title', 'content']
|
||||||
queryset = Page.objects.all()
|
ordering_fields = ['created_at', 'updated_at', 'title']
|
||||||
if not self.request.user.is_authenticated:
|
ordering = ['-created_at']
|
||||||
queryset = queryset.filter(status='published')
|
|
||||||
elif not self.request.user.role in ['admin', 'moderator']:
|
def perform_create(self, serializer):
|
||||||
queryset = queryset.filter(
|
serializer.save(author=self.request.user)
|
||||||
status='published'
|
|
||||||
) | Page.objects.filter(
|
def get_queryset(self):
|
||||||
author=self.request.user
|
queryset = Page.objects.all()
|
||||||
)
|
if not self.request.user.is_authenticated:
|
||||||
return queryset
|
queryset = queryset.filter(status='published')
|
||||||
|
elif not self.request.user.role in ['admin', 'moderator']:
|
||||||
class CategoryViewSet(viewsets.ModelViewSet):
|
queryset = queryset.filter(
|
||||||
queryset = Category.objects.all()
|
status='published'
|
||||||
serializer_class = CategorySerializer
|
) | Page.objects.filter(
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
author=self.request.user
|
||||||
filter_backends = [filters.SearchFilter]
|
)
|
||||||
search_fields = ['name', 'slug']
|
return queryset
|
||||||
|
|
||||||
def get_queryset(self):
|
# Obsługa kategorii
|
||||||
return Category.objects.all()
|
class CategoryViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Category.objects.all()
|
||||||
class UploadedImageViewSet(viewsets.ModelViewSet):
|
serializer_class = CategorySerializer
|
||||||
serializer_class = UploadedImageSerializer
|
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||||
parser_classes = (MultiPartParser, FormParser)
|
filter_backends = [filters.SearchFilter]
|
||||||
|
search_fields = ['name', 'slug']
|
||||||
def get_queryset(self):
|
|
||||||
return UploadedImage.objects.filter(user=self.request.user)
|
def get_queryset(self):
|
||||||
|
return Category.objects.all()
|
||||||
def get_serializer_context(self):
|
|
||||||
context = super().get_serializer_context()
|
# Obsługa przesyłania obrazów
|
||||||
context['request'] = self.request
|
class UploadedImageViewSet(viewsets.ModelViewSet):
|
||||||
return context
|
serializer_class = UploadedImageSerializer
|
||||||
|
parser_classes = (JSONParser, MultiPartParser, FormParser)
|
||||||
def perform_create(self, serializer):
|
permission_classes = [IsAuthenticated]
|
||||||
# Check daily upload quota (5MB = 5 * 1024 * 1024 bytes)
|
|
||||||
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024 # 5MB in bytes
|
def get_queryset(self):
|
||||||
|
return UploadedImage.objects.filter(user=self.request.user)
|
||||||
# Get current daily upload size
|
|
||||||
current_size = UploadedImage.get_user_daily_upload_size(self.request.user)
|
def get_serializer_context(self):
|
||||||
file_size = self.request.FILES['image'].size
|
context = super().get_serializer_context()
|
||||||
|
context['request'] = self.request
|
||||||
# Check if upload would exceed daily limit
|
return context
|
||||||
if current_size + file_size > DAILY_UPLOAD_LIMIT:
|
|
||||||
raise serializers.ValidationError({
|
def perform_create(self, serializer):
|
||||||
'image': 'Daily upload limit (5MB) exceeded. Please try again tomorrow.'
|
user = self.request.user
|
||||||
})
|
|
||||||
|
# Admin? – zero limitów
|
||||||
try:
|
if not user.is_staff:
|
||||||
instance = serializer.save(user=self.request.user, file_size=file_size)
|
# Limit: 5MB dziennie
|
||||||
|
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024
|
||||||
except Exception as e:
|
current_size = UploadedImage.get_user_daily_upload_size(user)
|
||||||
raise
|
file_size = self.request.FILES['image'].size
|
||||||
|
|
||||||
@action(detail=False, methods=['get'])
|
if current_size + file_size > DAILY_UPLOAD_LIMIT:
|
||||||
def quota(self, request):
|
raise serializers.ValidationError({
|
||||||
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024 # 5MB in bytes
|
'image': 'Daily upload limit (5MB) exceeded. Try again tomorrow.'
|
||||||
current_size = UploadedImage.get_user_daily_upload_size(request.user)
|
})
|
||||||
remaining = max(0, DAILY_UPLOAD_LIMIT - current_size)
|
|
||||||
|
# Limit 1 plik / minutę
|
||||||
return Response({
|
one_minute_ago = timezone.now() - timezone.timedelta(minutes=1)
|
||||||
'daily_limit': DAILY_UPLOAD_LIMIT,
|
if UploadedImage.objects.filter(user=user, uploaded_at__gte=one_minute_ago).exists():
|
||||||
'used': current_size,
|
raise serializers.ValidationError({
|
||||||
'remaining': remaining,
|
'image': 'Upload limit: 1 image per minute.'
|
||||||
'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"
|
serializer.save(user=user, file_size=self.request.FILES['image'].size)
|
||||||
})
|
|
||||||
|
def perform_update(self, serializer):
|
||||||
class TagViewSet(viewsets.ModelViewSet):
|
instance = serializer.instance
|
||||||
queryset = Tag.objects.all()
|
if instance.user != self.request.user and not self.request.user.is_staff:
|
||||||
serializer_class = TagSerializer
|
raise serializers.ValidationError("Brak uprawnień do edycji tego pliku.")
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
serializer.save()
|
||||||
filter_backends = [filters.SearchFilter]
|
|
||||||
search_fields = ['name', 'slug']
|
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)
|
||||||
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__/admin.cpython-312.pyc
Normal file
BIN
backend/core/__pycache__/admin.cpython-312.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__/models.cpython-312.pyc
Normal file
BIN
backend/core/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
@ -1,3 +1,3 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class CoreConfig(AppConfig):
|
class CoreConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'core'
|
name = 'core'
|
||||||
|
|||||||
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.
@ -1,3 +1,3 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|||||||
@ -1,60 +1,60 @@
|
|||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework_simplejwt',
|
'rest_framework_simplejwt',
|
||||||
'rest_framework_simplejwt.token_blacklist',
|
'rest_framework_simplejwt.token_blacklist',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'users',
|
'users',
|
||||||
'content',
|
'content',
|
||||||
]
|
]
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
],
|
],
|
||||||
'DEFAULT_PERMISSION_CLASSES': [
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
'rest_framework.permissions.IsAuthenticated',
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
],
|
],
|
||||||
'DEFAULT_FILTER_BACKENDS': [
|
'DEFAULT_FILTER_BACKENDS': [
|
||||||
'django_filters.rest_framework.DjangoFilterBackend',
|
'django_filters.rest_framework.DjangoFilterBackend',
|
||||||
'rest_framework.filters.SearchFilter',
|
'rest_framework.filters.SearchFilter',
|
||||||
'rest_framework.filters.OrderingFilter',
|
'rest_framework.filters.OrderingFilter',
|
||||||
],
|
],
|
||||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||||
'PAGE_SIZE': 10
|
'PAGE_SIZE': 10
|
||||||
}
|
}
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
SIMPLE_JWT = {
|
SIMPLE_JWT = {
|
||||||
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
|
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
|
||||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
|
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
|
||||||
'ROTATE_REFRESH_TOKENS': True,
|
'ROTATE_REFRESH_TOKENS': True,
|
||||||
'BLACKLIST_AFTER_ROTATION': True,
|
'BLACKLIST_AFTER_ROTATION': True,
|
||||||
'UPDATE_LAST_LOGIN': False,
|
'UPDATE_LAST_LOGIN': False,
|
||||||
|
|
||||||
'ALGORITHM': 'HS256',
|
'ALGORITHM': 'HS256',
|
||||||
'SIGNING_KEY': SECRET_KEY,
|
'SIGNING_KEY': SECRET_KEY,
|
||||||
'VERIFYING_KEY': None,
|
'VERIFYING_KEY': None,
|
||||||
'AUDIENCE': None,
|
'AUDIENCE': None,
|
||||||
'ISSUER': None,
|
'ISSUER': None,
|
||||||
'JWK_URL': None,
|
'JWK_URL': None,
|
||||||
'LEEWAY': 0,
|
'LEEWAY': 0,
|
||||||
|
|
||||||
'AUTH_HEADER_TYPES': ('Bearer',),
|
'AUTH_HEADER_TYPES': ('Bearer',),
|
||||||
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
|
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
|
||||||
'USER_ID_FIELD': 'id',
|
'USER_ID_FIELD': 'id',
|
||||||
'USER_ID_CLAIM': 'user_id',
|
'USER_ID_CLAIM': 'user_id',
|
||||||
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
|
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
|
||||||
|
|
||||||
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
|
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
|
||||||
'TOKEN_TYPE_CLAIM': 'token_type',
|
'TOKEN_TYPE_CLAIM': 'token_type',
|
||||||
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
|
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
|
||||||
|
|
||||||
'JTI_CLAIM': 'jti',
|
'JTI_CLAIM': 'jti',
|
||||||
}
|
}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|||||||
Binary file not shown.
BIN
frontend/src/.DS_Store → backend/formulas/.DS_Store
vendored
BIN
frontend/src/.DS_Store → backend/formulas/.DS_Store
vendored
Binary file not shown.
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/formulas/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/formulas/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
9
backend/formulas/models/__init__.py
Normal file
9
backend/formulas/models/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from .formula import Formula
|
||||||
|
from .symbol import Symbol
|
||||||
|
from .category import FormulaCategory
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Formula",
|
||||||
|
"Symbol",
|
||||||
|
"FormulaCategory",
|
||||||
|
]
|
||||||
BIN
backend/formulas/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/formulas/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/models/__pycache__/category.cpython-312.pyc
Normal file
BIN
backend/formulas/models/__pycache__/category.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/models/__pycache__/favorite.cpython-312.pyc
Normal file
BIN
backend/formulas/models/__pycache__/favorite.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/models/__pycache__/formula.cpython-312.pyc
Normal file
BIN
backend/formulas/models/__pycache__/formula.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/formulas/models/__pycache__/symbol.cpython-312.pyc
Normal file
BIN
backend/formulas/models/__pycache__/symbol.cpython-312.pyc
Normal file
Binary file not shown.
12
backend/formulas/models/category.py
Normal file
12
backend/formulas/models/category.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class FormulaCategory(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
prefix = models.PositiveIntegerField(unique=True) # cyfra `x` w kodzie xyz
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Kategoria wzoru"
|
||||||
|
verbose_name_plural = "Kategorie wzorów"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.prefix} - {self.name}"
|
||||||
17
backend/formulas/models/favorite.py
Normal file
17
backend/formulas/models/favorite.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# backend/formulas/models/favorite_formula.py
|
||||||
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
from formulas.models.formula import Formula
|
||||||
|
|
||||||
|
class FavoriteFormula(models.Model):
|
||||||
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
|
formula = models.ForeignKey(Formula, on_delete=models.CASCADE)
|
||||||
|
added_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('user', 'formula')
|
||||||
|
verbose_name = "Ulubiony wzór"
|
||||||
|
verbose_name_plural = "Ulubione wzory"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.username} ❤️ {self.formula.code}"
|
||||||
38
backend/formulas/models/formula.py
Normal file
38
backend/formulas/models/formula.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from django.db import models
|
||||||
|
from .category import FormulaCategory
|
||||||
|
from users.models import User
|
||||||
|
from django.utils import timezone
|
||||||
|
from .symbol import Symbol
|
||||||
|
|
||||||
|
class Formula(models.Model):
|
||||||
|
formula_id = models.CharField(max_length=100)
|
||||||
|
title = models.CharField(max_length=255)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
meta = models.TextField(blank=True)
|
||||||
|
category = models.ForeignKey(FormulaCategory, on_delete=models.PROTECT)
|
||||||
|
tags = models.JSONField(default=list)
|
||||||
|
latex = models.TextField()
|
||||||
|
raw_latex = models.TextField()
|
||||||
|
symbols = models.ManyToManyField(Symbol, related_name='formulas')
|
||||||
|
calculator = models.CharField(max_length=100)
|
||||||
|
revision = models.PositiveIntegerField(default=1)
|
||||||
|
code = models.CharField(max_length=10, unique=True)
|
||||||
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||||
|
created_at = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.code:
|
||||||
|
base_prefix = f"{self.category.prefix:02d}"
|
||||||
|
existing_codes = Formula.objects.filter(category=self.category).values_list('code', flat=True)
|
||||||
|
index = 1
|
||||||
|
while True:
|
||||||
|
proposed_code = f"{base_prefix}{index:02d}{self.revision:02d}"
|
||||||
|
if proposed_code not in existing_codes:
|
||||||
|
self.code = proposed_code
|
||||||
|
break
|
||||||
|
index += 1
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.title} ({self.code})"
|
||||||
25
backend/formulas/models/symbol.py
Normal file
25
backend/formulas/models/symbol.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from django.db import models
|
||||||
|
from .category import FormulaCategory
|
||||||
|
|
||||||
|
class Symbol(models.Model):
|
||||||
|
symbol_id = models.CharField(max_length=10) # np. F
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
unit = models.CharField(max_length=50)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
category = models.ForeignKey(FormulaCategory, on_delete=models.PROTECT)
|
||||||
|
tags = models.JSONField(default=list, blank=True)
|
||||||
|
revision = models.PositiveIntegerField(default=1)
|
||||||
|
code = models.CharField(max_length=10, unique=True, editable=False)
|
||||||
|
meta = models.TextField(blank=True) # np. meta-opis dla SEO / edytora
|
||||||
|
|
||||||
|
|
||||||
|
#Zaspis zmiennej z code
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.code:
|
||||||
|
count = Symbol.objects.filter(category=self.category).count() + 1
|
||||||
|
self.code = f"{self.category.prefix:02d}{count:02d}{self.revision:02d}"
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
#Definiowanie czym jest "self"
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} ({self.symbol_id}) [{self.code}]"
|
||||||
9
backend/formulas/permissions.py
Normal file
9
backend/formulas/permissions.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from rest_framework.permissions import BasePermission
|
||||||
|
|
||||||
|
class IsAdminOrEditor(BasePermission):
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return request.method in ("GET", "HEAD", "OPTIONS") or (
|
||||||
|
request.user and request.user.is_authenticated and (
|
||||||
|
request.user.is_superuser or getattr(request.user, "role", None) == "editor"
|
||||||
|
)
|
||||||
|
)
|
||||||
9
backend/formulas/serializers/__init__.py
Normal file
9
backend/formulas/serializers/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from .formula import FormulaSerializer
|
||||||
|
from .symbol import SymbolSerializer
|
||||||
|
from .category import FormulaCategorySerializer
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"FormulaSerializer",
|
||||||
|
"SymbolSerializer",
|
||||||
|
"FormulaCategorySerializer",
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
BIN
backend/formulas/serializers/__pycache__/formula.cpython-312.pyc
Normal file
BIN
backend/formulas/serializers/__pycache__/formula.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/formulas/serializers/__pycache__/symbol.cpython-312.pyc
Normal file
BIN
backend/formulas/serializers/__pycache__/symbol.cpython-312.pyc
Normal file
Binary file not shown.
7
backend/formulas/serializers/category.py
Normal file
7
backend/formulas/serializers/category.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from formulas.models.category import FormulaCategory
|
||||||
|
|
||||||
|
class FormulaCategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = FormulaCategory
|
||||||
|
fields = '__all__'
|
||||||
8
backend/formulas/serializers/formula.py
Normal file
8
backend/formulas/serializers/formula.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from formulas.models.formula import Formula
|
||||||
|
|
||||||
|
class FormulaSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Formula
|
||||||
|
fields = "__all__"
|
||||||
|
read_only_fields = ('code', 'created_by', 'created_at')
|
||||||
8
backend/formulas/serializers/symbol.py
Normal file
8
backend/formulas/serializers/symbol.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# backend/formulas/serializers/symbol.py
|
||||||
|
from rest_framework import serializers
|
||||||
|
from formulas.models.symbol import Symbol
|
||||||
|
|
||||||
|
class SymbolSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Symbol
|
||||||
|
fields = "__all__"
|
||||||
3
backend/formulas/tests.py
Normal file
3
backend/formulas/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
19
backend/formulas/urls.py
Normal file
19
backend/formulas/urls.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.urls import path, include
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from .views import SymbolViewSet, FormulaViewSet, FormulaCategoryViewSet
|
||||||
|
|
||||||
|
# 🔧 Router z obsługą lookup_field='code'
|
||||||
|
class LookupByCodeRouter(DefaultRouter):
|
||||||
|
def get_lookup_regex(self, viewset, lookup_prefix=''):
|
||||||
|
if hasattr(viewset, 'lookup_field') and viewset.lookup_field == 'code':
|
||||||
|
return r'(?P<code>[^/.]+)'
|
||||||
|
return super().get_lookup_regex(viewset, lookup_prefix)
|
||||||
|
|
||||||
|
router = LookupByCodeRouter()
|
||||||
|
router.register(r'symbols', SymbolViewSet, basename='symbol')
|
||||||
|
router.register(r'formulas', FormulaViewSet, basename='formula')
|
||||||
|
router.register(r'formulascategories', FormulaCategoryViewSet, basename='category')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
]
|
||||||
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