blog posts
This commit is contained in:
parent
aa5f443442
commit
b0f5fc690a
12 changed files with 288 additions and 14 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -162,3 +162,4 @@ cython_debug/
|
|||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
uploads
|
||||
|
|
|
@ -5,6 +5,7 @@ DEBUG = True
|
|||
ALLOWED_HOSTS = []
|
||||
INSTALLED_APPS = [
|
||||
'unfold',
|
||||
'unfold.contrib.forms',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
|
@ -65,3 +66,5 @@ USE_I18N = True
|
|||
USE_TZ = True
|
||||
STATIC_URL = 'static/'
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = BASE_DIR / "uploads"
|
|
@ -2,7 +2,9 @@ from django.contrib import admin
|
|||
from django.contrib.auth.admin import UserAdmin,GroupAdmin
|
||||
from django.contrib.auth.models import User, Group
|
||||
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(User)
|
||||
|
||||
|
@ -19,6 +21,11 @@ class SiteAdmin(ModelAdmin):
|
|||
|
||||
@admin.register(Page)
|
||||
class PageAdmin(ModelAdmin): pass
|
||||
# formfield_overrides = {
|
||||
# models.TextField: {
|
||||
# "widget": WysiwygWidget
|
||||
# }
|
||||
# }
|
||||
|
||||
@admin.register(Url)
|
||||
class UrlAdmin(ModelAdmin):
|
||||
|
@ -30,3 +37,19 @@ class UrlAdmin(ModelAdmin):
|
|||
class VariableAdmin(ModelAdmin):
|
||||
list_display = ["name","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"]
|
||||
|
|
55
cmsMain/migrations/0005_feedback_file_post.py
Normal file
55
cmsMain/migrations/0005_feedback_file_post.py
Normal 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': 'Посты',
|
||||
},
|
||||
),
|
||||
]
|
19
cmsMain/migrations/0006_post_url.py
Normal file
19
cmsMain/migrations/0006_post_url.py
Normal 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,
|
||||
),
|
||||
]
|
|
@ -1,5 +1,5 @@
|
|||
from django.db import models
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
# Create your models here.
|
||||
|
||||
class Page(models.Model):
|
||||
|
@ -38,3 +38,35 @@ class Variable(models.Model):
|
|||
class Meta:
|
||||
verbose_name = "Переменная"
|
||||
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 = "Обращения"
|
||||
|
|
@ -1,8 +1,13 @@
|
|||
from django import template
|
||||
from ..models import Variable
|
||||
from ..models import Variable, File
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def var(name):
|
||||
v = Variable.objects.get(name=name)
|
||||
if v: return v.value
|
||||
|
||||
@register.simple_tag
|
||||
def file(name):
|
||||
f = File.objects.get(name=name)
|
||||
if f: return f.value.url
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -1,7 +1,10 @@
|
|||
from django.urls import path, include
|
||||
from .views import MainView, PageView
|
||||
|
||||
from django.conf.urls.static import static
|
||||
from django.conf import settings
|
||||
urlpatterns = [
|
||||
*static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT),
|
||||
path('<slug:page>',PageView.as_view()),
|
||||
path('',MainView.as_view())
|
||||
]
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.shortcuts import render, get_object_or_404
|
||||
from django.views import View
|
||||
from django import template
|
||||
from .models import Site, Url, Variable
|
||||
from .models import Site, Url, Variable, Post
|
||||
from django.http import HttpResponse
|
||||
|
||||
context = template.Context({})
|
||||
|
@ -12,12 +12,20 @@ class MainView(View):
|
|||
class PageView(View):
|
||||
def get(self,request,page):
|
||||
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)
|
||||
t = template.Template(r.page.content)
|
||||
content = t.render(context)
|
||||
context.update({
|
||||
"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)
|
||||
return HttpResponse(t.render(context))
|
79
index.html
Normal file
79
index.html
Normal 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
49
logo.svg
Normal 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 |
Loading…
Reference in a new issue