blog posts

This commit is contained in:
doesnm 2024-09-01 15:07:03 +03:00
parent aa5f443442
commit b0f5fc690a
Signed by: doesnm
SSH key fingerprint: SHA256:fSXBOeK0SXxGqmbQ2pKhSvH3TF0kCijXZfzh3gHfQYM
12 changed files with 288 additions and 14 deletions

1
.gitignore vendored
View file

@ -162,3 +162,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
uploads

View file

@ -5,6 +5,7 @@ DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
INSTALLED_APPS = [ INSTALLED_APPS = [
'unfold', 'unfold',
'unfold.contrib.forms',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
@ -64,4 +65,6 @@ TIME_ZONE = 'Europe/Moscow'
USE_I18N = True USE_I18N = True
USE_TZ = True USE_TZ = True
STATIC_URL = 'static/' STATIC_URL = 'static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "uploads"

View file

@ -2,7 +2,9 @@ from django.contrib import admin
from django.contrib.auth.admin import UserAdmin,GroupAdmin from django.contrib.auth.admin import UserAdmin,GroupAdmin
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from .models import Site, Page, Url, Variable from .models import Site, Page, Url, Variable, File, Post, Feedback
from unfold.contrib.forms.widgets import WysiwygWidget
from django.db import models
admin.site.unregister(Group) admin.site.unregister(Group)
admin.site.unregister(User) admin.site.unregister(User)
@ -19,6 +21,11 @@ class SiteAdmin(ModelAdmin):
@admin.register(Page) @admin.register(Page)
class PageAdmin(ModelAdmin): pass class PageAdmin(ModelAdmin): pass
# formfield_overrides = {
# models.TextField: {
# "widget": WysiwygWidget
# }
# }
@admin.register(Url) @admin.register(Url)
class UrlAdmin(ModelAdmin): class UrlAdmin(ModelAdmin):
@ -29,4 +36,20 @@ class UrlAdmin(ModelAdmin):
@admin.register(Variable) @admin.register(Variable)
class VariableAdmin(ModelAdmin): class VariableAdmin(ModelAdmin):
list_display = ["name","value"] list_display = ["name","value"]
list_editable = ["value"] list_editable = ["value"]
@admin.register(File)
class FileAdmin(ModelAdmin): pass
@admin.register(Post)
class PostAdmin(ModelAdmin):
list_display = ["author","title"]
list_display_links = ["author","title"]
def save_model(self,request,obj,form,change):
obj.author = request.user
obj.save()
@admin.register(Feedback)
class FeedbackAdmin(ModelAdmin):
list_display = ["name","email"]
list_display_links = ["name","email"]

View file

@ -0,0 +1,55 @@
# Generated by Django 5.1 on 2024-08-21 13:20
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cmsMain', '0004_variable'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Feedback',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254, verbose_name='Почта')),
('name', models.CharField(max_length=50, verbose_name='Имя')),
('message', models.TextField(verbose_name='Сообщение')),
],
options={
'verbose_name': 'Обращение',
'verbose_name_plural': 'Обращения',
},
),
migrations.CreateModel(
name='File',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='Имя')),
('value', models.FileField(upload_to='files', verbose_name='Значение')),
],
options={
'verbose_name': 'Файл',
'verbose_name_plural': 'Файлы',
},
),
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100, verbose_name='Название')),
('content', models.TextField(verbose_name='Содержимое')),
('created_at', models.DateTimeField(auto_now_add=True)),
('author', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Автор')),
],
options={
'verbose_name': 'Пост',
'verbose_name_plural': 'Посты',
},
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 5.1 on 2024-09-01 10:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cmsMain', '0005_feedback_file_post'),
]
operations = [
migrations.AddField(
model_name='post',
name='url',
field=models.CharField(default='/test-post', help_text='Без слеша в начале, например: blog/test-post или my-first-test-post', max_length=100, verbose_name='Ссылка'),
preserve_default=False,
),
]

View file

