dynamic news
This commit is contained in:
@@ -1,9 +1,13 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from sitemanagement.models import FAQ
|
from sitemanagement.models import FAQ, News
|
||||||
|
|
||||||
class FAQMainSerializer(serializers.ModelSerializer):
|
class FAQMainSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FAQ
|
model = FAQ
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
class NewsMainSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = News
|
||||||
|
fields= "__all__"
|
||||||
@@ -3,8 +3,8 @@ from rest_framework.views import APIView
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from api.utils.decorators import handle_exceptions
|
from api.utils.decorators import handle_exceptions
|
||||||
|
|
||||||
from api.main.serializers import FAQMainSerializer
|
from api.main.serializers import FAQMainSerializer, NewsMainSerializer
|
||||||
from sitemanagement.models import FAQ
|
from sitemanagement.models import FAQ, News
|
||||||
|
|
||||||
class FAQView(APIView):
|
class FAQView(APIView):
|
||||||
@handle_exceptions
|
@handle_exceptions
|
||||||
@@ -16,4 +16,15 @@ class FAQView(APIView):
|
|||||||
'faqs': FAQMainSerializer(faqs, many=True).data
|
'faqs': FAQMainSerializer(faqs, many=True).data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
class NewsView(APIView):
|
||||||
|
@handle_exceptions
|
||||||
|
def get(self, request):
|
||||||
|
|
||||||
|
news = News.objects.all()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'news': NewsMainSerializer(news, many=True).data
|
||||||
|
}
|
||||||
|
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from api.main.views import FAQView
|
from api.main.views import FAQView, NewsView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("v1/faq/", FAQView.as_view(), name='faqMain'),
|
path("v1/faq/", FAQView.as_view(), name='faqMain'),
|
||||||
|
path("v1/news/", NewsView.as_view(), name="newsmain")
|
||||||
]
|
]
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('api/', include('api.urls')),
|
path('api/', include('api.urls')),
|
||||||
]
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-16 07:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sitemanagement', '0005_news_created_at_alter_news_slug'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='filename',
|
||||||
|
field=models.CharField(blank=True, max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='news',
|
||||||
|
name='path',
|
||||||
|
field=models.CharField(blank=True, max_length=255),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-16 08:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sitemanagement', '0006_news_filename_news_path'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='news',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, editable=False, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='news',
|
||||||
|
name='titleImage',
|
||||||
|
field=models.ImageField(upload_to='news/images/', verbose_name='Главная картинка'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -20,11 +20,14 @@ class FAQ (models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class News(models.Model):
|
class News(models.Model):
|
||||||
titleImage = models.ImageField(verbose_name="Главная картинка")
|
titleImage = models.ImageField(upload_to='uploads/news/images/', verbose_name="Главная картинка")
|
||||||
title = models.CharField(max_length=100, verbose_name="Заголовок")
|
title = models.CharField(max_length=100, verbose_name="Заголовок")
|
||||||
content = models.TextField(max_length=1000, verbose_name="Контент статьи")
|
content = models.TextField(max_length=1000, verbose_name="Контент статьи")
|
||||||
slug = models.SlugField(null=True, blank=True, editable=False)
|
slug = models.SlugField(max_length=255, null=True, blank=True, editable=False)
|
||||||
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
|
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
|
||||||
|
filename = models.CharField(max_length=255, blank=True)
|
||||||
|
path = models.CharField(max_length=255, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Новость'
|
verbose_name = 'Новость'
|
||||||
verbose_name_plural = 'Новости'
|
verbose_name_plural = 'Новости'
|
||||||
@@ -34,17 +37,20 @@ class News(models.Model):
|
|||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
super().save(*args, **kwargs) #сохраняем изображение
|
if not self.pk: # Только при первом сохранении
|
||||||
|
super().save(*args, **kwargs) # сохраняем изображение
|
||||||
# генерируем путь к файлу если удалось его сохранить
|
|
||||||
if self.titleImage:
|
# генерируем путь к файлу если удалось его сохранить
|
||||||
self.filename = os.path.basename(self.titleImage.name)
|
if self.titleImage:
|
||||||
self.path = f'{settings.BASE_URL}{settings.MEDIA_URL}{self.titleImage.name}'
|
self.filename = os.path.basename(self.titleImage.name)
|
||||||
super().save(*args, **kwargs) # записываем путь и имя файла в базу
|
self.path = f'{settings.BASE_URL}{settings.MEDIA_URL}{self.titleImage.name}'
|
||||||
|
super().save(*args, **kwargs) # записываем путь и имя файла в базу
|
||||||
|
else:
|
||||||
|
self.filename = ''
|
||||||
|
self.path = ''
|
||||||
else:
|
else:
|
||||||
self.filename = ''
|
super().save(*args, **kwargs)
|
||||||
self.path = ''
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=News)
|
@receiver(pre_save, sender=News)
|
||||||
def generate_slug(sender, instance, **kwargs):
|
def generate_slug(sender, instance, **kwargs):
|
||||||
if not instance.slug:
|
if not instance.slug:
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import { data } from '@/app/staticData'
|
|||||||
import { routes } from '@/app/staticData'
|
import { routes } from '@/app/staticData'
|
||||||
import Button from '@/components/ui/Button'
|
import Button from '@/components/ui/Button'
|
||||||
import News from '@/components/News'
|
import News from '@/components/News'
|
||||||
import { getFAQs } from '@/lib/fetchFAQ'
|
import { getFAQs } from '@/lib/main/fetchFAQ'
|
||||||
|
import { getNews } from '@/lib/main/fetchNews'
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
const faqs = await getFAQs()
|
const faqs = await getFAQs()
|
||||||
|
const news = await getNews()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center max-w-[93%] mx-auto">
|
<div className="flex flex-col items-center justify-center max-w-[93%] mx-auto">
|
||||||
@@ -239,7 +241,7 @@ export default async function Home() {
|
|||||||
<FAQ faqs={faqs} />
|
<FAQ faqs={faqs} />
|
||||||
|
|
||||||
{/* новости */}
|
{/* новости */}
|
||||||
<News />
|
<News news={news} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,3 +54,15 @@ export interface FAQ {
|
|||||||
export interface FAQProps {
|
export interface FAQProps {
|
||||||
faqs: FAQ[]
|
faqs: FAQ[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NewsItem {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
content: string
|
||||||
|
image: string
|
||||||
|
slug: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NewsProps {
|
||||||
|
news: NewsItem[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { news } from '@/app/staticData'
|
|
||||||
import ShowMore from './ui/ShowMore'
|
import ShowMore from './ui/ShowMore'
|
||||||
|
import { NewsProps } from '@/app/types'
|
||||||
|
|
||||||
interface NewsItem {
|
const News: React.FC<NewsProps> = ({ news }) => {
|
||||||
id: number
|
if (!news || news.length === 0) {
|
||||||
title: string
|
return null
|
||||||
description: string
|
}
|
||||||
image: string
|
|
||||||
slug: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function News() {
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-[1250px] mx-auto px-4 sm:px-6 mb-20">
|
<div className="w-full max-w-[1250px] mx-auto px-4 sm:px-6 mb-20">
|
||||||
<h2 className="text-3xl sm:text-4xl text-center font-bold mb-10">
|
<h2 className="text-3xl sm:text-4xl text-center font-bold mb-10">
|
||||||
@@ -33,7 +28,7 @@ export default function News() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="pt-6">
|
<div className="pt-6">
|
||||||
<h3 className="text-base font-semibold mb-3">{item.title}</h3>
|
<h3 className="text-base font-semibold mb-3">{item.title}</h3>
|
||||||
<ShowMore text={item.description} />
|
<ShowMore text={item.content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -42,3 +37,5 @@ export default function News() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default News
|
||||||
|
|||||||
18
frontend/lib/main/fetchNews.ts
Normal file
18
frontend/lib/main/fetchNews.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { NewsItem, NewsProps } from '@/app/types'
|
||||||
|
|
||||||
|
export async function getNews(): Promise<NewsItem[]> {
|
||||||
|
const API_URL = process.env.NEXT_PUBLIC_API_URL
|
||||||
|
|
||||||
|
const response = await fetch(`${API_URL}/news/`, {
|
||||||
|
next: {
|
||||||
|
revalidate: 259200, // три дня
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch FAQs')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: NewsProps = await response.json()
|
||||||
|
return data.news
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user