본문 바로가기

문돌이 존버/Django 스터디

윈도우(window) 버전 Django 세팅 및 코드 예제

반응형

지금까지 Mac 버전의 장고를 소개했는데요. 이번에는 윈도우 버전으로 장고를 설정하고 제가 현재 진행하고 있는 코드 예제를 공유하고자 합니다. 정말 기초적이지만 장고가 처음인 저에게는 시간이 꽤나 걸렸는데요! 그 어려움을 잊지 않기 위해 여기에 기록하겠습니다. 

윈도우 버전은 크게 다르지 않은데 Mac 터미널 대신 cmd 혹은 powershell을 사용한다는 점이 차이라고 할 수 있겠습니다. 명령어도 처음 가상환경 셋팅만 다르지 장고의 경우 모두 같습니다. 

python -m venv first-env를 입력하고 source ./first-env/Scripts/activate를 입력하면 가상 환경이 시작됩니다. first-env에 해당하는 부분은 자유롭게 이름을 정하시면 됩니다. 윈도우 보안 에러가 날 수도 있는데요. 이럴 경우, 관리자 모드로 powershell을 실행한 뒤 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned 명령어 입력 후 Y or A를 치시면 됩니다. 

이렇게 기본 셋팅이 끝나셨다면 장고 설치를 하면 되고요, 이때 설치 방법은 Mac 버전과 동일하여 생략하겠습니다. 한 가지, Mac에선 django_admin과 같이 "_"를 사용했는데, 윈도우 버전은 django-admin인 것 같더라고요. 상관없을 수도 있지만 참고 부탁드려요. 

이제부터 제가 작업하고 있는 코드 예제를 설명하겠습니다. DB 설계 및 구축도 처음인 저는 장고 ORM을 통해 쿼리 명령을 날리다보니 더 어렵게 다가오더라구요. 그럼에도 raw SQL을 따로 설정하지 않고 꾸역꾸역 해나갔습니다. 일단 models.py 코드는 다음과 같습니다. 

from django.db import models

# Create your models here.
class TBL_DEVICE_INFO(models.Model):
    status = models.BooleanField()
    station = models.CharField(max_length=50)
    region = models.CharField(max_length=50)
    installdate = models.DateField()

    def __str__(self):
        return str(self.id)

class TBL_PERSON_INFO(models.Model):
    degree = models.FloatField()
    hasMask = models.BooleanField()
    regdate = models.DateTimeField(auto_now_add=True)
    deviceid = models.ForeignKey(
        TBL_DEVICE_INFO,
        on_delete=models.CASCADE,
        null=False
    )
    # img = models.BinaryField(blank=True)

    def __str__(self):
        return str(self.degree)

class TBL_TOTAL_INFO(models.Model):
    deviceid = models.IntegerField()
    total_pop = models.IntegerField()
    mask_pop = models.IntegerField()
    hightemp_pop = models.IntegerField()
    region = models.CharField(max_length=50)

    def __str__(self):
        return str(self.total_pop)

TBL_DEVICE_INFO는 디바이스(카메라) 관련 테이블이고, TBL_PERSON_INFO는 카메라에 찍힌 사람들 정보 관련 테이블입니다. TBL_TOTAL_INFO는 카메라 앞을 지나친 사람들의 하루 정보를 총체적으로 나타내기 위한 테이블입니다. 다음은 views.py에 해당하는 코드입니다.

from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpResponseRedirect
from django.db.models import Count, Case, When, Sum, IntegerField
# Create your views here.
from .models import *
from .forms import *

def hello_world(request):

    return render(request, 'home.html')

def device(request):
    if request.method == 'POST':
        form = TBL_DEVICE_INFOFORM(request.POST)
        if form.is_valid():
            try:
                form.save()
                return redriect('device')
            except:
                pass
    else:
        form = TBL_DEVICE_INFOFORM()

    context = {
            'form': form
        }
    return render(request, 'device.html', context)

def device_show(request):
    devices = TBL_DEVICE_INFO.objects.all()
    context = {
        'devices': devices
    }
    return render(request, 'device_show.html', context)

def device_edit(request, tbl_device_info_id):
    device = TBL_DEVICE_INFO.objects.get(id=tbl_device_info_id)
    if request.method == 'POST':
        form = TBL_DEVICE_INFOFORM(request.POST)
        if form.is_valid():
            device.status = form.cleaned_data['status']
            device.station = form.cleaned_data['station']
            device.region = form.cleaned_data['region']
            device.installdate = form.cleaned_data['installdate']
            device.save()
            return redirect('device_show')
    else:
        form = TBL_DEVICE_INFOFORM(instance = device)

        context = {
            'form': form
        }
        return render(request, 'device_edit.html', context)

def device_destroy(request, tbl_device_info_id):
    device = TBL_DEVICE_INFO.objects.get(id=tbl_device_info_id)
    device.delete()
    return redirect('device_show')