@ -1,5 +1,5 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User
# Create your models here. # Create your models here.
class Page(models.Model): class Page(models.Model):
@ -37,4 +37,36 @@ class Variable(models.Model):
return self.name return self.name
class Meta: class Meta:
verbose_name = "Переменная" verbose_name = "Переменная"
verbose_name_plural = "Переменные" verbose_name_plural = "Переменные"
class File(models.Model):
name = models.CharField("Имя",max_length=50)
value = models.FileField("Значение",upload_to="files")
def __str__(self):
return self.name
class Meta:
verbose_name = "Файл"
verbose_name_plural = "Файлы"
class Post(models.Model):
url = models.CharField("Ссылка",help_text="например: my-first-test-post",max_length=100)
title = models.CharField("Название",max_length=100)
author = models.ForeignKey(User,editable=False,verbose_name="Автор",on_delete=models.PROTECT)
content = models.TextField("Содержимое")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.title} by {self.author.username}"
class Meta:
verbose_name = "Пост"
verbose_name_plural = "Посты"
class Feedback(models.Model):
email = models.EmailField("Почта")
name = models.CharField("Имя",max_length=50)
message = models.TextField("Сообщение")
def __str__(self):
return f"{self.name} | {self.name}"
class Meta:
verbose_name = "Обращение"
verbose_name_plural = "Обращения"

View file

@ -1,8 +1,13 @@
from django import template from django import template
from ..models import Variable from ..models import Variable, File
register = template.Library() register = template.Library()
@register.simple_tag @register.simple_tag
def var(name): def var(name):
v = Variable.objects.get(name=name) v = Variable.objects.get(name=name)
if v: return v.value if v: return v.value
@register.simple_tag
def file(name):
f = File.objects.get(name=name)
if f: return f.value.url

View file

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

View file

@ -1,7 +1,10 @@
from django.urls import path, include from django.urls import path, include
from .views import MainView, PageView from .views import MainView, PageView
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [ urlpatterns = [
*static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT),
path('<slug:page>',PageView.as_view()), path('<slug:page>',PageView.as_view()),
path('',MainView.as_view()) path('',MainView.as_view())
] ]

View file

@ -1,7 +1,7 @@
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.views import View from django.views import View
from django import template from django import template
from .models import Site, Url, Variable from .models import Site, Url, Variable, Post
from django.http import HttpResponse from django.http import HttpResponse
context = template.Context({}) context = template.Context({})
@ -12,12 +12,20 @@ class MainView(View):
class PageView(View): class PageView(View):
def get(self,request,page): def get(self,request,page):
s = get_object_or_404(Site,domain=request.META["HTTP_HOST"]) s = get_object_or_404(Site,domain=request.META["HTTP_HOST"])
r = Url.objects.filter(path=page,site=s)
post = None
if "post" in request.GET:
post = Post.objects.filter(url=request.GET["post"])
if post: post = post.first()
r = get_object_or_404(Url,path=page,site=s) r = get_object_or_404(Url,path=page,site=s)
t = template.Template(r.page.content) t = template.Template(r.page.content)
content = t.render(context) content = t.render(context)
context.update({ context.update({
"content": content, "content": content,
"title": r.page.title "title": r.page.title,
"url": page,
"posts": Post.objects.all(),
"post": post
}) })
t = template.Template(s.base_page.content) t = template.Template(s.base_page.content)
return HttpResponse(t.render(context)) return HttpResponse(t.render(context))

79
index.html Normal file
View file

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>doesnm/cms</title>
</head>
<body>
<header>
<div>
<a class="active" href="/">Главная</a>
<a href="/blog">Блог</a>
<a href="/contact">Обратная связь</a>
</div>
</header>
<section>
<h1>Блог</h1>
<div>
<p class="subtitle">Система управления контентом написанная на Django имеющая возможность вести свой блог и принимать обратную связь от пользователей</p>
<h3>Статистика:</h3>
<p>Количество пользователей: 0</p>
<p>Количество обращений: 0</p>
<p>Количество постов: 0</p>
</div>
<a href="/admin">Перейти в админку</a>
</section>
<style>
body {
padding: 0;
margin: 0;
}
header {
display: flex;
justify-content: center;
align-items: center;
background-color: #A0A0A0;
margin: 0;
padding: 0;
height: 50px;
}
header div {
margin: 0;
padding: 0;
}
a {
text-decoration: none;
color: #333;
}
body {
background-color: #926EAE;
color: #333;
}
section {
margin: 10px;
}
section div {
margin-bottom: 40px;
}
header a {
padding: 10px;
}
section a {
padding: 10px 30px;
border: 1px solid #333;
color: #333;
border-radius: 20px;
}
body {
background-color: #C0C0C0;
}
a.active {
background-color: #B0B0B0;
}
</style>
</body>
</html>

49
logo.svg Normal file
View file

