묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결
웹개발 진로에 대해서 질문드리고싶습니다.
안녕하세요? 저는 현재 웹개발 백엔드공부중인 컴공 졸업생입니다.인프런에서 스프링 강의를 수강하고 있습니다.제가 나이도 좀 있고 공백기도 있어서 고민이 있습니다.현재 제가 공부중인, 웹개발 스프링을 공부하면, 웹 개발 직종뿐만 아니라 다른 유사한 직종으로도 들어갈 수 있을까요?개발자 아무나 하는 것이 아니라는 말도 많이 들었고, 처음부터 웹 개발 회사 진입도 어렵다는 말을 많이 들었어서 고민이 됩니다.스프링 웹개발 분야를 공부했을 때, 개발직종말고 이와 유사한 직종 들어갈 수 있는 곳들도 있을까요?예를들면, 금융회사라든지 이런 부차적으로 개발을 하는 곳들로요.빨리 취업하는 것이 목표라서 질문드립니다. 감사합니다.
-
해결됨IT 회사에서 비개발자가 살아남기 위한 모든 개발 지식 A to Z
강의 보던 중 프론트엔드 개발자 역할에 대해 질문 있습니다.
Q1. 강의를 보니, 배포는 벡엔드 쪽에서 담당하는 것같은데요. 그런데, 프론트엔드 면접시에 배포에 관한 이해를 요구하는 이유가 뭔가요?? Q2. 큰 기업같은 경우는 배포만 담당하는 부서가 따로 있다고 들었는데요. 그런게 아니라면, 일반적으로 프론트엔드 개발자가 실무에서 배포를 다루는 경우가 있나요?? Q3. 저는 현재 프론트엔드 취준생인데요. "파이어베이스"나 "헤로쿠" 정도만 사용하고 있습니다. 근데, 많은 곳에서 AWS 사용량이 압도적이다보니, 취업을 위해 AWS 사용법을 필수적으로 알아야할지 여줘보고 싶습니다. Q4. 간혹 프론트엔드 개발자 뽑는다면서, 리엑트 네이티브나 스위프트를 이용한 앱 개발까지 같이할 수 있는 능력을 요구하는 곳이 있는데요. 요즘은 프론트엔드 개발자가 웹 + 앱 개발을 둘다 하는게 당연한 추세인가요??
-
해결됨남박사의 파이썬으로 실전 웹사이트 만들기
리눅스 우분투 서버에서 배포하기 강좌중 몽고db연동..
안녕하세요 남박사님 이번에 진도를 진행하다가... 우분투에서 몽고db연결하는 부분을 따라하다가 진행이 안되서 다시 질문 올립니다! 구글링 해봐도 몽고db사이트가서 봐도... 이해가 잘 안가더라구요.. 터미널에서 20:44초 처럼 코드를 작성하니까.. key값을 가지고 올수 없다고 하는데.. 기존방법에서 다른 방법으로 바뀌었을까요..? 버추얼 머신때문은 아니겠죠..? ms꺼는 설치가 아예안되서 버추얼박스로 잠깐 사용하다가 렉도심하고 잘안되서요.. 현재 내용에서 진도가 안나가서 지금은 VMware 설치해서 사용중입니다..!
-
미해결
이런 사이트를 만들고 운영하기위해선 뭘 배워야 할까요?
마케팅 업무에 종사하고 있고 프로그램 제작에 관심이 많은데요 그 중 꼭 개발해보고 싶은 서비스가 있어 문의드려봅니다. ndev.co.kr 이라는 사이트구요 네이버 마케팅과 관련된 정보를 제공하는 유료사이트입니다. 공개되어있는 서비스 설명글을 보면 아래 기능들을 제공한다고 되어있는데요 개인이 1인 개발자로서 이런 서비스를 제작하고 운영하기위해선 어떤 기술들을 배워야하는지 알려주시면 감사하겠습니다!
-
해결됨남박사의 파이썬으로 실전 웹사이트 만들기
(게시판 댓글구현 오류) count 부분에서 오류가 나는데.. 어떻게 해야할까요..ㅠ?
안녕하세요 남박사님 남박사님 덕분에 강의를 잘듣고 진도를 나가고 있습니다. 이해가 안가는 부분도 아직 있지만 열심히 배울려고 노력하고 있는데요! 이번 댓글기능 만들기 부분에서 오류나는 부분있어서 질문올립니다. 댓글기능 구연하고 마지막 부분에서 jinja2.exceptions.UndefinedError: 'pymongo.cursor.Cursor object' has no attribute 'count'라고 오류가 나는데.. 저번에 {% if comments.count() > 0 %} 이부분이 몽고db 부분하고 연동이 안되는거 같아서 버전 차이라고 하셔서 count_documents({조건})로 변경해봤는데.. 오류는 나더라구요..! - 오류 - -코드 - -board.py- from main import * from flask import Blueprint, send_from_directory from flask import send_from_directory bluerprint = Blueprint("board", __name__, url_prefix="/board") def board_delete_attach_file(filename): abs_path = os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename) if os.path.exists(abs_path): os.remove(abs_path) return True return False @bluerprint.route("/comment_write", methods=["POST"]) @login_required def comment_write(): if request.method == "POST": print("POST OK") name = session.get("name") writer_id = session.get("id") root_idx = request.form.get("root_idx") comment = request.form.get("comment") current_utc_time = round(datetime.utcnow().timestamp() * 1000) c_comment = mongo.db.comment post = { "root_idx": str(root_idx), "writer_id": writer_id, "name": name, "comment": comment, "pubdate": current_utc_time } print(post) c_comment.insert_one(post) return redirect(url_for("board.board_view", idx=root_idx)) @bluerprint.route("/upload_image", methods=["POST"]) def upload_image(): if request.method == "POST": file = request.files["image"] if file and allowed_file(file.filename): filename = "{}.jpg".format(rand_generator()) savefilepath = os.path.join(app.config["BOARD_IMAGE_PATH"], filename) file.save(savefilepath) return url_for("board.board_images", filename=filename) @bluerprint.route("/images/<filename>") def board_images(filename): return send_from_directory(app.config["BOARD_IMAGE_PATH"], filename) @bluerprint.route("/files/<filename>") def board_files(filename): return send_from_directory(app.config["BOARD_ATTACH_FILE_PATH"], filename, as_attachment=True) @bluerprint.route("/list") def lists(): # 페이지 값 (값이 없는 경우 기본값는 1) page = request.args.get("page", 1, type=int) # 한페이지당 몇개의 게시물을 출력할지 limit = request.args.get("limit", 5, type=int) search = request.args.get("search", -1, type=int) keyword = request.args.get("keyword", "", type=str) # 최종적으로 완성된 쿼리를 만들 변수 query = {} # 검색어 상태를 추가할 리스트 변수 search_list = [] if search == 0: search_list.append({"title": {"$regex": keyword}}) elif search == 1: search_list.append({"contents": {"$regex": keyword}}) elif search == 2: search_list.append({"title": {"$regex": keyword}}) search_list.append({"contents": {"$regex": keyword}}) elif search == 3: search_list.append({"name": {"$regex": keyword}}) # 검색 대상이 한개라도 존재할 경우 query 변수에 $or 리스트를 쿼리 합니다. if len(search_list) > 0: query = {"$or": search_list} print(query) board = mongo.db.board datas = board.find({}).skip( (page - 1) * limit).limit(limit).sort("pubdate", -1) # 게시물의 총 갯수 tot_count = board.count_documents({}) # 마지막 페이지의 수를 구한다. last_page_num = math.ceil(tot_count / limit) # 페이지 블럭을 5개씩 표기 block_size = 5 # 현재 블럭의 위치 block_num = int((page - 1) / block_size) # 블럭의 시작 위치 block_start = int((block_size * block_num) + 1) # 블럭의 끝 위치 block_last = math.ceil(block_start + (block_size - 1)) return render_template( "list.html", datas=list(datas), limit=limit, page=page, block_start=block_start, block_last=block_last, last_page_num=last_page_num, search=search, keyword=keyword, title="게시판 리스트") @bluerprint.route("/view/<idx>") @login_required def board_view(idx): # idx = request.args.get("idx") if idx is not None: page = request.args.get("page") search = request.args.get("search") keyword = request.args.get("keyword") board = mongo.db.board # data = board.find_one({"_id": ObjectId(idx)}) data = board.find_one_and_update({"_id": ObjectId(idx)}, { "$inc": {"view": 1}}, return_document=True) if data is not None: result = { "id": data.get("_id"), "name": data.get("name"), "title": data.get("title"), "contents": data.get("contents"), "pubdate": data.get("pubdate"), "view": data.get("view"), "writer_id": data.get("writer_id", ""), "attachfile": data.get("attachfile", "") } comment = mongo.db.comment comments = comment.find({"root.idx": str(data.get("_id"))}) return render_template("view.html", result=result, comments=comments, page=page, search=search, keyword=keyword, title="글 상세보기") return abort(404) @bluerprint.route("/write", methods=["GET", "POST"]) def board_write(): if request.method == "POST": filename = None if "attachfile" in request.files: file = request.files["attachfile"] if file and allowed_file(file.filename): filename = check_filename(file.filename) file.save(os.path.join(app.config['BOARD_ATTACH_FILE_PATH'], filename)) name = request.form.get("name") title = request.form.get("title") contents = request.form.get("contents") request.files current_utc_time = round(datetime.utcnow().timestamp() * 1000) board = mongo.db.board post = { "name": name, "title": title, "contents": contents, "pubdate": current_utc_time, "writer_id": session.get("id"), "view": 0, } if filename is not None: post["attachfile"] = filename x = board.insert_one(post) print(x.inserted_id) return redirect(url_for("board.board_view", idx=x.inserted_id)) else: return render_template("write.html", title="글 작성") @bluerprint.route("/edit/<idx>", methods=["GET", "POST"]) def board_edit(idx): if request.method == "GET": board = mongo.db.board data = board.find_one({"_id": ObjectId(idx)}) if data is None: flash("해당 게시물이 존재하지 않습니다.") return redirect(url_for("board.lists")) else: if session.get("id") == data.get("writer_id"): return render_template("edit.html", data=data, title="글 수정") else: flash("글 수정 권한이 없습니다.") return redirect(url_for("board.lists")) else: title = request.form.get("title") contains = request.form.get("contents") deleteoldfile = request.form.get("deleteoldfile", "") board = mongo.db.board data = board.find_one({"_id": ObjectId(idx)}) if session.get("id") == data.get("writer_id"): filename = None if "attachfile" in request.files: file = request.files["attachfile"] if file and allowed_file(file.filename): filename = check_filename(file.filename) file.save(os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename)) if data.get("attachfile"): board_delete_attach_file(data.get("attachfile")) else: if deleteoldfile == "on": filename = None if data.get("attachfile"): board_delete_attach_file(data.get("attachfile")) else: filename = data.get("attachfile") board.update_one({"_id": ObjectId(idx)}, { "$set": { "title": title, "contents": contains, "attachfile": filename } }) flash("수정 되었습니다.") return redirect(url_for("board.board_view", idx=idx)) else: flash("글 수정 권한이 없습니다.") return redirect(url_for("board.lists")) @bluerprint.route("/delete/<idx>") def board_delete(idx): board = mongo.db.board data = board.find_one({"_id": ObjectId(idx)}) if data.get("writer_id") == session.get("id"): board.delete_one({"_id": ObjectId(idx)}) flash("삭제 되었습니다.") else: flash("삭제 권한이 없습니다.") return redirect(url_for("board.lists")) ------------------------------------------------------------------------------------------------------- - view.html- {% extends "main.html" %} {% block contents %} <div style="padding: 50px 50px 50px 50px;"> <table class="table table-bordered"> <tbody> <tr> <td colspan="2">{{result.title}}</td> </tr> <tr> <td>{{result.name}}</td> <td class="text-right">{{result.pubdate|formatdatetime}}</td> </tr> {% if result.attachfile %} <tr> <td>첨부파일</td> <td><a href="{{url_for('board.board_files', filename=result.attachfile)}}">{{result.attachfile}}</a></td> </tr> {% endif %} <tr> <td colspan="2"><div style="min-height: 200px;"></div>{% autoescape false %}{{result.contents}}{% endautoescape %}</td> </tr> </tbody> </table> <a class="btn btn-primary" href="{{url_for('board.lists', page=page, search=search, keyword=keyword)}}">리스트</a> {% if session["id"] == result.writer_id %} <a class="btn btn-danger float-right ml-2" href="{{url_for('board.board_delete', idx=result.id)}}">글삭제</a> <a class="btn btn-warning float-right" href="{{url_for('board.board_edit', idx=result.id)}}">글수정</a> {% endif %} <br> <br> <form id="commentForm" name="commentForm" action="{{url_for('board.comment_write')}}" method="POST"> <input type="hidden" name="csrf_token" value="{{csrf_token()}}"> <input type="hidden" name="root_idx" value="{{result.id}}"> <div> <span><strong>댓글</strong></span> <span id="cCnt"></span> <table class="table"> <tr> <td><textarea rows="3" cols="110" id="comment" name="comment" placeholder="댓글을 입력하세요"></textarea></td> <td><input type="submit" class="btn btn-success" style="height: 80px;" value="등록하기"></td> </tr> </table> </div> </form> {% if comments.count() > 0 %} {% for c in comments %} <div> <table class="table"> <tr> <td width="100"><h6>{{c.name}}</h6></td> <td>{{c.comment}}</td> <td class="text-right" width="200">{{c.pubdate | formatdatetime}}</td> </tr> </table> </div> {% endfor %} {% endif %} </div> {% endblock %}
-
해결됨남박사의 파이썬으로 실전 웹사이트 만들기
(회원가입 비밀번호 일치하지 않음 및 flask_wtf.csrf 오류) 보안강화 - 로그인 정보 암호화 하고 CSRF Protect 구현하기 강의중 오류..
남박사님 안녕하세요 이번 보안강화 - 로그인정보 암호화 강좌 진행중에.. 오류를 해결하지 못해서 질문 드립니다. 우선 내용은 이렇습니다. 1. werkzeug.security 설정진행중 패스워드 보안작업중에 "pass": hash_password(pass1) 로 변경후에 회원가입을 하면 비밀번호가 일치하지 않습니다라고 계속 나오더라구요.. 몽고db에서 내용지워도 마찬가지 입니다. 그전에는 정상적으로 로그인이 되었습니다. 2. flask_wtf.csrf import CSRFProtect 를 적용하는데 pip install flask-wtf를 설치해도 오류가 나오더라구요.. 문제 사진 1번 2번 3번 4번 5번 코드 - board.py- from main import * from flask import Blueprint bluerprint = Blueprint("member", __name__, url_prefix="/member") # @csrf.exempt @bluerprint.route("/join", methods=["GET", "POST"]) def member_join(): if request.method == "POST": name = request.form.get("name", type=str) email = request.form.get("email", type=str) pass1 = request.form.get("pass", type=str) pass2 = request.form.get("pass2", type=str) if name == "" or email == "" or pass1 == "" or pass2 == "": flash("입력되지 않은 값이 있습니다.") return render_template("join.html", title="회원가입") if pass1 != pass2: flash("비밀번호가 일치하지 않습니다.") return render_template("join.html", title="회원가입") members = mongo.db.members cnt = members.count_documents({"email": email}) if cnt > 0: flash("중복된 이메일 주소입니다.") return render_template("join.html", title="회원가입") current_utc_time = round(datetime.utcnow().timestamp() * 1000) post = { "name": name, "email": email, "pass": hash_password(pass1), "joindate": current_utc_time, "logintime": "", "logincount": 0, } members.insert_one(post) return redirect(url_for("member.member_login")) else: return render_template("join.html", title="회원가입") @bluerprint.route("/login", methods=["GET", "POST"]) def member_login(): if request.method == "POST": email = request.form.get("email") password = request.form.get("pass") next_url = request.form.get("next_url") members = mongo.db.members data = members.find_one({"email": email}) if data is None: flash("회원 정보가 없습니다.") return redirect(url_for("member.member_login")) else: if check_password(data.get("pass"), password): session["email"] = email session["name"] = data.get("name") session["id"] = str(data.get("_id")) session.permanent = True if next_url is not None: return redirect(next_url) else: return redirect(url_for("board.lists")) else: flash("비밀번호가 일치하지 않습니다.") return redirect(url_for("member.member_login")) return "" else: next_url = request.args.get("next_url", type=str) if next_url is not None: return render_template("login.html", next_url=next_url, title="회원로그인") else: return render_template("login.html", title="회원로그인") @bluerprint.route("/logout") def member_logout(): try: del session["name"] del session["id"] del session["email"] except: pass return redirect(url_for('member.member_login')) ---------------------------------------------------------------------------------------------------- - common.py - from functools import wraps from main import session, redirect, request, url_for, ALLOWED_EXTENSIONS from string import digits, ascii_uppercase, ascii_lowercase import random import re import os from werkzeug.security import generate_password_hash, check_password_hash def hash_password(password): return generate_password_hash(password) def check_password(hashed_password, user_password): return check_password_hash(hashed_password, user_password) def check_filename(filname): reg = re.compile("[^A-Za-z0-9_.가-힝-]") for s in os.path.sep, os.path.altsep: if s: filname = filname.replace(s, ' ') filname = str(reg.sub('', '_'.join(filname.split()))).strip("._") return filname def allowed_file(filename): return "." in filename and filename.rsplit(".", 1)[1] in ALLOWED_EXTENSIONS def rand_generator(length=8): char = ascii_lowercase + ascii_uppercase + digits return "".join(random.sample(char, length)) def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if session.get("id") is None or session.get("id") == "": return redirect(url_for("member.member_login", next_url=request.url)) return f(*args, **kwargs) return decorated_function ---------------------------------------------------------------------------------------------------- - __init__.py - from flask import Flask from flask import request from flask import render_template from flask_pymongo import PyMongo from bson.objectid import ObjectId from datetime import datetime, timedelta from flask import abort from flask import redirect from flask import url_for from flask import flash from flask import session # from flask_wtf.csrf import CSRFProtect import math import time import os app = Flask(__name__) app.config["MONGO_URI"] = "mongodb://localhost:27017/myweb" app.config["SECRET_KEY"] = "abcd" app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=30) mongo = PyMongo(app) BOARD_IMAGE_PATH = "C:\\python\\images" BOARD_ATTACH_FILE_PATH = "C:\\python\\uploads" ALLOWED_EXTENSIONS =set(["txt", "pdf", "png", "jpg", "jpeg", "gif"]) app.config["BOARD_IMAGE_PATH"] = BOARD_IMAGE_PATH app.config["BOARD_ATTACH_FILE_PATH"] = BOARD_ATTACH_FILE_PATH app.config["MAX-CONTENT_LENGTH"] = 15 * 1024 * 1024 if not os.path.exists(app.config["BOARD_IMAGE_PATH"]): os.mkdir(app.config["BOARD_IMAGE_PATH"]) if not os.path.exists(app.config["BOARD_ATTACH_FILE_PATH"]): os.mkdir(app.config["BOARD_ATTACH_FILE_PATH"]) from .common import login_required, allowed_file, rand_generator, check_filename, hash_password, check_password from .filter import format_datetime from . import board from . import member app.register_blueprint(board.bluerprint) app.register_blueprint(member.bluerprint) ---------------------------------------------------------------------------------------------------- -member.py- from main import * from flask import Blueprint bluerprint = Blueprint("member", __name__, url_prefix="/member") # @csrf.exempt @bluerprint.route("/join", methods=["GET", "POST"]) def member_join(): if request.method == "POST": name = request.form.get("name", type=str) email = request.form.get("email", type=str) pass1 = request.form.get("pass", type=str) pass2 = request.form.get("pass2", type=str) if name == "" or email == "" or pass1 == "" or pass2 == "": flash("입력되지 않은 값이 있습니다.") return render_template("join.html", title="회원가입") if pass1 != pass2: flash("비밀번호가 일치하지 않습니다.") return render_template("join.html", title="회원가입") members = mongo.db.members cnt = members.count_documents({"email": email}) if cnt > 0: flash("중복된 이메일 주소입니다.") return render_template("join.html", title="회원가입") current_utc_time = round(datetime.utcnow().timestamp() * 1000) post = { "name": name, "email": email, "pass": hash_password(pass1), "joindate": current_utc_time, "logintime": "", "logincount": 0, } members.insert_one(post) return redirect(url_for("member.member_login")) else: return render_template("join.html", title="회원가입") @bluerprint.route("/login", methods=["GET", "POST"]) def member_login(): if request.method == "POST": email = request.form.get("email") password = request.form.get("pass") next_url = request.form.get("next_url") members = mongo.db.members data = members.find_one({"email": email}) if data is None: flash("회원 정보가 없습니다.") return redirect(url_for("member.member_login")) else: if check_password(data.get("pass"), password): session["email"] = email session["name"] = data.get("name") session["id"] = str(data.get("_id")) session.permanent = True if next_url is not None: return redirect(next_url) else: return redirect(url_for("board.lists")) else: flash("비밀번호가 일치하지 않습니다.") return redirect(url_for("member.member_login")) return "" else: next_url = request.args.get("next_url", type=str) if next_url is not None: return render_template("login.html", next_url=next_url, title="회원로그인") else: return render_template("login.html", title="회원로그인") @bluerprint.route("/logout") def member_logout(): try: del session["name"] del session["id"] del session["email"] except: pass return redirect(url_for('member.member_login')) ---------------------------------------------------------------------------------------------------- - write.html - {% extends "main.html" %} {% block contents %} <script> $(document).ready(function() { $("#summernote").summernote({ height: 300, minHeight: null, maxHeight: null, lang: "ko-KR", popover: { image:[], link:[], air:[] }, callbacks: { onImageUpload: function(image) { for(var i = 0; i < image.length; i++) { uploadImage(image[i]); } } } }); }); function uploadImage(image) { var data = new FormData(); data.append("image", image); var csrf_token = "{{csrf_token()}}"; $.ajaxSetup({ beforeSend: function(x, s) { if(!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(s.type)) { x.setRequestHeader("X-CSRFToken", csrf_token) } } }); $.ajax({ url: "{{url_for('board.upload_image')}}", cache: false, contentType: false, processData: false, data: data, type:"post", success: function(url) { var image = $("<img>").attr("src",url).css('max-width', "900px"); $("#summernote").summernote("insertNode",image[0]); }, error: function(data) { console.log(data); alert(data); } }); } </script> <script> function checkForm() { if ($.trim($("#title").val()) == "") { alert("제목을 입력하세요"); $("#title").focus(); return false; } if ($.trim($("#summernote").val()) == "") { alert("내용을 입력하세요"); $("#summernote").focus(); return false; } } </script> <div style="padding: 50px 50px 50px 50px;"> <form name="form" method="post" action="{{url_for('board.board_write')}}" onsubmit="return checkForm()"enctype="multipart/form-data"> <input type="hidden" name="csrf_token" value="{{csrf_token()}}"> <div class="form-group"> <label for="name">작성자</label> <input class="form-control" id="name" type="text" name="name" value="{{session['name']}}" readonly> </div> <div class="form-group"> <label for="title">제목</label> <input class="form-control" type="text" name="title" id="title" placeholder="제목을 입력하세요"/> </div> <div class="form-group"> <label for="contents">내용</label> <textarea rows="8" class="form-control" name="contents" id="summernote" placeholder="내용을 입력하세요"></textarea> </div> <div class="custom-file"> <input class="custom-file-input" id="customFile" type="file" name="attachfile"> <label class="custom-file-label" for="customFile">파일선택</label> </div> <div class="text-center"><input class="btn btn-primary" type="submit" value="작성하기"></div> </form> </div> {% endblock %} ---------------------------------------------------------------------------------------------------- - edit.html - {% extends "main.html" %} {% block contents %} <script> $(document).ready(function() { $("#summernote").summernote({ height: 300, minHeight: null, maxHeight: null, lang: "ko-KR", popover: { image:[], link:[], air:[] }, callbacks: { onImageUpload: function(image) { for(var i = 0; i < image.length; i++) { uploadImage(image[i]); } } } }); }); function uploadImage(image) { var data = new FormData(); data.append("image", image); var csrf_token = "{{csrf_token()}}"; $.ajaxSetup({ beforeSend: function(x, s) { if(!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(s.type)) { x.setRequestHeader("X-CSRFToken", csrf_token) } } }); $.ajax({ url: "{{url_for('board.upload_image')}}", cache: false, contentType: false, processData: false, data: data, type:"post", success: function(url) { var image = $("<img>").attr("src",url).css('max-width', "900px"); $("#summernote").summernote("insertNode",image[0]); }, error: function(data) { console.log(data); alert(data); } }); } </script> <div style="padding: 50px 50px 50px 50px;"> <form name="form" method="post" action="{{url_for('board.board_edit', idx=data._id)}}" enctype="multipart/form-data"> <input type="hidden" name="csrf_token" value="{{csrf_token()}}"> <div class="form-group"> <label for="name">작성자</label> <input class="form-control" type="text" name="name" value="{{session['name']}}" readonly /> </div> <div class="form-group"> <label for="title">제목</label> <input class="form-control" type="text" name="title" value={{data.title}}> </div> {% if data.attachfile %} <div class="form-check text-right"> <input type="checkbox" class="form-check-input" id="deleteoldfile" name="deleteoldfile"> <label class="form-check-label" for="deleteoldfile">첨부파일 삭제 ({{data.attachfile}})</label> </div> {% endif %} <div class="form-group"> <label for="contents">내용</label> <textarea rows="8" class="form-control" name="contents" id="summernote">{{data.contents}}</textarea> </div> <div class="custom-file"> <input class="custom-file-input" id="customFile" type="file" name="attachfile"> <label class="custom-file-label" for="customFile">파일선택</label> </div> <div class="text-center"><input class="btn btn-primary" type="submit" value="수정하기"/></div> </form> </div> {% endblock %} ---------------------------------------------------------------------------------------------------- - join.html - {% extends "main.html" %} {% block contents %} <script> function checkForm() { if ($.trim($("#name").val()) == "") { alert("이름을 입력하세요"); $("#name").focus(); return false; } if ($.trim($("#email").val()) == "") { alert("이메일을 입력하세요"); $("#email").focus(); return false; } if (!validateEmail($.trim($("#email").val()))) { alert("이메일 유효성이 올바르지 않습니다."); $("#email").focus(); return false; } if ($.trim($("#pass1").val()) == "") { alert("비밀번호를 입력하세요"); $("#pass1").focus(); return false; } if ($.trim($("#pass2").val()) == "") { alert("비밀번호를 입력하세요"); $("#pass2").focus(); return false; } if($.trim($("#pass1").val()) != $.trim($("#pass2").val())) { alert("비밀번호가 일치하지 않습니다."); $("#pass2").select().focus(); return false; } } </script> <div style="padding: 50px 50px 50px 50px;"> <form name="form" action="{{url_for('member.member_join')}}" method="POST" onsubmit="return checkForm()"> <input type="hidden" name="csrf_token" value="{{csrf_token()}}"> <div class="form-group"> <label for="name">이름</label> <input class="form-control" type="text" name="name" id="name"> </div> <div class="form-group"> <label for="name">이메일</label> <input class="form-control" type="text" name="email" id="email"> </div> <div class="form-group"> <label for="name">비밀번호</label> <input class="form-control" type="password" name="pass1" id="pass1"> </div> <div class="form-group"> <label for="name">비밀번호 확인</label> <input class="form-control" type="password" name="pass2" id="pass2"> </div> <div class="text-center"><input class="btn btn-primary" type="submit" value="가입하기" /></div> </form> </div> {% endblock %} ---------------------------------------------------------------------------------------------------- - login.html - {% extends "main.html" %} {% block contents %} <script> function checkForm() { if ($.trim($("#email").val()) == "") { alert("이메일을 입력하세요"); $("#email").focus(); return false; } if (!validateEmail($.trim($("#email").val()))) { alert("이메일 유효성이 올바르지 않습니다."); $("#email").focus(); return false; } if ($.trim($("#pass").val()) == "") { alert("비밀번호를 입력하세요"); $("#pass").focus(); return false; } return true; } </script> <div style="padding: 50px 50px 50px 50px;"> <form name="form" action="{{url_for('member.member_login')}}" method="POST" onsubmit="return checkForm()"> <input type="hidden" name="csrf_token" value="{{csrf_token()}}"> {% if next_url %} <input type="hidden" name="next_url" value="{{next_url}}" /> {% endif %} <div class="form-group"> <label for="email">이메일</label> <input class="form-control" type="text" name="email" id="email"> </div> <div class="form-group"> <label for="pass">비밀번호</label> <input class="form-control" type="password" name="pass" id="pass"> </div> <div class="text-center"><input type="submit" class="btn btn-primary" value="로그인"></div> </form> </div> {% endblock %} ---------------------------------------------------------------------------------------------------- - view.html - {% extends "main.html" %} {% block contents %} <div style="padding: 50px 50px 50px 50px;"> <table class="table table-bordered"> <tbody> <tr> <td colspan="2">{{result.title}}</td> </tr> <tr> <td>{{result.name}}</td> <td class="text-right">{{result.pubdate|formatdatetime}}</td> </tr> {% if result.attachfile %} <tr> <td>첨부파일</td> <td><a href="{{url_for('board.board_files', filename=result.attachfile)}}">{{result.attachfile}}</a></td> </tr> {% endif %} <tr> <td colspan="2"><div style="min-height: 200px;"></div>{% autoescape false %}{{result.contents}}{% endautoescape %}</td> </tr> </tbody> </table> <a class="btn btn-primary" href="{{url_for('board.lists', page=page, search=search, keyword=keyword)}}">리스트</a> {% if session["id"] == result.writer_id %} <a class="btn btn-danger float-right ml-2" href="{{url_for('board.board_delete', idx=result.id)}}">글삭제</a> <a class="btn btn-warning float-right" href="{{url_for('board.board_edit', idx=result.id)}}">글수정</a> {% endif %} </div> {% endblock %} ----------------------------------------------------------------------------------------------------
-
해결됨남박사의 파이썬으로 실전 웹사이트 만들기
오류 나는 부분이 있어서 다시 질문 드립니다! 도와주세요! (join 로그인시 백지, 몽고db 이메일주소 null 표시)
안녕하세요 웹개발 입문부분 강좌를 거의다 들었는데.. 오류가 나는 부분이 있어서.. 질문을 올립니다! 문제는..join 로그인 하면 몽고db에서 이메일 주소가 null로 표시가 되서.. 동작이 되지 않는점이 있습니다.. 비슷한 증상을 찾기 어려워서 질문 남깁니다! -화면- -join- -mongoDB- -list- -list 로그인 페이지- next_url << 이게 문제가 나왔었는데 다른 코드들 참고 했는데 해결은 됬습니다. -run.py- from flask import Flask from flask import request from flask import render_template from flask_pymongo import PyMongo from bson.objectid import ObjectId from flask import abort from flask import redirect from flask import url_for from flask import flash from flask import session from functools import wraps import math import time from datetime import datetime, timedelta app = Flask(__name__) app.config["MONGO_URI"] = "mongodb://localhost:27017/myweb" app.config["SECRET_KEY"] = "abcd" app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=30) mongo = PyMongo(app) def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if session.get("id") is None or session.get("id") == "": return redirect(url_for("member_login", next_url=request.url)) return f(*args, **kwargs) return decorated_function @app.template_filter('formatdatetime') def format_datetime(value): if value is None: return "" now_timestamp = time.time() offset = datetime.fromtimestamp( now_timestamp) - datetime.utcfromtimestamp(now_timestamp) value = datetime.fromtimestamp((int(value) / 1000)) + offset return value.strftime('%Y-%m-%d %H:%M:%S') @app.route("/list") def lists(): # 페이지 값 (값이 없는 경우 기본값는 1) page = request.args.get("page", 1, type=int) # 한페이지당 몇개의 게시물을 출력할지 limit = request.args.get("limit", 5, type=int) search = request.args.get("search", -1, type=int) keyword = request.args.get("keyword", type=str) # 최종적으로 완성된 쿼리를 만들 변수 query = {} # 검색어 상태를 추가할 리스트 변수 search_list = [] if search == 0: search_list.append({"title": {"$regex": keyword}}) elif search == 1: search_list.append({"contents": {"$regex": keyword}}) elif search == 2: search_list.append({"title": {"$regex": keyword}}) search_list.append({"contents": {"$regex": keyword}}) elif search == 3: search_list.append({"name": {"$regex": keyword}}) # 검색 대상이 한개라도 존재할 경우 query 변수에 $or 리스트를 쿼리 합니다. if len(search_list) > 0: query = {"$or": search_list} print(query) board = mongo.db.board datas = board.find({}).skip((page - 1) * limit).limit(limit) # 게시물의 총 갯수 tot_count = board.count_documents({}) # 마지막 페이지의 수를 구한다. last_page_num = math.ceil(tot_count / limit) # 페이지 블럭을 5개씩 표기 block_size = 5 # 현재 블럭의 위치 block_num = int((page - 1) / block_size) # 블럭의 시작 위치 block_start = int((block_size * block_num) + 1) # 블럭의 끝 위치 block_last = math.ceil(block_start + (block_size - 1)) return render_template("list.html", datas=list(datas), limit=limit, page=page, block_start=block_start, block_last=block_last, last_page_num=last_page_num, search=search, keyword=keyword) @ app.route("/view/<idx>") @login_required def board_view(idx): # idx = request.args.get("idx") if idx is not None: page = request.args.get("page") search = request.args.get("search") keyword = request.args.get("keyword") board = mongo.db.board # data = board.find_one({"_id": ObjectId(idx)}) data = board.find_one_and_update({"_id": ObjectId(idx)}, { "$inc": {"view": 1}}, return_document=True) if data is not None: result = { "id": data.get("_id"), "name": data.get("name"), "title": data.get("title"), "contents": data.get("contents"), "pubdate": data.get("pubdate"), "view": data.get("view"), "writer_id": data.get("writer_id", "") } return render_template("view.html", result=result, page=page, search=search, keyword=keyword) return abort(404) @ app.route("/write", methods=["GET", "POST"]) def board_write(): if session.get("id") is None: return redirect(url_for("member_login")) if request.method == "POST": name = request.form.get("name") title = request.form.get("title") contents = request.form.get("contents") print(name, title, contents) current_utc_time = round(datetime.utcnow().timestamp() * 1000) board = mongo.db.board post = { "name": name, "title": title, "contents": contents, "pubdate": current_utc_time, "writer_id": session.get("id"), "view": 0, } x = board.insert_one(post) print(x.inserted_id) return redirect(url_for("board_view", idx=x.inserted_id)) else: return render_template("write.html") @app.route("/join", methods=["GET", "POST"]) def member_join(): if request.method == "POST": name = request.form.get("name", type=str) email = request.form.get("email", type=str) pass1 = request.form.get("pass", type=str) pass2 = request.form.get("pass2", type=str) if name == "" or email == "" or pass1 == "" or pass2 == "": flash("입력되지 않은 값이 있습니다.") return render_template("join.html") if pass1 != pass2: flash("비밀번호가 일치하지 않습니다.") return render_template("join.html") members = mongo.db.members cnt = members.count_documents({"email": email}) if cnt > 0: flash("중복된 이메일 주소입니다.") return render_template("join.html") current_utc_time = round(datetime.utcnow().timestamp() * 1000) post = { "name": name, "email": email, "pass": pass1, "joindate": current_utc_time, "logintime": "", "logincount": 0, } members.insert_one(post) return "" else: return render_template("join.html") @app.route("/login", methods=["GET", "POST"]) def member_login(): if request.method == "POST": email = request.form.get("email") password = request.form.get("pass") next_url = request.form.get("next_url") members = mongo.db.members data = members.find_one({"email": email}) if data is None: flash("회원 정보가 없습니다.") return redirect(url_for("member_login")) else: if data.get("pass") == password: session["email"] = email session["name"] = data.get("name") session["id"] = str(data.get("_id")) session.permanent = True if next_url is not None: return redirect(next_url) else: return redirect(url_for("lists")) return redirect(url_for("lists")) else: flash("비밀번호가 일치하지 않습니다.") return redirect(url_for("member_login")) return "" else: next_url = request.args.get("next_url", type=str) if next_url is not None: return render_template("login.html", next_url=next_url) else: return render_template("login.html") @app.route("/edit/<idx>", methods=["GET", "POST"]) def board_edit(idx): if request.method == "GET": board = mongo.db.board data = board.find_one({"_id": ObjectId(idx)}) if data in None: flash("해당 게시물이 존재하지 않습니다.") return redirect(url_for("lists")) else: if session.get("id") == data.get("writer_id"): return render_template("edit.html", data=data) else: flash("글 수정 권한이 없습니다.") return redirect(url_for("lists")) else: title = request.form.get("title") contains = request.form.get("contents") board = mongo.db.board data = board.find_one({"_id": ObjectId(idx)}) if session.get("id") == data.get("writer_id"): board.update_one({"_id": ObjectId(idx)}, { "$set": { "title": title, "contents": contains, } }) flash("수정 되었습니다.") return redirect(url_for("board_view", idx=idx)) else: flash("글 수정 권한이 없습니다.") return redirect(url_for("lists")) @app.route("/delete/<idx>") def board_delete(idx): return "" if __name__ == "__main__": app.run(host="0.0.0.0", debug=True, port=9000) ------------------------------------------------------------------------------------------------ -join- {% with messages = get_flashed_messages() %} {% if messages %} <script> alert('{{messages[-1]}}'); </script> {% endif %} {% endwith %} <table> <form name="form" action="/join" method="POST"> <thead> <caption>회원가입</caption> </thead> <tbody> <tr> <td>이름</td> <td><input type="text" name="name"></td> </tr> <tr> <td>이메일</td> <td><input type="text" nmae="email"></td> </tr> <tr> <td>비밀번호</td> <td><input type="password" name="pass"></td> </tr> <tr> <td>비밀번호 확인</td> <td><input type="password" name="pass2"></td> </tr> <tr> <td colspan="2"><input type="submit" value="가입하기"></td> </tr> </tbody> </form> </table> ---------------------------------------------------------------------------------------------------------- -login- {% with messages = get_flashed_messages() %} {% if messages %} <script> alert("{{messages[-1]}}"); </script> {% endif %} {% endwith %} <table> <form name="form" action="/login" method="POST"> {% if next_url %} <input type="hidden" name="next_url" value="{{next_url}}" /> {% endif %} <thead> <caption> 회원 로그인 </caption> </thead> <tbody> <tr> <td>이메일</td> <td><input type="text" name="email" /></td> </tr> <tr> <td>비밀번호</td> <td><input type="password" name="pass" /></td> </tr> <tr> <td colspan="2"><input type="submit" value="로그인" /></td> </tr> </tbody> </form> </table> -------------------------------------------------------------------------------------------------------- - list - {% with messages = get_flashed_messages() %} {% if messages %} <script> alert('{{messages[-1]}}'); </script> {% endif %} {% endwith %} <script> function search() { var v_search = document.getElementById("search").value; var v_keyword = document.getElementById("keyword").value; if (v_search == "" || v_keyword == "") { return false; } else { self.location.href = "{{url_for('lists')}}?search=" + v_search + "&keyword=" + v_keyword; } } </script> {% if datas|length > 0 %} <table> <thead> <tr> <td>번호</td> <td>제목</td> <td>이름</td> <td>날짜</td> <td>조회수</td> </tr> </thead> <tbody> <!--반복되는 구간--> <tr> {% for data in datas %} <td>{{loop.index + ((page - 1) * limit)}}</td> <td> <a href="{{url_for('board_view', idx=data._id, page=page, search=search, keyword=keyword)}}">{{data.title}}</a> </td> <td>{{data.name}}</td> <td>{{data.pubdate|formatdatetime}}</td> <td>{{data.view}}</td> </tr> {% endfor %} <!--반복되는 구간 끝--> </tbody> </table> {% if block_start - 1 > 0 %} <a href="{{url_for('lists', page=block_start - 1, search=search, keyword=keyword)}}">[이전]</a> {% endif %} {% for i in range(block_start, block_last + 1) %} {% if i > last_page_num %} {{ i }} {% else %} {% if i == page %} <b>{{ i }}</b> {% else %} <a href="{{url_for('lists', page=i, search=search, keyword=keyword)}}">{{ i }}</a> {% endif %} {% endif %} {% endfor %} {% if block_last < last_page_num %} <a href="{{url_for('lists', page=block_last + 1, search=search, keyword=keyword)}}">[다음]</a> {% endif %} <select name="search" id="search"> <option value="" {% if search=='' or search==-1 %} selected {% endif %}>검색대상</option> <option value="0" {% if search==0 %} selected {% endif %}>제목</option> <option value="1" {% if search==1 %} selected {% endif %}>내용</option> <option value="2" {% if search==2 %} selected {% endif %}>제목+내용</option> <option value="3" {% if search==3 %} selected {% endif %}>작성자</option> </select> <input type="text" name="keyword" id="keyword" {% if keyword !="" %} value="{{keyword}}" {% endif %} /> <input type="button" value="검색" onclick="search()" /> {% else %} <h3>데이터가 없습니다.</h3> {% endif %} <a href="{{url_for('board_write')}}">글작성</a> ------------------------------------------------------------------------------------------------------- - edit - <html> <body> <table> <form name="form" method="post" action="/edit/{{data._id}}"> <tr> <td>작성자</td> <td><input type="text" name="name" value="{{session['name']}}" readonly /></td> </tr> <tr> <td>제목</td> <td><input type="text" name="title" /></td> </tr> <tr> <td>내용</td> <td><textarea type="text" name="contents">{{data.contents}}</textarea></td> </tr> <tr> <td colspan="2"><input type="submit" /></td> </tr> </form> </table> </body> </html>
-
해결됨남박사의 파이썬으로 실전 웹사이트 만들기
회원가입 만들기 오류 문의 (AttributeError: 'Cursor' object has no attribute 'count') 오류
안녕하세요 몇일째 오류랑 씨름하다가 여기까지 왔는데.. 도저희 해결이 안나서 문의 남깁니다. 구글링이나.. 다른분들 오류 해결하시는거 찾아보면서 해도.. 계속오류 나면서 하니까 진도가 너무 늦네요..ㅠ 파이썬으로 실전 웹사이트 만들기 - 회원가입 페이지 만들기 중입니다. -오류내용- -코드- (run.py) from flask import Flask from flask import request from flask import render_template from flask_pymongo import PyMongo from bson.objectid import ObjectId from flask import abort from flask import redirect from flask import url_for from flask import flash import math import time from datetime import datetime app = Flask(__name__) app.config["MONGO_URI"] = "mongodb://localhost:27017/myweb" app.config["SECRET_KEY"] = "abcd" mongo = PyMongo(app) @app.template_filter('formatdatetime') def format_datetime(value): if value is None: return "" now_timestamp = time.time() offset = datetime.fromtimestamp( now_timestamp) - datetime.utcfromtimestamp(now_timestamp) value = datetime.fromtimestamp((int(value) / 1000)) + offset return value.strftime('%Y-%m-%d %H:%M:%S') @app.route("/list") def lists(): # 페이지 값 (값이 없는 경우 기본값는 1) page = request.args.get("page", 1, type=int) # 한페이지당 몇개의 게시물을 출력할지 limit = request.args.get("limit", 5, type=int) search = request.args.get("search", -1, type=int) keyword = request.args.get("keyword", type=str) # 최종적으로 완성된 쿼리를 만들 변수 query = {} # 검색어 상태를 추가할 리스트 변수 search_list = [] if search == 0: search_list.append({"title": {"$regex": keyword}}) elif search == 1: search_list.append({"contents": {"$regex": keyword}}) elif search == 2: search_list.append({"title": {"$regex": keyword}}) search_list.append({"contents": {"$regex": keyword}}) elif search == 3: search_list.append({"name": {"$regex": keyword}}) # 검색 대상이 한개라도 존재할 경우 query 변수에 $or 리스트를 쿼리 합니다. if len(search_list) > 0: query = {"$or": search_list} print(query) board = mongo.db.board datas = board.find({}).skip((page - 1) * limit).limit(limit) # 게시물의 총 갯수 tot_count = board.count_documents({}) # 마지막 페이지의 수를 구한다. last_page_num = math.ceil(tot_count / limit) # 페이지 블럭을 5개씩 표기 block_size = 5 # 현재 블럭의 위치 block_num = int((page - 1) / block_size) # 블럭의 시작 위치 block_start = int((block_size * block_num) + 1) # 블럭의 끝 위치 block_last = math.ceil(block_start + (block_size - 1)) return render_template("list.html", datas=list(datas), limit=limit, page=page, block_start=block_start, block_last=block_last, last_page_num=last_page_num, search=search, keyword=keyword) @ app.route("/view/<idx>") def board_view(idx): # idx = request.args.get("idx") if idx is not None: page = request.args.get("page") search = request.args.get("search") keyword = request.args.get("keyword") board = mongo.db.board data = board.find_one({"_id": ObjectId(idx)}) if data is not None: result = { "id": data.get("_id"), "name": data.get("name"), "title": data.get("title"), "contents": data.get("contents"), "pubdate": data.get("pubdate"), "view": data.get("view") } return render_template("view.html", result=result, page=page, search=search, keyword=keyword) return abort(404) @ app.route("/write", methods=["GET", "POST"]) def board_write(): if request.method == "POST": name = request.form.get("name") title = request.form.get("title") contents = request.form.get("contents") print(name, title, contents) current_utc_time = round(datetime.utcnow().timestamp() * 1000) board = mongo.db.board post = { "name": name, "title": title, "contents": contents, "pubdate": current_utc_time, "view": 0, } x = board.insert_one(post) print(x.inserted_id) return redirect(url_for("board_view", idx=x.inserted_id)) else: return render_template("write.html") @app.route("/join", methods=["GET", "POST"]) def member_join(): if request.method == "POST": name = request.form.get("name", type=str) email = request.form.get("email", type=str) pass1 = request.form.get("pass", type=str) pass2 = request.form.get("pass2", type=str) if name == "" or email == "" or pass1 == "" or pass2 == "": flash("입력되지 않은 값이 있습니다.") return render_template("join.html") if pass1 != pass2: flash("비밀번호가 일치하지 않습니다.") return render_template("join.html") members = mongo.db.members cnt = members.find({"email": email}).count() if cnt > 0: flash("중복된 이메일 주소입니다.") return render_template("join.html") current_utc_time = round(datetime.utcnow().timestamp(*1000)) post = { "name": name, "email": email, "pass": pass1, "joindate": current_utc_time, "logintime": " ", "logincount": 0 } members.insert_one(post) return "" else: return render_template("join.html") if __name__ == "__main__": app.run(host="0.0.0.0", debug=True, port=9000) (join.html) {% with messages = get_flashed_messages() %} {% if messages %} <script> alert('{{messages[-1]}}'); </script> {% endif %} {% endwith %} <table> <form name="form" action="/join" method="POST"> <thead> <caption>회원가입</caption> </thead> <tbody> <tr> <td>이름</td> <td><input type="text" name="name"></td> </tr> <tr> <td>이메일</td> <td><input type="text" nmae="email"></td> </tr> <tr> <td>비밀번호</td> <td><input type="password" name="pass"></td> </tr> <tr> <td>비밀번호 확인</td> <td><input type="password" name="pass2"></td> </tr> <tr> <td colspan="2"><input type="submit" value="가입하기"></td> </tr> </tbody> </form> </table>