def person(request):
    if request.method == 'POST':
        form = TBL_PERSON_INFOFORM(request.POST)
        if form.is_valid():
            try:
                form.save()
                return redriect('person')
            except:
                pass
    else:
        form = TBL_PERSON_INFOFORM()

    context = {
            'form': form
        }
    return render(request, 'person.html', context)

def person_show(request):
    persons = TBL_PERSON_INFO.objects.all()
    context = {
        'persons': persons
    }
    return render(request, 'person_show.html', context)

def person_edit(request, tbl_person_info_id):
    person = TBL_PERSON_INFO.objects.get(id=tbl_person_info_id)
    if request.method == 'POST':
        form = TBL_PERSON_INFOFORM(request.POST)
        if form.is_valid():
            person.degree = form.cleaned_data['degree']
            person.hasMask = form.cleaned_data['hasMask']
            person.deviceid = form.cleaned_data['deviceid']
            person.save()
            return redirect('person_show')
    else:
        form = TBL_PERSON_INFOFORM(instance = person)

        context = {
            'form': form
        }
        return render(request, 'person_edit.html', context)

def person_destroy(request, tbl_person_info_id):
    person = TBL_PERSON_INFO.objects.get(id=tbl_person_info_id)
    person.delete()
    return redirect('person_show')
    
def total_table(request):
	num = TBL_PERSON_INFO.objects.values('deviceid').annotate(count=Count('deviceid'))
    if request.method == 'GET':
        for i in range(num.count()):
            total = TBL_PERSON_INFO.objects.filter(deviceid=num[i]['deviceid'])
            mask = TBL_PERSON_INFO.objects.filter(deviceid=num[i]['deviceid'], hasMask=True)
            degree = TBL_PERSON_INFO.objects.filter(deviceid=num[i]['deviceid'], degree__gte=37.5) #__gte: greater than / __lte: less than
        
            deviceid = total[0].deviceid_id
            total_pop = total.count()
            mask_pop = mask.count()
            hightemp_pop = degree.count()
            region = total[0].deviceid.region

            if TBL_TOTAL_INFO.objects.filter(deviceid=num[i]['deviceid']):
                TBL_TOTAL_INFO.objects.filter(deviceid=num[i]['deviceid']).update(total_pop=total_pop, mask_pop=mask_pop, hightemp_pop=hightemp_pop)

            else:
                TBL_TOTAL_INFO.objects.create(deviceid=deviceid, total_pop=total_pop, mask_pop=mask_pop, hightemp_pop=hightemp_pop, region=region)

    total_table = TBL_TOTAL_INFO.objects.all()

    context = {
        'total_table': total_table
    }
    return render(request, 'total.html', context)

POST request method를 통해서 데이터를 받았고, form 형식을 썼습니다. 마지막 함수 total_table의 경우, 개인적으로 정말 헷갈리고 지금의 버전이 괜찮은지도 모르겠습니다. raw SQL을 써면 더 간단하게 코드가 나올 듯 한데, 장고 ORM으로는 지금이 최선이네요 ㅠ 더 나은 버전이 있으면 한 수 가르쳐주시면 감사하겠습니다. 다음은 app 아래 있는 urls.py에 해당하는 코드입니다.

from django.urls import path
from django.conf.urls import url

from . import views

urlpatterns = [
    path('', views.hello_world),
    path('device/', views.device, name='device'),
    path('deviceshow/', views.device_show, name='device_show'),
    path('deviceedit/<int:tbl_device_info_id>', views.device_edit, name='device_edit'),
    path('devicedelete/<int:tbl_device_info_id>', views.device_destroy, name='device_delete'),
    path('person/', views.person, name='person'),
    path('personshow/', views.person_show, name='person_show'),
    path('personedit/<int:tbl_person_info_id>', views.person_edit, name='person_edit'),
    path('persondelete/<int:tbl_person_info_id>', views.person_destroy, name='person_delete'),
    path('total/', views.total_table, name='total')
]

project 아래 urls.py는 다음과 같습니다.

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('monitoring/', include("monitor_app.urls"))
]

마지막으로 html templates를 살펴보겠는데요. 저는 총 9개의 html 파일을 생성했는데 중복되지 않게 대표적인 것들만 공유하겠습니다. 먼저 base.html 코드를 살펴볼께요.

{% load static %}
<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <!--<link rel="stylesheet" type="text/css" href="{% static 'monitor_app/css/styles.css' %}">-->
    <title> Real time Monitoring</title>
</head>
<body>
    
    <div class="jumbotron jumbotron-fluid" style="background: url({% static "bgi.png" %}) no-repeat center center fixed; -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover; background-size: cover;">
        <div class="container text-white">
            <h1 class="display-3"> <a href="/monitoring" style="color:black; font-weight:bold;"> Thermal Monitoring System </a> </h1>
            <hr class="my-4 bg-secondary">
            <p class="text-right" style="color:black; font-weight:bold;"> by Hanium </p>
        </div>
    </div>
    {% block header %}

    {% endblock %}
    <div class="container">
        {% block content %}

        {% endblock %}

    </div>
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>

