공지사항관리 기능 분리 및 개선
All checks were successful
Build And Test / build-and-push (push) Successful in 4m23s

This commit is contained in:
2025-01-25 18:02:49 +09:00
parent 06d1853fb0
commit af57b56e69
38 changed files with 710 additions and 188 deletions

View File

@ -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)

View File

@ -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'}),
}

View File

@ -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',
),
]

View File

@ -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}'

View File

@ -1,50 +0,0 @@
{% 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 Post</button>
<a href="{% url 'butler: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>
<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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -1,44 +0,0 @@
{% extends "components/base.html" %}
{% block title %}Notice{% endblock %}
{% block main_area %}
<h2 class="fw-bold pt-3 pb-2 d-flex justify-content-between">
공지사항
</h2>
<!-- 공지사항 목록 -->
{% if records %}
<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>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr>
<td>{{ record.pk }}</td>
<td>
<a href="{{ record.get_absolute_url }}">{{ record.title }}</a>
</td>
<td>{{ record.author }}</td>
<td>{{ record.created_at|date:"Y-m-d H:i" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<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>
{% endif %}
{% endblock %}

View File

@ -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 추가

View File

@ -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()