@ -0,0 +1,49 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="500.000000pt" height="500.000000pt" viewBox="0 0 500.000000 500.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,500.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M3390 4257 l0 -417 -935 0 -935 0 0 410 c0 267 -3 410 -10 410 -7 0
-10 -143 -10 -410 l0 -410 -426 0 c-281 0 -423 -3 -419 -10 4 -6 157 -10 426
-10 l419 0 0 -965 0 -965 -419 0 c-269 0 -422 -4 -426 -10 -4 -7 138 -10 419
-10 l426 0 0 -327 0 -328 -29 -42 c-72 -106 -37 -237 78 -295 91 -46 180 -33
247 36 91 95 78 239 -29 313 -50 35 -156 45 -205 19 -17 -9 -33 -16 -37 -16
-3 0 -5 144 -5 320 l0 320 935 0 935 0 0 -337 -1 -338 -20 28 c-18 23 -28 27
-70 27 l-49 0 2 -192 3 -193 75 0 75 0 3 114 c1 64 8 121 14 128 14 17 62 17
76 0 6 -7 13 -64 14 -128 l3 -114 75 0 75 0 3 114 c1 64 8 121 14 128 14 17
62 17 76 0 6 -7 13 -64 14 -128 l3 -114 75 0 75 0 0 145 c0 135 -2 148 -23
183 -30 47 -68 67 -129 67 -50 0 -123 -37 -123 -63 0 -6 -12 1 -26 16 -14 16
-37 32 -51 38 -36 13 -104 10 -133 -6 -14 -8 -28 -14 -33 -15 -4 0 -6 143 -5
318 l3 317 423 3 c270 1 422 6 422 12 0 6 -151 10 -425 10 l-425 0 0 965 0
965 425 0 c274 0 425 4 425 10 0 6 -152 11 -422 12 l-423 3 -2 409 c-1 225 -5
411 -8 414 -3 3 -5 -182 -5 -411z m0 -1402 l0 -965 -220 0 -220 0 19 48 c10
26 87 218 171 427 84 209 160 400 170 425 l18 45 -188 470 -188 470 -601 3
-601 2 2 -267 3 -268 68 -3 67 -3 0 -404 0 -404 -67 -3 -68 -3 -3 -267 -2
-268 -115 0 -115 0 0 965 0 965 935 0 935 0 0 -965z m-768 185 c43 -107 78
-199 78 -205 0 -6 -35 -98 -78 -205 -73 -181 -80 -195 -105 -198 l-27 -3 0
406 0 406 27 -3 c25 -3 32 -17 105 -198z m-934 -1948 c17 -22 15 -52 -5 -74
-46 -51 -123 20 -80 74 23 30 60 30 85 0z"/>
<path d="M1220 1361 l0 -109 -58 5 c-71 7 -120 -12 -154 -58 -80 -107 -52
-276 55 -328 58 -28 106 -27 150 4 36 24 37 24 47 5 8 -16 21 -20 60 -20 l50
0 0 305 0 305 -75 0 -75 0 0 -109z m-16 -267 c24 -24 20 -61 -9 -80 -23 -15
-27 -15 -50 0 -29 19 -32 51 -8 78 20 22 46 23 67 2z"/>
<path d="M2015 1241 c-73 -33 -115 -102 -115 -185 0 -57 11 -85 52 -131 40
-47 99 -74 156 -75 49 0 142 36 142 55 0 6 -13 30 -29 52 -29 40 -30 41 -70
30 -29 -8 -47 -8 -65 1 -51 23 -28 32 82 32 94 0 111 3 126 19 39 43 -15 165
-87 200 -55 27 -137 27 -192 2z m130 -131 c18 -20 17 -20 -33 -20 -53 0 -62 6
-40 28 18 18 53 14 73 -8z"/>
<path d="M2422 1240 c-47 -29 -68 -82 -52 -133 12 -40 16 -43 144 -96 22 -9
26 -15 17 -24 -9 -9 -23 -7 -63 7 l-51 18 -40 -40 c-48 -49 -45 -65 19 -93 96
-43 211 -33 266 21 22 23 28 37 28 72 0 56 -26 83 -111 112 -66 23 -89 46 -45
46 13 0 37 -5 52 -10 25 -10 31 -7 65 26 46 44 41 62 -24 93 -59 29 -159 29
-205 1z"/>
<path d="M2955 1249 c-11 -6 -30 -21 -42 -31 -21 -20 -21 -20 -38 6 -15 22
-25 26 -66 26 l-49 0 0 -195 0 -195 75 0 75 0 0 108 c0 123 11 152 60 152 16
0 32 -6 37 -12 4 -7 10 -65 13 -128 l5 -115 72 -3 c71 -3 73 -2 79 24 12 46
-2 258 -19 290 -29 56 -62 77 -125 81 -32 2 -66 -2 -77 -8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3 KiB