style="background: url({% static "big.png" %}) 부분이 익숙하지 않을 수 있는데 app 아래 static이란 폴더를 만들고 그 안에 원하는 배경 이미지를 저장해놓으면 됩니다. 다음은 device 정보를 입력하는 html 파일인데요. html은 각자가 원하는 스타일대로 설정하면 되는 것이고, 가장 중요한 부분은 아무래도 {% csrf_token %} 인 것 같은데요. 장고에선 해킹을 방지하기 위해 이를 반드시 입력해야 데이터를 추가할 수 있습니다. 

{% extends 'base.html' %}
{% load static %}
{% block content %}

<body>  
    <form method="POST" class="post-form" action="{% url 'device' %}">  
            {% csrf_token %}  
        <div class="container">  
    <br>  
        <div class="form-group row">  
        <label class="col-sm-1 col-form-label"></label>  
        <div class="col-sm-4">  
        <h3>Enter Details</h3>  
        </div>  
      </div>  
        <div class="form-group row">  
        <label class="col-sm-2 col-form-label">Device Id:</label>  
        <div class="col-sm-4">  
          {{ form.id }}  
        </div>  
      </div>  
      <div class="form-group row">  
        <label class="col-sm-2 col-form-label">Status:</label>  
        <div class="col-sm-4">  
          {{ form.status }}  
        </div>  
      </div>  
        <div class="form-group row">  
        <label class="col-sm-2 col-form-label">Station:</label>  
        <div class="col-sm-4">  
          {{ form.station }}  
        </div>  
      </div>  
        <div class="form-group row">  
        <label class="col-sm-2 col-form-label">Region:</label>  
        <div class="col-sm-4">  
          {{ form.region }}  
        </div>  
      </div>  
      <div class="form-group row">  
        <label class="col-sm-2 col-form-label">Install Date:</label>  
        <div class="col-sm-4">  
          {{ form.installdate }}  
        </div>  
      </div>  
        <div class="form-group row">  
        <label class="col-sm-1 col-form-label"></label>  
        <div class="col-sm-4">  
        <button type="submit" class="btn btn-primary">Submit</button>  
        </div>  
      </div>  
        </div>  
    </form>  
    </body>  

{% endblock %}

다음은 디바이스 정보를 보여주는 html 파일입니다. 마찬가지로 참고만 해주시고 본인 스타일대로 다시 코딩하면 됩니다.

{% extends 'base.html' %}
{% load static %}
{% block content %}

<body>  
    <div class='row p-3 mb-2'>
        <div class='col-md-12'>
    <table class="table table-striped table-bordered table-sm">  
        <thead class="thead-dark">  
        <tr>  
            <th>Device ID</th>  
            <th>Status</th>  
            <th>Station</th>  
            <th>Region</th>  
            <th>Install Date</th>
            <th>Actions</th>
        </tr>  
        </thead>  
        <tbody>  
    {% for device in devices %}  
        <tr>  
            <td>{{ device.id }}</td>  
            <td>{{ device.status }}</td>  
            <td>{{ device.station }}</td>  
            <td>{{ device.region }}</td>
            <td>{{ device.installdate }}</td>  
            <td>  
                <a href="{% url 'device_edit' device.id %}"><span class="glyphicon glyphicon-pencil" >Edit</span></a>  
                <a href="{% url 'device_delete' device.id %}">Delete</a>  
            </td>  
        </tr>  
    {% endfor %}  
        </tbody>  
    </table>  
    <br>  
    <br>  
    <center><a href="{% url 'device' %}" class="btn btn-primary">Add New Record</a></center>  
        </div>
    </div>
</body>  
{% endblock %}

마지막으로 보여드릴 html 파일은 데이터를 수정하는 부분인데요. <thead>, <tr>, <td> 등을 작성할 필요 없이 {{ form.as_table }}로 간단하게 수정 테이블을 만들어주었습니다. 

{% extends 'base.html' %}
{% load static %}
{% block content %}


<body>  
    <form method="POST" class="post-form">  
            {% csrf_token %}  
        <div class="container">
          <div class="form-group row">  
            <label class="col-sm-1 col-form-label"></label>  
            <div class="col-sm-4">  
            <h3>Update Details</h3>  
            </div>  
          </div>  
          <div class="form-group row">  
            <label class="col-sm-1 col-form-label"></label>  
            <div class="col-sm-4"> 
          <table class="table table-striped table-bordered table-sm">
            {{ form.as_table }}
          </table>
        </div>  
      </div> 
        <div class="form-group row">  
        <label class="col-sm-1 col-form-label"></label>  
        <div class="col-sm-4">  
        <button type="submit" class="btn btn-success">Update</button>  
        </div>  
      </div>  
        </div>  
    </form>  
    </body>  

{% endblock %}

이렇게 잘 따라오셨다면 아래와 같은 웹 페이지를 보실 수 있을 것입니다. 혹여나 문제나 오류가 발생한다면 댓글로 문의해주세요~ 

<device 정보 페이지>
<total_table 정보 페이지>

728x90
반응형