Compare commits
No commits in common. "zmiany-mariusz" and "main" have entirely different histories.
zmiany-mar
...
main
106
backend.log
106
backend.log
@ -1,106 +0,0 @@
|
|||||||
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
BIN
backend/.DS_Store
vendored
Binary file not shown.
3
backend/.gitignore
vendored
3
backend/.gitignore
vendored
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
#Ignorowanie plików i katalogów w projekcie backend
|
|
||||||
# Created by venv; see https://docs.python.org/3/library/venv.html
|
# Created by venv; see https://docs.python.org/3/library/venv.html
|
||||||
/bin
|
/bin
|
||||||
/lib
|
/lib
|
||||||
@ -9,4 +7,3 @@
|
|||||||
/doc
|
/doc
|
||||||
/html
|
/html
|
||||||
pyvenv.cfg
|
pyvenv.cfg
|
||||||
/venv
|
|
||||||
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.
@ -1,4 +1,3 @@
|
|||||||
#Obsługa panelu administracyjnego dla aplikacji content
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#Ogbługa aplikacji content w Django
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
# Definicja klasy konfiguracyjnej aplikacji Django dla modułu content.
|
|
||||||
class ContentConfig(AppConfig):
|
class ContentConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'content'
|
name = 'content'
|
||||||
|
|||||||
@ -1,71 +0,0 @@
|
|||||||
# 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),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# 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),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# 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.
Binary file not shown.
@ -1,30 +1,16 @@
|
|||||||
#Modele do obsługi treści: takie jak posty, strony, komentarze i przesłane obrazy.
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.validators import FileExtensionValidator
|
from django.core.validators import FileExtensionValidator
|
||||||
from django.utils.text import slugify
|
|
||||||
|
|
||||||
#Obsługa kategorii
|
|
||||||
class Category(models.Model):
|
class Category(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
slug = models.SlugField(unique=True, blank=True)
|
slug = models.SlugField(unique=True)
|
||||||
description = models.TextField(blank=True, null=True)
|
|
||||||
parent_category = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, related_name='subcategories')
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
meta_description = models.CharField(max_length=160, blank=True, null=True, help_text="Opis dla wyszukiwarek (SEO)")
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if not self.slug:
|
|
||||||
self.slug = slugify(self.name)
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
#Obsługa treści
|
|
||||||
class AbstractContent(models.Model):
|
class AbstractContent(models.Model):
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
('draft', 'Draft'),
|
('draft', 'Draft'),
|
||||||
@ -40,24 +26,18 @@ class AbstractContent(models.Model):
|
|||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
category = models.ForeignKey('Category', on_delete=models.CASCADE)
|
category = models.ForeignKey('Category', on_delete=models.CASCADE)
|
||||||
tags = models.ManyToManyField('Tag', blank=True)
|
tags = models.ManyToManyField('Tag', blank=True)
|
||||||
meta_title = models.CharField(max_length=70, blank=True, help_text="Tytuł wyświetlany w wyszukiwarkach. Domyślnie będzie użyty title.")
|
|
||||||
meta_description = models.CharField(max_length=160, blank=True, null=True, help_text="Opis dla wyszukiwarek (SEO)")
|
|
||||||
meta_image = models.URLField(blank=True, null=True, help_text="Obrazek do podglądu w linkach (np. na Facebooku)")
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
#Obsługa postów i stron
|
|
||||||
class Post(AbstractContent):
|
class Post(AbstractContent):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Page(AbstractContent):
|
class Page(AbstractContent):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
#Obsługa komentarzy
|
|
||||||
class Comment(models.Model):
|
class Comment(models.Model):
|
||||||
content = models.TextField(max_length=2000)
|
content = models.TextField(max_length=2000)
|
||||||
approved = models.BooleanField(default=False)
|
approved = models.BooleanField(default=False)
|
||||||
@ -66,7 +46,6 @@ class Comment(models.Model):
|
|||||||
page = models.ForeignKey(Page, null=True, blank=True, on_delete=models.CASCADE)
|
page = models.ForeignKey(Page, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
#Obsługa przesłanych obrazów
|
|
||||||
class UploadedImage(models.Model):
|
class UploadedImage(models.Model):
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
image = models.ImageField(
|
image = models.ImageField(
|
||||||
@ -76,8 +55,6 @@ class UploadedImage(models.Model):
|
|||||||
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() # Size in bytes
|
file_size = models.PositiveIntegerField() # Size in bytes
|
||||||
category = models.ForeignKey('Category', on_delete=models.CASCADE, default=1)
|
|
||||||
description = models.TextField(blank=True, null=True, help_text="Opis obrazka")
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-uploaded_at']
|
ordering = ['-uploaded_at']
|
||||||
@ -104,7 +81,6 @@ class UploadedImage(models.Model):
|
|||||||
uploaded_at__lt=tomorrow
|
uploaded_at__lt=tomorrow
|
||||||
).aggregate(total_size=models.Sum('file_size'))['total_size'] or 0
|
).aggregate(total_size=models.Sum('file_size'))['total_size'] or 0
|
||||||
|
|
||||||
#Obsługa tagów
|
|
||||||
class Tag(models.Model):
|
class Tag(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
|
|||||||
@ -1,40 +1,25 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from django.utils.text import slugify
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from .models import Post, Comment, Page, Category, UploadedImage, Tag
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from .models import (
|
|
||||||
UploadedImage,
|
|
||||||
Category,
|
|
||||||
Post,
|
|
||||||
Comment,
|
|
||||||
Page,
|
|
||||||
Tag,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Serialized do tagów
|
|
||||||
class TagSerializer(serializers.ModelSerializer):
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug']
|
||||||
read_only_fields = ['id']
|
read_only_fields = ['id']
|
||||||
|
|
||||||
# Serialized do kategorii
|
|
||||||
class CategorySerializer(serializers.ModelSerializer):
|
class CategorySerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Category
|
model = Category
|
||||||
fields = ['id', 'name', 'slug', 'description', 'parent_category', 'created_at']
|
fields = ['id', 'name', 'slug']
|
||||||
read_only_fields = ['id', 'created_at']
|
read_only_fields = ['id']
|
||||||
|
|
||||||
# Serialized do użytkowników
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['id', 'username', 'first_name', 'last_name']
|
fields = ['id', 'username', 'first_name', 'last_name']
|
||||||
read_only_fields = ['id']
|
read_only_fields = ['id']
|
||||||
|
|
||||||
# Serialized do komentarzy
|
|
||||||
class CommentSerializer(serializers.ModelSerializer):
|
class CommentSerializer(serializers.ModelSerializer):
|
||||||
author = UserSerializer(read_only=True)
|
author = UserSerializer(read_only=True)
|
||||||
|
|
||||||
@ -43,7 +28,6 @@ class CommentSerializer(serializers.ModelSerializer):
|
|||||||
fields = ['id', 'content', 'author', 'post', 'page', 'approved', 'created_at']
|
fields = ['id', 'content', 'author', 'post', 'page', 'approved', 'created_at']
|
||||||
read_only_fields = ['id', 'created_at', 'author']
|
read_only_fields = ['id', 'created_at', 'author']
|
||||||
|
|
||||||
# Serialized do postów
|
|
||||||
class PostSerializer(serializers.ModelSerializer):
|
class PostSerializer(serializers.ModelSerializer):
|
||||||
author = UserSerializer(read_only=True)
|
author = UserSerializer(read_only=True)
|
||||||
category = CategorySerializer(read_only=True)
|
category = CategorySerializer(read_only=True)
|
||||||
@ -63,34 +47,19 @@ class PostSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
slug = serializers.SlugField(required=True)
|
slug = serializers.SlugField(required=True)
|
||||||
|
|
||||||
meta_title = serializers.CharField(required=False, allow_blank=True)
|
|
||||||
meta_description = serializers.CharField(required=False, allow_blank=True)
|
|
||||||
meta_image = serializers.CharField(required=False, allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Post
|
model = Post
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'title', 'content', 'status', 'author',
|
'id', 'title', 'content', 'status', 'author',
|
||||||
'category', 'category_id', 'comments',
|
'category', 'category_id', 'comments',
|
||||||
'created_at', 'updated_at', 'tags', 'tag_ids', 'slug',
|
'created_at', 'updated_at', 'tags', 'tag_ids', 'slug'
|
||||||
'meta_title', 'meta_description', 'meta_image',]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'url': {'lookup_field': 'slug'}
|
'url': {'lookup_field': 'slug'}
|
||||||
}
|
}
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
if not validated_data.get('slug'):
|
|
||||||
validated_data['slug'] = slugify(validated_data.get('title', ''))
|
|
||||||
return super().create(validated_data)
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
if not validated_data.get('slug') and validated_data.get('title'):
|
|
||||||
validated_data['slug'] = slugify(validated_data['title'])
|
|
||||||
return super().update(instance, validated_data)
|
|
||||||
|
|
||||||
# Serialized do stron
|
|
||||||
class PageSerializer(serializers.ModelSerializer):
|
class PageSerializer(serializers.ModelSerializer):
|
||||||
author = UserSerializer(read_only=True)
|
author = UserSerializer(read_only=True)
|
||||||
category = CategorySerializer(read_only=True)
|
category = CategorySerializer(read_only=True)
|
||||||
@ -101,25 +70,19 @@ class PageSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
comments = CommentSerializer(many=True, read_only=True)
|
comments = CommentSerializer(many=True, read_only=True)
|
||||||
slug = serializers.SlugField(required=True)
|
slug = serializers.SlugField(required=True)
|
||||||
|
|
||||||
meta_title = serializers.CharField(required=False, allow_blank=True)
|
|
||||||
meta_description = serializers.CharField(required=False, allow_blank=True)
|
|
||||||
meta_image = serializers.ImageField(required=False, allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Page
|
model = Page
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'title', 'content', 'status', 'author',
|
'id', 'title', 'content', 'status', 'author',
|
||||||
'category', 'category_id', 'comments',
|
'category', 'category_id', 'comments',
|
||||||
'created_at', 'updated_at', 'slug',
|
'created_at', 'updated_at', 'slug'
|
||||||
'meta_title', 'meta_description', 'meta_image']
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'author', 'slug']
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'url': {'lookup_field': 'slug'}
|
'url': {'lookup_field': 'slug'}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Serialized do przesłanych obrazów
|
|
||||||
class UploadedImageSerializer(serializers.ModelSerializer):
|
class UploadedImageSerializer(serializers.ModelSerializer):
|
||||||
url = serializers.SerializerMethodField()
|
url = serializers.SerializerMethodField()
|
||||||
file_size_display = serializers.SerializerMethodField()
|
file_size_display = serializers.SerializerMethodField()
|
||||||
@ -127,45 +90,9 @@ class UploadedImageSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UploadedImage
|
model = UploadedImage
|
||||||
fields = [
|
fields = ['id', 'title', 'image', 'url', 'uploaded_at', 'file_size', 'file_size_display']
|
||||||
'id',
|
|
||||||
'title',
|
|
||||||
'image',
|
|
||||||
'url',
|
|
||||||
'uploaded_at',
|
|
||||||
'file_size',
|
|
||||||
'file_size_display',
|
|
||||||
'category',
|
|
||||||
'description',
|
|
||||||
]
|
|
||||||
read_only_fields = ['file_size', 'uploaded_at', 'url']
|
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):
|
def get_url(self, obj):
|
||||||
request = self.context.get('request')
|
request = self.context.get('request')
|
||||||
if obj.image and hasattr(obj.image, 'url'):
|
if obj.image and hasattr(obj.image, 'url'):
|
||||||
@ -185,7 +112,4 @@ class UploadedImageSerializer(serializers.ModelSerializer):
|
|||||||
raise serializers.ValidationError("Image file is required.")
|
raise serializers.ValidationError("Image file is required.")
|
||||||
return value
|
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,4 +1,3 @@
|
|||||||
#Obsługa testów dla aplikacji content
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
#Obsługa ścieżek dla aplikacji content.
|
|
||||||
|
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from .views import PostViewSet, CommentViewSet, PageViewSet, CategoryViewSet, UploadedImageViewSet, TagViewSet, SearchView
|
from .views import PostViewSet, CommentViewSet, PageViewSet, CategoryViewSet, UploadedImageViewSet, TagViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'posts', PostViewSet, basename='post')
|
router.register(r'posts', PostViewSet, basename='post')
|
||||||
@ -11,8 +9,6 @@ router.register(r'pages', PageViewSet, basename='page')
|
|||||||
router.register(r'categories', CategoryViewSet, basename='category')
|
router.register(r'categories', CategoryViewSet, basename='category')
|
||||||
router.register(r'images', UploadedImageViewSet, basename='image')
|
router.register(r'images', UploadedImageViewSet, basename='image')
|
||||||
router.register(r'tags', TagViewSet, basename='tag')
|
router.register(r'tags', TagViewSet, basename='tag')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
path('search/', SearchView.as_view(), name='search'),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,48 +1,44 @@
|
|||||||
#Obsługa widoków dla aplikacji content.
|
from rest_framework import viewsets, status, filters
|
||||||
import os
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from rest_framework import viewsets, status, filters, serializers
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import (
|
|
||||||
IsAuthenticated,
|
|
||||||
IsAuthenticatedOrReadOnly,
|
|
||||||
AllowAny,
|
|
||||||
)
|
|
||||||
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from .models import Post, Comment, Page, Category, UploadedImage, Tag
|
||||||
|
from .serializers import PostSerializer, CommentSerializer, PageSerializer, CategorySerializer, UploadedImageSerializer, TagSerializer
|
||||||
|
from users.permissions import IsAuthorOrReadOnly, IsEditorOrAdmin
|
||||||
|
from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
||||||
|
from rest_framework.parsers import MultiPartParser, FormParser
|
||||||
|
from django.conf import settings
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import (
|
|
||||||
Post,
|
|
||||||
Comment,
|
|
||||||
Page,
|
|
||||||
Category,
|
|
||||||
UploadedImage,
|
|
||||||
Tag,
|
|
||||||
)
|
|
||||||
from .serializers import (
|
|
||||||
PostSerializer,
|
|
||||||
CommentSerializer,
|
|
||||||
PageSerializer,
|
|
||||||
CategorySerializer,
|
|
||||||
UploadedImageSerializer,
|
|
||||||
TagSerializer,
|
|
||||||
)
|
|
||||||
from users.permissions import IsAuthorOrReadOnly, IsEditorOrAdmin, IsAdminOrReadOnly
|
|
||||||
|
|
||||||
# Obsługa logiczna widoku dla postów
|
|
||||||
class PostViewSet(viewsets.ModelViewSet):
|
class PostViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint for managing blog posts.
|
||||||
|
|
||||||
|
list:
|
||||||
|
Return a list of all posts. Results can be filtered by:
|
||||||
|
- status (draft/pending/published)
|
||||||
|
- author
|
||||||
|
- category
|
||||||
|
- created_at (date range)
|
||||||
|
|
||||||
|
create:
|
||||||
|
Create a new post. Requires authentication and appropriate permissions.
|
||||||
|
|
||||||
|
retrieve:
|
||||||
|
Return the details of a specific post by ID.
|
||||||
|
|
||||||
|
update:
|
||||||
|
Update all fields of a specific post. Requires authentication and appropriate permissions.
|
||||||
|
|
||||||
|
partial_update:
|
||||||
|
Update one or more fields of a specific post. Requires authentication and appropriate permissions.
|
||||||
|
|
||||||
|
destroy:
|
||||||
|
Delete a specific post. Requires authentication and appropriate permissions.
|
||||||
|
"""
|
||||||
queryset = Post.objects.all()
|
queryset = Post.objects.all()
|
||||||
serializer_class = PostSerializer
|
serializer_class = PostSerializer
|
||||||
permission_classes = [IsAdminOrReadOnly]
|
permission_classes = [IsAuthorOrReadOnly]
|
||||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||||
filterset_fields = ['status', 'author', 'category']
|
filterset_fields = ['status', 'author', 'category']
|
||||||
search_fields = ['title', 'content']
|
search_fields = ['title', 'content']
|
||||||
@ -67,8 +63,7 @@ class PostViewSet(viewsets.ModelViewSet):
|
|||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
print(serializer.validated_data)
|
print(serializer.validated_data)
|
||||||
return super().perform_update(serializer)
|
return super().perform_update(serializer)
|
||||||
|
class CommentViewSet(viewsets.ModelViewSet):
|
||||||
# Obsługa komentarzy do postów i stron
|
|
||||||
"""
|
"""
|
||||||
API endpoint for managing comments on posts and pages.
|
API endpoint for managing comments on posts and pages.
|
||||||
|
|
||||||
@ -94,7 +89,6 @@ class PostViewSet(viewsets.ModelViewSet):
|
|||||||
destroy:
|
destroy:
|
||||||
Delete a specific comment. Requires authentication and appropriate permissions.
|
Delete a specific comment. Requires authentication and appropriate permissions.
|
||||||
"""
|
"""
|
||||||
class CommentViewSet(viewsets.ModelViewSet):
|
|
||||||
queryset = Comment.objects.all()
|
queryset = Comment.objects.all()
|
||||||
serializer_class = CommentSerializer
|
serializer_class = CommentSerializer
|
||||||
permission_classes = [IsAuthorOrReadOnly]
|
permission_classes = [IsAuthorOrReadOnly]
|
||||||
@ -118,7 +112,6 @@ class CommentViewSet(viewsets.ModelViewSet):
|
|||||||
)
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
#obsługa widoków dla stron
|
|
||||||
class PageViewSet(viewsets.ModelViewSet):
|
class PageViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Page.objects.all()
|
queryset = Page.objects.all()
|
||||||
serializer_class = PageSerializer
|
serializer_class = PageSerializer
|
||||||
@ -144,7 +137,6 @@ class PageViewSet(viewsets.ModelViewSet):
|
|||||||
)
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
# Obsługa kategorii
|
|
||||||
class CategoryViewSet(viewsets.ModelViewSet):
|
class CategoryViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Category.objects.all()
|
queryset = Category.objects.all()
|
||||||
serializer_class = CategorySerializer
|
serializer_class = CategorySerializer
|
||||||
@ -155,11 +147,9 @@ class CategoryViewSet(viewsets.ModelViewSet):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Category.objects.all()
|
return Category.objects.all()
|
||||||
|
|
||||||
# Obsługa przesyłania obrazów
|
|
||||||
class UploadedImageViewSet(viewsets.ModelViewSet):
|
class UploadedImageViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = UploadedImageSerializer
|
serializer_class = UploadedImageSerializer
|
||||||
parser_classes = (JSONParser, MultiPartParser, FormParser)
|
parser_classes = (MultiPartParser, FormParser)
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return UploadedImage.objects.filter(user=self.request.user)
|
return UploadedImage.objects.filter(user=self.request.user)
|
||||||
@ -170,51 +160,28 @@ class UploadedImageViewSet(viewsets.ModelViewSet):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
user = self.request.user
|
# Check daily upload quota (5MB = 5 * 1024 * 1024 bytes)
|
||||||
|
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024 # 5MB in bytes
|
||||||
|
|
||||||
# Admin? – zero limitów
|
# Get current daily upload size
|
||||||
if not user.is_staff:
|
current_size = UploadedImage.get_user_daily_upload_size(self.request.user)
|
||||||
# Limit: 5MB dziennie
|
file_size = self.request.FILES['image'].size
|
||||||
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024
|
|
||||||
current_size = UploadedImage.get_user_daily_upload_size(user)
|
|
||||||
file_size = self.request.FILES['image'].size
|
|
||||||
|
|
||||||
if current_size + file_size > DAILY_UPLOAD_LIMIT:
|
# Check if upload would exceed daily limit
|
||||||
raise serializers.ValidationError({
|
if current_size + file_size > DAILY_UPLOAD_LIMIT:
|
||||||
'image': 'Daily upload limit (5MB) exceeded. Try again tomorrow.'
|
raise serializers.ValidationError({
|
||||||
})
|
'image': 'Daily upload limit (5MB) exceeded. Please try again tomorrow.'
|
||||||
|
})
|
||||||
|
|
||||||
# Limit 1 plik / minutę
|
try:
|
||||||
one_minute_ago = timezone.now() - timezone.timedelta(minutes=1)
|
instance = serializer.save(user=self.request.user, file_size=file_size)
|
||||||
if UploadedImage.objects.filter(user=user, uploaded_at__gte=one_minute_ago).exists():
|
|
||||||
raise serializers.ValidationError({
|
|
||||||
'image': 'Upload limit: 1 image per minute.'
|
|
||||||
})
|
|
||||||
|
|
||||||
serializer.save(user=user, file_size=self.request.FILES['image'].size)
|
except Exception as e:
|
||||||
|
raise
|
||||||
def perform_update(self, serializer):
|
|
||||||
instance = serializer.instance
|
|
||||||
if instance.user != self.request.user and not self.request.user.is_staff:
|
|
||||||
raise serializers.ValidationError("Brak uprawnień do edycji tego pliku.")
|
|
||||||
serializer.save()
|
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
|
||||||
obj = self.get_object()
|
|
||||||
if obj.user != request.user and not request.user.is_staff:
|
|
||||||
return Response({'detail': 'Brak uprawnień do usunięcia.'}, status=403)
|
|
||||||
|
|
||||||
# Usuń fizyczny plik z dysku
|
|
||||||
image_path = obj.image.path
|
|
||||||
if os.path.isfile(image_path):
|
|
||||||
os.remove(image_path)
|
|
||||||
|
|
||||||
obj.delete()
|
|
||||||
return Response({'detail': 'Plik usunięty.'}, status=200)
|
|
||||||
|
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def quota(self, request):
|
def quota(self, request):
|
||||||
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024
|
DAILY_UPLOAD_LIMIT = 5 * 1024 * 1024 # 5MB in bytes
|
||||||
current_size = UploadedImage.get_user_daily_upload_size(request.user)
|
current_size = UploadedImage.get_user_daily_upload_size(request.user)
|
||||||
remaining = max(0, DAILY_UPLOAD_LIMIT - current_size)
|
remaining = max(0, DAILY_UPLOAD_LIMIT - current_size)
|
||||||
|
|
||||||
@ -227,62 +194,9 @@ class UploadedImageViewSet(viewsets.ModelViewSet):
|
|||||||
'remaining_display': f"{remaining / (1024 * 1024):.1f}MB"
|
'remaining_display': f"{remaining / (1024 * 1024):.1f}MB"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# Obsługa tagów
|
|
||||||
class TagViewSet(viewsets.ModelViewSet):
|
class TagViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Tag.objects.all()
|
queryset = Tag.objects.all()
|
||||||
serializer_class = TagSerializer
|
serializer_class = TagSerializer
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||||
filter_backends = [filters.SearchFilter]
|
filter_backends = [filters.SearchFilter]
|
||||||
search_fields = ['name', 'slug']
|
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)
|
|
||||||
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.
@ -1,3 +0,0 @@
|
|||||||
from .formula import FormulaAdmin
|
|
||||||
from .symbol import SymbolAdmin
|
|
||||||
from .category import FormulaCategoryAdmin
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,8 +0,0 @@
|
|||||||
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",)
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from formulas.models.formula import Formula
|
|
||||||
|
|
||||||
@admin.register(Formula)
|
|
||||||
class FormulaAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("formula_id", "title", "code", "revision", "category")
|
|
||||||
list_filter = ("category", "revision")
|
|
||||||
search_fields = ("formula_id", "title", "code", "description")
|
|
||||||
readonly_fields = ("code", "created_at")
|
|
||||||
ordering = ("code",)
|
|
||||||
Binary file not shown.
@ -1,10 +0,0 @@
|
|||||||
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",)
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class FormulasConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'formulas'
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
# 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)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
# 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')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# 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'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# 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'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,9 +0,0 @@
|
|||||||
from .formula import Formula
|
|
||||||
from .symbol import Symbol
|
|
||||||
from .category import FormulaCategory
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"Formula",
|
|
||||||
"Symbol",
|
|
||||||
"FormulaCategory",
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,12 +0,0 @@
|
|||||||
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}"
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# 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}"
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
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})"
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
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}]"
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
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"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
from .formula import FormulaSerializer
|
|
||||||
from .symbol import SymbolSerializer
|
|
||||||
from .category import FormulaCategorySerializer
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"FormulaSerializer",
|
|
||||||
"SymbolSerializer",
|
|
||||||
"FormulaCategorySerializer",
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +0,0 @@
|
|||||||
from rest_framework import serializers
|
|
||||||
from formulas.models.category import FormulaCategory
|
|
||||||
|
|
||||||
class FormulaCategorySerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = FormulaCategory
|
|
||||||
fields = '__all__'
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
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')
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
# 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__"
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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)),
|
|
||||||
]
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
from .formula import FormulaViewSet
|
|
||||||
from .symbol import SymbolViewSet
|
|
||||||
from .category import FormulaCategoryViewSet
|
|
||||||
from .favorite import FavoriteFormulaViewSet
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"FormulaViewSet",
|
|
||||||
"SymbolViewSet",
|
|
||||||
"FormulaCategoryViewSet",
|
|
||||||
"FavoriteFormulaViewSet",
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,11 +0,0 @@
|
|||||||
# backend/formulas/views/category.py
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
|
||||||
from formulas.models.category import FormulaCategory
|
|
||||||
from formulas.serializers.category import FormulaCategorySerializer
|
|
||||||
from formulas.permissions import IsAdminOrEditor
|
|
||||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly
|
|
||||||
|
|
||||||
class FormulaCategoryViewSet(ModelViewSet):
|
|
||||||
queryset = FormulaCategory.objects.all()
|
|
||||||
serializer_class = FormulaCategorySerializer
|
|
||||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
|
||||||
Binary file not shown.
@ -1,40 +0,0 @@
|
|||||||
# backend/formulas/views/favorite.py
|
|
||||||
from rest_framework import status, viewsets
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from formulas.models.formula import Formula
|
|
||||||
from formulas.models.favorite import FavoriteFormula
|
|
||||||
from formulas.serializers.formula import FormulaSerializer
|
|
||||||
|
|
||||||
class FavoriteFormulaViewSet(viewsets.ViewSet):
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
|
|
||||||
@action(detail=False, methods=['get'])
|
|
||||||
def list(self, request):
|
|
||||||
favorites = FavoriteFormula.objects.filter(user=request.user)
|
|
||||||
formulas = [fav.formula for fav in favorites]
|
|
||||||
serializer = FormulaSerializer(formulas, many=True)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
|
||||||
def add(self, request, pk=None):
|
|
||||||
try:
|
|
||||||
formula = Formula.objects.get(pk=pk)
|
|
||||||
except Formula.DoesNotExist:
|
|
||||||
return Response({"detail": "Formula not found."}, status=404)
|
|
||||||
|
|
||||||
fav, created = FavoriteFormula.objects.get_or_create(user=request.user, formula=formula)
|
|
||||||
if not created:
|
|
||||||
return Response({"detail": "Already in favorites."}, status=400)
|
|
||||||
|
|
||||||
return Response({"detail": "Added to favorites."}, status=200)
|
|
||||||
|
|
||||||
@action(detail=True, methods=['delete'])
|
|
||||||
def remove(self, request, pk=None):
|
|
||||||
try:
|
|
||||||
fav = FavoriteFormula.objects.get(user=request.user, formula_id=pk)
|
|
||||||
fav.delete()
|
|
||||||
return Response({"detail": "Removed from favorites."}, status=204)
|
|
||||||
except FavoriteFormula.DoesNotExist:
|
|
||||||
return Response({"detail": "Not in favorites."}, status=404)
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# backend/formulas/views/formula.py
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
|
||||||
from formulas.models.formula import Formula
|
|
||||||
from formulas.serializers.formula import FormulaSerializer
|
|
||||||
from formulas.permissions import IsAdminOrEditor
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from formulas.serializers import SymbolSerializer
|
|
||||||
|
|
||||||
class FormulaViewSet(ModelViewSet):
|
|
||||||
queryset = Formula.objects.all()
|
|
||||||
serializer_class = FormulaSerializer
|
|
||||||
permission_classes = [IsAdminOrEditor]
|
|
||||||
lookup_field = "code"
|
|
||||||
|
|
||||||
@action(detail=True, methods=['get'], url_path='symbols')
|
|
||||||
def get_symbols(self, request, code=None):
|
|
||||||
formula = self.get_object()
|
|
||||||
symbols = formula.symbols.all()
|
|
||||||
serializer = SymbolSerializer(symbols, many=True)
|
|
||||||
return Response(serializer.data)
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
from rest_framework.viewsets import ModelViewSet
|
|
||||||
from formulas.models.symbol import Symbol
|
|
||||||
from formulas.serializers.symbol import SymbolSerializer
|
|
||||||
from formulas.permissions import IsAdminOrEditor
|
|
||||||
|
|
||||||
class SymbolViewSet(ModelViewSet):
|
|
||||||
queryset = Symbol.objects.all()
|
|
||||||
serializer_class = SymbolSerializer
|
|
||||||
permission_classes = [IsAdminOrEditor]
|
|
||||||
lookup_field = "code"
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -47,7 +47,7 @@ SECRET_KEY = 'django-insecure-^+2p2e93g2_1h-@u&l&j+(9n&6z02-e$pirm6(@@(4$+-zyhxr
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
CORS_ALLOWED_ORIGINS = [
|
CORS_ALLOWED_ORIGINS = [
|
||||||
"http://localhost:5173",
|
"http://localhost:5173",
|
||||||
@ -56,14 +56,9 @@ CORS_ALLOWED_ORIGINS = [
|
|||||||
|
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
|
||||||
"http://localhost:5173",
|
|
||||||
"http://127.0.0.1:5173",
|
|
||||||
]
|
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
'users.authentication.VersionedJWTAuthentication',
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
),
|
),
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
'rest_framework.permissions.IsAuthenticated',
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
@ -73,16 +68,6 @@ REST_FRAMEWORK = {
|
|||||||
'rest_framework.renderers.JSONRenderer',
|
'rest_framework.renderers.JSONRenderer',
|
||||||
'rest_framework.renderers.BrowsableAPIRenderer',
|
'rest_framework.renderers.BrowsableAPIRenderer',
|
||||||
),
|
),
|
||||||
|
|
||||||
'DEFAULT_THROTTLE_CLASSES': [
|
|
||||||
'rest_framework.throttling.ScopedRateThrottle',
|
|
||||||
],
|
|
||||||
'DEFAULT_THROTTLE_RATES': {
|
|
||||||
'login': '5/min', # ⬅️ scope do TokenObtainPair
|
|
||||||
'reset_password': '3/min',
|
|
||||||
'avatar_upload': '3/minute',
|
|
||||||
'avatar_get': '100/minute' # ⬅️ scope do resetu hasła
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SIMPLE_JWT = {
|
SIMPLE_JWT = {
|
||||||
@ -131,7 +116,6 @@ INSTALLED_APPS = [
|
|||||||
'core',
|
'core',
|
||||||
'users',
|
'users',
|
||||||
'content',
|
'content',
|
||||||
'formulas',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
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