공지사항관리 기능 분리 및 개선
All checks were successful
Build And Test / build-and-push (push) Successful in 4m23s
All checks were successful
Build And Test / build-and-push (push) Successful in 4m23s
This commit is contained in:
parent
06d1853fb0
commit
af57b56e69
BIN
README-1.png
Normal file
BIN
README-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 135 KiB |
52
README.md
52
README.md
@ -1,6 +1,50 @@
|
||||
# butler_ddochi
|
||||
|
||||
### dev_0.0.21
|
||||
사이드메뉴에 unity 게임 등록.
|
||||
* 쿠버네티스에 배포된 웹 게임 링크 등록.
|
||||
* 쿠버네티스 클러스터에 배포된 웹 게임 및 istio 설정 필요.
|
||||
butler_ddochi는 개인 테스트 환경 관리를 위한 목적으로 만들어지고 있습니다.
|
||||
* 클라우드 네이티브 환경을 이용한 서비스 환경 관리
|
||||
* 쿠버네티스 관리
|
||||
* 문의는 icurfer@gmail.com으로 부탁드립니다.
|
||||
|
||||
> 문서가 미흡하여 설치 및 구성이 어려울 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 목차
|
||||
|
||||
* [사전 구성요소 선택](#사전-구성-컴포넌트(선택))
|
||||
* [butler_ddochi설치](#butler_ddochi설치)
|
||||
|
||||
### 사전 구성 컴포넌트(필수)
|
||||
동작을 위한 최소한의 필수 컴포넌트입니다.
|
||||
* MariaDB
|
||||
* MinIO
|
||||
|
||||
### 사전 구성 컴포넌트(선택)
|
||||
아래 컴포넌트들이 배포 되어있다면, 로그인 후 Edit Profile을 이용하여 웹콘솔 링크를 입력하여 사용합니다.
|
||||

|
||||
* Gitea
|
||||
* Harbor
|
||||
* ArgoCD
|
||||
* CodeServer
|
||||
* Rancher
|
||||
* Prometheus
|
||||
* OpenSearch
|
||||
* Kiali
|
||||
---
|
||||
|
||||
### butler_ddochi설치
|
||||
docker 또는 kubernetes를 이용하여 배포 합니다.
|
||||
|
||||
> docker pull harbor.icurfer.com/py_prj/butler_ddochi:${version}
|
||||
|
||||
필수 환경변수
|
||||
* DEBUG=0
|
||||
* SQL_ENGINE='django.db.backends.mysql'
|
||||
* SQL_HOST='DB_SVR_address'
|
||||
* SQL_USER='DB_SVR_user'
|
||||
* SQL_PASSWORD='DB_SVR_user_password'
|
||||
* SQL_DATABASE='DB_SVR_database'
|
||||
* SQL_PORT='DB_SVR_portnumber'
|
||||
* CSRF_TRUSTED_ORIGINS=https://www.example.com,https://butler.example.com,https://butler.example.com:8000
|
||||
* MM_URL=https://mm.example.com/hooks/${hash}
|
||||
* (기능 미구현)MINIO_URL=https://minio.example.com/
|
21
blog/migrations/0006_alter_post_author.py
Normal file
21
blog/migrations/0006_alter_post_author.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.2.14 on 2025-01-25 14:39
|
||||
|
||||
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),
|
||||
('blog', '0005_rename_content_post_contents'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='post',
|
||||
name='author',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='blog_posts', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
@ -1,5 +1,6 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from markdown_it import MarkdownIt
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
@ -10,12 +11,20 @@ class Post(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True) # 작성일
|
||||
updated_at = models.DateTimeField(auto_now=True) # 수정일
|
||||
tags = TaggableManager()
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='blog_posts'
|
||||
)
|
||||
|
||||
def render_markdown(self):
|
||||
"""마크다운을 HTML로 변환"""
|
||||
md = MarkdownIt()
|
||||
return md.render(self.contents)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('blog:post_detail', args=[str(self.pk)])
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
@ -1,39 +0,0 @@
|
||||
{% extends "components/base.html" %}
|
||||
{% block title %}Create Post{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<h1>Create New Post</h1>
|
||||
<div class="row">
|
||||
<!-- 왼쪽: 마크다운 에디터 -->
|
||||
<div class="col-md-6">
|
||||
<form method="POST" id="post-form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn btn-primary mt-3">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 오른쪽: 미리보기 -->
|
||||
<div class="col-md-6">
|
||||
<h2>Preview</h2>
|
||||
<div id="preview" class="border p-3" style="background: #f8f9fa; min-height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 파서 및 스크립트 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
|
||||
<script>
|
||||
// 마크다운 파서 초기화
|
||||
const md = window.markdownit();
|
||||
|
||||
// content 필드와 미리보기 연결
|
||||
const textarea = document.getElementById("markdown-editor");
|
||||
const preview = document.getElementById("preview");
|
||||
|
||||
// 실시간 미리보기 업데이트
|
||||
textarea.addEventListener("input", function () {
|
||||
const markdownContent = textarea.value; // textarea 내용 가져오기
|
||||
preview.innerHTML = md.render(markdownContent); // 마크다운 -> HTML 변환
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 에디터 -->
|
||||
<h2>contents</h2>
|
||||
<h2>Contents</h2>
|
||||
|
||||
<div class="col-md-12">
|
||||
{{ form.contents }}
|
||||
@ -41,9 +41,22 @@
|
||||
|
||||
<!-- 마크다운 파서 및 스크립트 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>
|
||||
// 마크다운 파서 초기화
|
||||
const md = window.markdownit();
|
||||
const md = window.markdownit({
|
||||
highlight: function (str, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs
|
||||
.highlight(str, {language: lang})
|
||||
.value;
|
||||
} catch (__) {}
|
||||
}
|
||||
return ''; // 기본 HTML 이스케이프 처리
|
||||
}
|
||||
});
|
||||
|
||||
// content 필드와 미리보기 연결
|
||||
const textarea = document.getElementById("markdown-editor");
|
||||
@ -52,7 +65,16 @@
|
||||
// 실시간 미리보기 업데이트
|
||||
textarea.addEventListener("input", function () {
|
||||
const markdownContent = textarea.value; // textarea 내용 가져오기
|
||||
preview.innerHTML = md.render(markdownContent); // 마크다운 -> HTML 변환
|
||||
const renderedContent = md.render(markdownContent); // 마크다운 -> HTML 변환
|
||||
preview.innerHTML = renderedContent;
|
||||
|
||||
// Highlight.js로 코드 블록 강조
|
||||
document
|
||||
.querySelectorAll("#preview pre code")
|
||||
.forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,8 +1,8 @@
|
||||
{% extends "components/base.html" %}
|
||||
{% block title %}Create Notice{% endblock %}
|
||||
{% block title %}Create Post{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<h1 class="pt-3">Create New Notice</h1>
|
||||
<h1 class="pt-3">Create New Post</h1>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -12,6 +12,14 @@
|
||||
{{ form.title.label_tag }}
|
||||
{{ form.title }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form.summary.label_tag }}
|
||||
{{ form.summary }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form.tags.label_tag }}
|
||||
{{ form.tags }}
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 에디터 -->
|
||||
<h2>contents</h2>
|
||||
@ -22,7 +30,7 @@
|
||||
<!-- 버튼 -->
|
||||
<div class="d-flex justify-content-end mt-4">
|
||||
<button type="submit" class="btn btn-primary me-2">Create Post</button>
|
||||
<a href="{% url 'butler:notice_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
<a href="{% url 'blog:post_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -8,8 +8,11 @@
|
||||
<h5 class="text-muted">{{ post.summary }}</h5>
|
||||
{% endif %}
|
||||
<!-- Author -->
|
||||
<div class="text-muted fst-italic mb-2">{{ post.created_at }} <br>
|
||||
by. {{ post.author | upper }}</div>
|
||||
<div class="text-muted fst-italic mb-2">{{ post.created_at }}
|
||||
<br>
|
||||
by.
|
||||
{{ post.author | upper }}</div>
|
||||
<!-- Rendered Markdown Content -->
|
||||
<div>
|
||||
{{ post.render_markdown|safe }}
|
||||
</div>
|
||||
@ -22,5 +25,43 @@
|
||||
<br/>
|
||||
<br/>
|
||||
{% endif %}
|
||||
<a href="{% url 'blog:post_list' %}" class="btn btn-secondary mt-3">Back to List</a>
|
||||
|
||||
{% if request.user == post.author %}
|
||||
<div class="d-flex mt-3">
|
||||
<a href="{% url 'blog:post_list' %}" class="btn btn-secondary me-2">List</a>
|
||||
<a href="{% url 'blog:update_post' post.pk %}" class="btn btn-warning me-2">Edit</a>
|
||||
<form method="POST" action="{% url 'blog:delete_post' post.pk %}" class="d-inline" id="delete-form">
|
||||
{% csrf_token %}
|
||||
<button type="button" class="btn btn-danger" id="delete-button">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Highlight.js Styles -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css">
|
||||
<!-- Highlight.js Script -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>
|
||||
// Initialize Highlight.js
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document
|
||||
.querySelectorAll("pre code")
|
||||
.forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- Delete post -->
|
||||
<script>
|
||||
document
|
||||
.getElementById("delete-button")
|
||||
.addEventListener("click", function () {
|
||||
const confirmed = confirm("Are you sure you want to delete this post?");
|
||||
if (confirmed) {
|
||||
document
|
||||
.getElementById("delete-form")
|
||||
.submit();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
31
blog/templates/blog/post_detail_default.html
Normal file
31
blog/templates/blog/post_detail_default.html
Normal file
@ -0,0 +1,31 @@
|
||||
{% extends "components/base.html" %}
|
||||
{% block title %}Post{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<!-- Title -->
|
||||
<h1 class="mt-4">{{ post.title }}</h1>
|
||||
{% if post.summary %}
|
||||
<h5 class="text-muted">{{ post.summary }}</h5>
|
||||
{% endif %}
|
||||
<!-- Author -->
|
||||
<div class="text-muted fst-italic mb-2">{{ post.created_at }}
|
||||
<br>
|
||||
by.
|
||||
{{ post.author | upper }}</div>
|
||||
<div>
|
||||
{{ post.render_markdown|safe }}
|
||||
</div>
|
||||
<hr>
|
||||
{% if post.tags.exists %}
|
||||
<i class="fa-solid fa-tags"></i>
|
||||
{% for tag in post.tags.all %}
|
||||
<span class="badge bg-primary">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
<br/>
|
||||
<br/>
|
||||
{% endif %}
|
||||
<a href="{% url 'blog:post_list' %}" class="btn btn-secondary mt-3">List</a>
|
||||
{% if request.user == post.author %}
|
||||
<a href="{% url 'blog:update_post' post.pk %}" class="btn btn-warning">Edit</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -12,7 +12,7 @@
|
||||
<li class="list-group-item">
|
||||
<h5>{{ post.title }}</h5>
|
||||
<p>{{ post.summary }}</p>
|
||||
<a href="{% url 'blog:post_detail' post.pk %}" class="btn btn-secondary">Read More</a>
|
||||
<a href="{{ post.get_absolute_url }}" class="btn btn-secondary">Read More</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
58
blog/templates/blog/update_post.html
Normal file
58
blog/templates/blog/update_post.html
Normal file
@ -0,0 +1,58 @@
|
||||
{% extends "components/base.html" %}
|
||||
{% block title %}Update Post{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<h1 class="pt-3">Update Post</h1>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="POST" id="post-form">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
{{ form.title.label_tag }}
|
||||
{{ form.title }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form.summary.label_tag }}
|
||||
{{ form.summary }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form.tags.label_tag }}
|
||||
{{ form.tags }}
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 에디터 -->
|
||||
<h2>Contents</h2>
|
||||
<div class="col-md-12">
|
||||
{{ form.contents }}
|
||||
</div>
|
||||
<!-- 버튼 -->
|
||||
<div class="d-flex justify-content-end mt-4">
|
||||
<button type="submit" class="btn btn-primary me-2">Save Changes</button>
|
||||
<a href="{% url 'blog:post_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div id="preview" class="border p-3 bg-light h-100 overflow-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 파서 및 스크립트 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
|
||||
<script>
|
||||
// 마크다운 파서 초기화
|
||||
const md = window.markdownit();
|
||||
|
||||
// content 필드와 미리보기 연결
|
||||
const textarea = document.getElementById("markdown-editor");
|
||||
const preview = document.getElementById("preview");
|
||||
|
||||
// 실시간 미리보기 업데이트
|
||||
textarea.addEventListener("input", function () {
|
||||
const markdownContent = textarea.value; // textarea 내용 가져오기
|
||||
preview.innerHTML = md.render(markdownContent); // 마크다운 -> HTML 변환
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
@ -7,4 +7,6 @@ urlpatterns = [
|
||||
path('', views.post_list, name='post_list'), # 포스트 리스트
|
||||
path('create/', views.create_post, name='create_post'), # 포스트 작성
|
||||
path('<int:pk>/', views.post_detail, name='post_detail'), # 포스트 상세 보기
|
||||
path('<int:pk>/update/', views.update_post, name='update_post'), # 글 수정 URL
|
||||
path('<int:pk>/delete/', views.delete_post, name='delete_post'), # 삭제 URL
|
||||
]
|
@ -3,6 +3,16 @@ from django.contrib.auth.decorators import login_required
|
||||
from .models import Post
|
||||
from .forms import PostForm
|
||||
|
||||
# 게시글 목록
|
||||
def post_list(request):
|
||||
posts = Post.objects.all().order_by('-created_at')
|
||||
return render(request, 'blog/post_list.html', {'posts': posts})
|
||||
|
||||
# 게시글 상세 보기
|
||||
def post_detail(request, pk):
|
||||
post = get_object_or_404(Post, pk=pk)
|
||||
return render(request, 'blog/post_detail.html', {'post': post})
|
||||
|
||||
@login_required
|
||||
def create_post(request):
|
||||
if request.method == 'POST':
|
||||
@ -17,10 +27,37 @@ def create_post(request):
|
||||
form = PostForm()
|
||||
return render(request, 'blog/create_post.html', {'form': form})
|
||||
|
||||
def post_list(request):
|
||||
posts = Post.objects.all().order_by('-created_at')
|
||||
return render(request, 'blog/post_list.html', {'posts': posts})
|
||||
|
||||
def post_detail(request, pk):
|
||||
|
||||
@login_required
|
||||
def update_post(request, pk):
|
||||
post = get_object_or_404(Post, pk=pk)
|
||||
return render(request, 'blog/post_detail.html', {'post': post})
|
||||
|
||||
# 작성자만 수정 가능하도록 권한 검사
|
||||
if post.author != request.user:
|
||||
return redirect('blog:post_list')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = PostForm(request.POST, instance=post)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('blog:post_detail', pk=post.pk)
|
||||
else:
|
||||
form = PostForm(instance=post)
|
||||
|
||||
return render(request, 'blog/update_post.html', {'form': form, 'post': post})
|
||||
|
||||
@login_required
|
||||
def delete_post(request, pk):
|
||||
post = get_object_or_404(Post, pk=pk)
|
||||
|
||||
# 작성자만 삭제 가능
|
||||
if post.author != request.user:
|
||||
return redirect('blog:post_list')
|
||||
|
||||
# POST 요청일 때 삭제
|
||||
if request.method == 'POST':
|
||||
post.delete()
|
||||
return redirect('blog:post_list')
|
||||
|
||||
return render(request, 'blog/confirm_delete.html', {'post': post})
|
0
board_notice/__init__.py
Normal file
0
board_notice/__init__.py
Normal file
4
board_notice/admin.py
Normal file
4
board_notice/admin.py
Normal file
@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from .models import BoardNotice
|
||||
|
||||
admin.site.register(BoardNotice)
|
6
board_notice/apps.py
Normal file
6
board_notice/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BoardNoticeConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'board_notice'
|
11
board_notice/forms.py
Normal file
11
board_notice/forms.py
Normal file
@ -0,0 +1,11 @@
|
||||
from django import forms
|
||||
from .models import BoardNotice
|
||||
|
||||
class BoardNoticeForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = BoardNotice
|
||||
fields = ['title', 'contents']
|
||||
widgets = {
|
||||
'title': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'contents': forms.Textarea(attrs={'class': 'form-control', 'id': 'markdown-editor'}),
|
||||
}
|
28
board_notice/migrations/0001_initial.py
Normal file
28
board_notice/migrations/0001_initial.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.2.14 on 2025-01-25 14:39
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BoardNotice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('contents', models.TextField()),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='board_notice', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
0
board_notice/migrations/__init__.py
Normal file
0
board_notice/migrations/__init__.py
Normal file
27
board_notice/models.py
Normal file
27
board_notice/models.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from markdown_it import MarkdownIt
|
||||
|
||||
class BoardNotice(models.Model):
|
||||
title = models.CharField(max_length=200) # 제목
|
||||
contents = models.TextField() # 본문 (마크다운 저장)
|
||||
created_at = models.DateTimeField(auto_now_add=True) # 작성일
|
||||
updated_at = models.DateTimeField(auto_now=True) # 수정일
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='board_notice'
|
||||
)
|
||||
|
||||
def render_markdown(self):
|
||||
"""마크다운을 HTML로 변환"""
|
||||
md = MarkdownIt()
|
||||
return md.render(self.contents)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('board_notice:notice_detail', args=[str(self.pk)])
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
72
board_notice/templates/board_notice/create_notice.html
Normal file
72
board_notice/templates/board_notice/create_notice.html
Normal file
@ -0,0 +1,72 @@
|
||||
{% extends "components/base.html" %}
|
||||
{% block title %}Create Notice{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<h1 class="pt-3">Create New Notice</h1>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="POST" id="post-form">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
{{ form.title.label_tag }}
|
||||
{{ form.title }}
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 에디터 -->
|
||||
<h2>Contents</h2>
|
||||
|
||||
<div class="col-md-12">
|
||||
{{ form.contents }}
|
||||
</div>
|
||||
<!-- 버튼 -->
|
||||
<div class="d-flex justify-content-end mt-4">
|
||||
<button type="submit" class="btn btn-primary me-2">Create Notice</button>
|
||||
<a href="{% url 'board_notice:notice_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div id="preview" class="border p-3 bg-light h-100 overflow-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 파서 및 스크립트 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>
|
||||
// 마크다운 파서 초기화
|
||||
const md = window.markdownit({
|
||||
highlight: function (str, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs
|
||||
.highlight(str, {language: lang})
|
||||
.value;
|
||||
} catch (__) {}
|
||||
}
|
||||
return ''; // 기본 HTML 이스케이프 처리
|
||||
}
|
||||
});
|
||||
|
||||
// content 필드와 미리보기 연결
|
||||
const textarea = document.getElementById("markdown-editor");
|
||||
const preview = document.getElementById("preview");
|
||||
|
||||
// 실시간 미리보기 업데이트
|
||||
textarea.addEventListener("input", function () {
|
||||
const markdownContent = textarea.value; // textarea 내용 가져오기
|
||||
const renderedContent = md.render(markdownContent); // 마크다운 -> HTML 변환
|
||||
preview.innerHTML = renderedContent;
|
||||
|
||||
// Highlight.js로 코드 블록 강조
|
||||
document
|
||||
.querySelectorAll("#preview pre code")
|
||||
.forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
56
board_notice/templates/board_notice/notice_detail.html
Normal file
56
board_notice/templates/board_notice/notice_detail.html
Normal file
@ -0,0 +1,56 @@
|
||||
{% extends "components/base.html" %}
|
||||
{% block title %}Notice{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<!-- Title -->
|
||||
<h1 class="mt-4">{{ notice.title }}</h1>
|
||||
<!-- Author -->
|
||||
<div class="text-muted fst-italic mb-2">{{ notice.created_at }}
|
||||
<br>
|
||||
by.
|
||||
{{ notice.author | upper }}</div>
|
||||
<!-- Rendered Markdown Content -->
|
||||
<div>
|
||||
{{ notice.render_markdown|safe }}
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
{% if request.user == notice.author %}
|
||||
<div class="d-flex mt-3">
|
||||
<a href="{% url 'board_notice:notice_list' %}" class="btn btn-secondary me-2">List</a>
|
||||
<a href="{% url 'board_notice:update_notice' notice.pk %}" class="btn btn-warning me-2">Edit</a>
|
||||
<form method="POST" action="{% url 'board_notice:delete_notice' notice.pk %}" class="d-inline" id="delete-form">
|
||||
{% csrf_token %}
|
||||
<button type="button" class="btn btn-danger" id="delete-button">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Highlight.js Styles -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css">
|
||||
<!-- Highlight.js Script -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>
|
||||
// Initialize Highlight.js
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document
|
||||
.querySelectorAll("pre code")
|
||||
.forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- Delete Notice -->
|
||||
<script>
|
||||
document
|
||||
.getElementById("delete-button")
|
||||
.addEventListener("click", function () {
|
||||
const confirmed = confirm("Are you sure you want to delete this Notice?");
|
||||
if (confirmed) {
|
||||
document
|
||||
.getElementById("delete-form")
|
||||
.submit();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -9,23 +9,27 @@
|
||||
</h2>
|
||||
<!-- 공지사항 목록 -->
|
||||
|
||||
{% if records %}
|
||||
{% if notices %}
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th scope="col">글번호</th>
|
||||
<th scope="col">제목</th>
|
||||
<th scope="col">내용</th>
|
||||
<th scope="col">작성자</th>
|
||||
<th scope="col">작성일</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for record in records %}
|
||||
{% for record in notices %}
|
||||
<tr>
|
||||
<td>{{ record.pk }}</td>
|
||||
<td>
|
||||
<a href="{{ record.get_absolute_url }}">{{ record.title }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ record.get_absolute_url }}">{{ record.contents|truncatechars:50 }}</a>
|
||||
</td>
|
||||
<td>{{ record.author }}</td>
|
||||
<td>{{ record.created_at|date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
@ -36,9 +40,9 @@
|
||||
<p class="text-muted">현재 공지사항이 없습니다.</p>
|
||||
{% endif %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<!-- 버튼 컨테이너 -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<a href="{% url 'butler:create_notice' %}" class="btn btn-primary">Create Notice</a>
|
||||
</div>
|
||||
<!-- 버튼 컨테이너 -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<a href="{% url 'board_notice:create_notice' %}" class="btn btn-primary">Create Notice</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
58
board_notice/templates/board_notice/update_notice.html
Normal file
58
board_notice/templates/board_notice/update_notice.html
Normal file
@ -0,0 +1,58 @@
|
||||
{% extends "components/base.html" %}
|
||||
{% block title %}Update Post{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<h1 class="pt-3">Update Post</h1>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="POST" id="post-form">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
{{ form.title.label_tag }}
|
||||
{{ form.title }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form.summary.label_tag }}
|
||||
{{ form.summary }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form.tags.label_tag }}
|
||||
{{ form.tags }}
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 에디터 -->
|
||||
<h2>Contents</h2>
|
||||
<div class="col-md-12">
|
||||
{{ form.contents }}
|
||||
</div>
|
||||
<!-- 버튼 -->
|
||||
<div class="d-flex justify-content-end mt-4">
|
||||
<button type="submit" class="btn btn-primary me-2">Save Changes</button>
|
||||
<a href="{% url 'blog:post_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div id="preview" class="border p-3 bg-light h-100 overflow-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 마크다운 파서 및 스크립트 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
|
||||
<script>
|
||||
// 마크다운 파서 초기화
|
||||
const md = window.markdownit();
|
||||
|
||||
// content 필드와 미리보기 연결
|
||||
const textarea = document.getElementById("markdown-editor");
|
||||
const preview = document.getElementById("preview");
|
||||
|
||||
// 실시간 미리보기 업데이트
|
||||
textarea.addEventListener("input", function () {
|
||||
const markdownContent = textarea.value; // textarea 내용 가져오기
|
||||
preview.innerHTML = md.render(markdownContent); // 마크다운 -> HTML 변환
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
3
board_notice/tests.py
Normal file
3
board_notice/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
12
board_notice/urls.py
Normal file
12
board_notice/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'board_notice'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.notice_list, name='notice_list'), # 포스트 리스트
|
||||
path('create/', views.create_notice, name='create_notice'), # 포스트 작성
|
||||
path('<int:pk>/', views.notice_detail, name='notice_detail'), # 포스트 상세 보기
|
||||
path('<int:pk>/update/', views.update_notice, name='update_notice'), # 글 수정 URL
|
||||
path('<int:pk>/delete/', views.delete_notice, name='delete_notice'), # 삭제 URL
|
||||
]
|
62
board_notice/views.py
Normal file
62
board_notice/views.py
Normal file
@ -0,0 +1,62 @@
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .models import BoardNotice
|
||||
from .forms import BoardNoticeForm
|
||||
|
||||
# 게시글 목록
|
||||
def notice_list(request):
|
||||
notices = BoardNotice.objects.all().order_by('-created_at')
|
||||
return render(request, 'board_notice/notice_list.html', {'notices': notices})
|
||||
|
||||
# 게시글 상세 보기
|
||||
def notice_detail(request, pk):
|
||||
notice = get_object_or_404(BoardNotice, pk=pk)
|
||||
return render(request, 'board_notice/notice_detail.html', {'notice': notice})
|
||||
|
||||
# 공지사항 게시글 등록
|
||||
@login_required
|
||||
def create_notice(request):
|
||||
if request.method == 'POST':
|
||||
form = BoardNoticeForm(request.POST)
|
||||
if form.is_valid():
|
||||
notice = form.save(commit=False)
|
||||
notice.author = request.user # 작성자 정보 추가
|
||||
notice.save()
|
||||
return redirect('board_notice:notice_list')
|
||||
else:
|
||||
form = BoardNoticeForm()
|
||||
return render(request, 'board_notice/create_notice.html', {'form': form})
|
||||
|
||||
# 공지사항 게시글 수정
|
||||
@login_required
|
||||
def update_notice(request, pk):
|
||||
notice = get_object_or_404(BoardNotice, pk=pk)
|
||||
|
||||
# 작성자만 수정 가능하도록 권한 검사
|
||||
if notice.author != request.user:
|
||||
return redirect('board_notice:notice_list')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = BoardNoticeForm(request.POST, instance=notice)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('board_notice:notice_detail', pk=notice.pk)
|
||||
else:
|
||||
form = BoardNoticeForm(instance=notice)
|
||||
|
||||
return render(request, 'board_notice/update_notice.html', {'form': form, 'notice': notice})
|
||||
|
||||
# 공지사항 게시글 삭제
|
||||
@login_required
|
||||
def delete_notice(request, pk):
|
||||
notice = get_object_or_404(BoardNotice, pk=pk)
|
||||
|
||||
# 작성자만 삭제 가능하도록 권한 검사
|
||||
if notice.author != request.user:
|
||||
return redirect('board_notice:notice_list')
|
||||
|
||||
if request.method == 'POST':
|
||||
notice.delete()
|
||||
return redirect('board_notice:notice_list')
|
||||
|
||||
return render(request, 'board_notice/confirm_delete.html', {'notice': notice})
|
@ -1,5 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from .models import IPManagementRecord, NoticeBoard
|
||||
from .models import IPManagementRecord
|
||||
|
||||
admin.site.register(IPManagementRecord)
|
||||
admin.site.register(NoticeBoard)
|
||||
|
@ -1,11 +1 @@
|
||||
from django import forms
|
||||
from .models import NoticeBoard
|
||||
|
||||
class PostForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = NoticeBoard
|
||||
fields = ['title', 'contents']
|
||||
widgets = {
|
||||
'title': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'contents': forms.Textarea(attrs={'class': 'form-control', 'id': 'markdown-editor'}),
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.14 on 2025-01-25 11:19
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('butler', '0002_noticeboard'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='ipmanagementrecord',
|
||||
old_name='desc',
|
||||
new_name='contents',
|
||||
),
|
||||
]
|
@ -7,7 +7,7 @@ class IPManagementRecord(models.Model):
|
||||
network_nm = models.CharField(max_length=100) # NETWORK_NM
|
||||
ip_addrs = models.CharField(max_length=50) # IP_ADDRS
|
||||
svr_nm = models.CharField(max_length=100) # SVR_NM
|
||||
desc = models.TextField(blank=True, null=True) # DESC
|
||||
contents = models.TextField(blank=True, null=True) # DESC
|
||||
remark = models.TextField(blank=True, null=True) # REMARK
|
||||
created_at = models.DateTimeField(auto_now_add=True) # 생성 시간 자동 기록
|
||||
updated_at = models.DateTimeField(auto_now=True) # 수정 시간 자동 기록
|
||||
@ -15,21 +15,4 @@ class IPManagementRecord(models.Model):
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.network_nm} {self.ip_addrs} {self.svr_nm} {self.desc}'
|
||||
|
||||
|
||||
class NoticeBoard(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
contents = models.TextField(blank=True, null=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True) # 생성 시간 자동 기록
|
||||
updated_at = models.DateTimeField(auto_now=True) # 수정 시간 자동 기록
|
||||
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.title} {self.author} {self.created_at}'
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('butler:notice_detail', args=[str(self.pk)])
|
||||
# <a href="{% url 'butler:notice_detail' record.pk %}">{{ record.title }}</a>
|
||||
return f'{self.network_nm} {self.ip_addrs} {self.svr_nm} {self.contents}'
|
@ -3,46 +3,46 @@
|
||||
{% block title %}Landing Page{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<!-- NoticeBoard Section -->
|
||||
<div class="col-md-6">
|
||||
<h2>Latest Notices</h2>
|
||||
<div class="row">
|
||||
{% for notice in notices %}
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ notice.title }}</h5>
|
||||
<p class="card-text">{{ notice.content|truncatewords:20 }}</p>
|
||||
<a href="{% url 'butler:notice_detail' notice.id %}" class="btn btn-primary">View Notice</a>
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<!-- NoticeBoard Section -->
|
||||
<div class="col-md-6">
|
||||
<h2>Latest Notices</h2>
|
||||
<div class="row">
|
||||
{% for notice in board_notices %}
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ notice.title }}</h5>
|
||||
<p class="card-text">{{ notice.contents|truncatechars:50 }}</p>
|
||||
<a href="{{ notice.get_absolute_url }}" class="btn btn-primary">View Notice</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>No notices available.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>No notices available.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Blog Posts Section -->
|
||||
<div class="col-md-6">
|
||||
<h2>Latest Blog Posts</h2>
|
||||
<div class="row">
|
||||
{% for post in blog_posts %}
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ post.title }}</h5>
|
||||
<p class="card-text">{{ post.summary|truncatewords:20 }}</p>
|
||||
<a href="{% url 'blog:post_detail' post.id %}" class="btn btn-primary">Read More</a>
|
||||
<!-- Blog Posts Section -->
|
||||
<div class="col-md-6">
|
||||
<h2>Latest Blog Posts</h2>
|
||||
<div class="row">
|
||||
{% for post in blog_posts %}
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ post.title }}</h5>
|
||||
<p class="card-text">{{ post.contents|truncatechars:50 }}</p>
|
||||
<a href="{% url 'blog:post_detail' post.id %}" class="btn btn-primary">Read More</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>No blog posts available.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>No blog posts available.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,19 +0,0 @@
|
||||
{% extends "components/base.html" %}
|
||||
|
||||
{% block title %}Notice Detail{% endblock %}
|
||||
|
||||
{% block main_area %}
|
||||
<h2 class="fw-bold pt-3 pb-2">{{ notice.title }}</h2>
|
||||
<hr>
|
||||
<div>
|
||||
{{ notice.contents|linebreaks }}
|
||||
</div>
|
||||
<hr>
|
||||
<p class="text-muted">작성자:
|
||||
{{ notice.author }}</p>
|
||||
<p class="text-muted">작성일:
|
||||
{{ notice.created_at|date:"Y-m-d H:i" }}</p>
|
||||
<p class="text-muted">수정일:
|
||||
{{ notice.updated_at|date:"Y-m-d H:i" }}</p>
|
||||
<a href="{% url 'butler:notice_list' %}" class="btn btn-secondary mt-3">Back to Notices</a>
|
||||
{% endblock %}
|
@ -8,9 +8,9 @@ urlpatterns = [
|
||||
# Landing Page
|
||||
path('', views.LandingPageView.as_view(), name='landing'), # 클래스 기반 뷰(CBV) 호출
|
||||
|
||||
path('notice', views.notice_list, name='notice_list'),
|
||||
path('create_notice/', views.create_notice, name='create_notice'), # 포스트 작성
|
||||
path('notice/<int:pk>/', views.notice_detail_view, name='notice_detail'),
|
||||
# path('notice', views.notice_list, name='notice_list'),
|
||||
# path('create_notice/', views.create_notice, name='create_notice'), # 포스트 작성
|
||||
# path('notice/<int:pk>/', views.notice_detail_view, name='notice_detail'),
|
||||
path('ip_mgmt', views.ip_mgmt_view, name='ip_mgmt'),
|
||||
path('ip-mgmt/add/', views.add_ip_record, name='ip_mgmt_add'), # 데이터 추가 경로
|
||||
path('ip-mgmt/delete/', views.delete_ip_records, name='ip_mgmt_delete'), # 삭제 URL 추가
|
||||
|
@ -4,9 +4,9 @@ from django.views.generic import TemplateView
|
||||
from pathlib import Path
|
||||
import markdown
|
||||
import os
|
||||
from .models import NoticeBoard, IPManagementRecord
|
||||
from .models import IPManagementRecord
|
||||
from blog.models import Post
|
||||
from .forms import PostForm
|
||||
from board_notice.models import BoardNotice
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
@ -15,39 +15,11 @@ class LandingPageView(TemplateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# context['var'] var를 templates로 전달해서 보여지는 것.
|
||||
context['blog_posts'] = Post.objects.order_by('-created_at')[:3]
|
||||
context['notices'] = NoticeBoard.objects.order_by('-created_at')[:3]
|
||||
context['board_notices'] = BoardNotice.objects.order_by('-created_at')[:3]
|
||||
return context
|
||||
|
||||
# --- notice ---
|
||||
@login_required
|
||||
def create_notice(request):
|
||||
if request.method == 'POST':
|
||||
form = PostForm(request.POST)
|
||||
if form.is_valid():
|
||||
post = form.save(commit=False)
|
||||
post.author = request.user # 작성자 정보 추가
|
||||
post.save()
|
||||
form.save_m2m()
|
||||
return redirect('butler:notice_list')
|
||||
else:
|
||||
form = PostForm()
|
||||
return render(request, 'butler/create_notice.html', {'form': form})
|
||||
|
||||
def notice_list(request):
|
||||
records = NoticeBoard.objects.all()
|
||||
return render(request, "butler/notice_list.html", {"records": records})
|
||||
|
||||
|
||||
def notice_detail_view(request, pk):
|
||||
try:
|
||||
notice = NoticeBoard.objects.get(pk=pk)
|
||||
except NoticeBoard.DoesNotExist:
|
||||
return render(request, "404.html") # 선택적으로 에러 처리
|
||||
|
||||
return render(request, "butler/notice_detail.html", {"notice": notice})
|
||||
|
||||
|
||||
# --- ip management ---
|
||||
# def ip_mgmt_view(request):
|
||||
# # records = IPManagementRecord.objects.all()
|
||||
|
@ -41,6 +41,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'taggit',
|
||||
# Custom by.icurfer
|
||||
'board_notice',
|
||||
'custom_auth',
|
||||
'butler',
|
||||
'blog',
|
||||
|
@ -24,10 +24,11 @@ urlpatterns = [
|
||||
path('', include('butler.urls')),
|
||||
path('admin/', admin.site.urls),
|
||||
path('accounts/', include('custom_auth.urls')), # custom_auth URL 추가
|
||||
path('notice/', include('board_notice.urls')),
|
||||
path('blog/', include('blog.urls')),
|
||||
path('nhncloud/', include('nhncloud.urls')),
|
||||
path('nhnc_mgmt/', include('nhnc_mgmt.urls')),
|
||||
path('mm_msg/', include('mm_msg.urls')),
|
||||
path('blog/', include('blog.urls')),
|
||||
path('ansible_manager/', include('ansible_manager.urls')),
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user