AIcodingmaster

CVpage end

Showing 65 changed files with 1139 additions and 0 deletions

701 KB

2.45 MB

No preview for this file type
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
import config
db = SQLAlchemy()
migrate = Migrate()
def create_app():
app = Flask(__name__)
from .views import workdetail_view,main_view,about_view
#db 설정 등록
app.config.from_object(config)
#ORM(db 코드 추상화) 등록
db.init_app(app)
migrate.init_app(app, db)
from . import models
#블루 프린트 등록
app.register_blueprint(workdetail_view.bp)
app.register_blueprint(main_view.bp)
app.register_blueprint(about_view.bp)
from .filter import format_datetime
app.jinja_env.filters['datetime'] = format_datetime
return app
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
def format_datetime(value, fmt='%Y/%m/%d %H:%M'):
return value.strftime(fmt)
\ No newline at end of file
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField
from wtforms.validators import DataRequired
class ProjectForm(FlaskForm):
projectName = StringField('프로젝트이름', validators=[DataRequired()])
projectType = StringField('타입', validators=[DataRequired()])
pictureName = StringField('video, pic이름', validators=[DataRequired()])
content = TextAreaField('내용', validators=[DataRequired()])
gitAddress = StringField('git주소', validators=[DataRequired()])
from CVpage import db
class Project(db.Model):
id = db.Column(db.Integer, primary_key=True)
projectName = db.Column(db.String(200), nullable=False)
projectType = db.Column(db.String(200), nullable=False)
pictureName = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text(), nullable=False)
gitAddress = db.Column(db.String(200), nullable=False)
create_date = db.Column(db.DateTime(), nullable=False)
\ No newline at end of file
.jumbotron {
padding-top: 3rem;
padding-bottom: 3rem;
margin-bottom: 0;
background-color: #fff;
}
@media (min-width: 768px) {
.jumbotron {
padding-top: 6rem;
padding-bottom: 6rem;
}
}
.nooutline{
border:0;
}
.jumbotron p:last-child {
margin-bottom: 0;
}
.jumbotron h1 {
font-weight: 300;
}
.jumbotron .container {
max-width: 40rem;
}
footer {
padding-top: 3rem;
padding-bottom: 3rem;
}
footer p {
margin-bottom: .25rem;
}
header { position: fixed; z-index: 10; top: 0; left: 0; width: 100%; height: 0px; transition: top 0.2s ease-in-out; }
.nav-up { top: -70px;}
.bg-dark{
background-color: burlywood;
}
.textblack{
color:black;
text-decoration:none;
}
.textblack:hover{
color:#808080;
text-decoration:none;
}
.sub{
font-size:.5em;
color:#000000;
}
.box{
position: static;
height: 220px;
}
.front{
position: absolute;
top:0;
left:0;
display: none;
}
.back{
position: absolute;
top:0;
left:0;
display: none;
}
.box img:nth-child(2){
opacity: 0;
transition:all 0.3s ease in ease-in-out;
}
.box:hover img:nth-child(2){
opacity: 1;
transition:all 0.3s ease in ease-in-out;
}
.form{
}
This diff could not be displayed because it is too large.
.bgImage{
-webkit-filter: brightness(70%);filter: brightness(70%);
position: absolute;
top:0;
left:0;
z-index: -1;
height: 100%;
width:100%;
animation: fadeIn 0.5s linear;
}
.bgImg{
-webkit-filter: brightness(70%);filter: brightness(70%);
position: absolute;
top:0;
left:0;
z-index: -1;
height: 100%;
width:100%;
}
@keyframes fadeIn{
from{
opacity:0;
}
to{
opacity:1;
}
}
html, body {
margin: 0;
height: 100%;
overflow: hidden;
}
.detail-container{
display:grid;
grid-template-columns:10% 1fr;
grid-template-rows:20% 60% 1fr;
height:100%;
grid-gap:10px;
grid-template-areas:
"header header"
"side main"
"side footer"
}
header{
grid-area:header;
}
.main{
grid-area:main;
padding-left:3rem;
}
.side{
grid-area: side;
display: flex;
justify-content: center;
padding-top:100%;
}
.footer{
grid-area:footer;
}
.textwhite{
color:rgb(255, 255, 255);
text-decoration:none;
border:2px solid white;
padding:1px 5px 3px 5px;
transition:all 0.2s;
margin-right:0.5rem;
}
.textwhite:hover{
color:#cecece;
text-decoration:none;
border:4px solid #cecece;
padding:1px 5px 3px 5px;
margin-right:0.5rem;
}
.textwhitebx{
color:rgb(255, 255, 255);
text-decoration:none;
transition:all 0.2s;
}
.textwhitebx:hover{
color:#cecece;
text-decoration:none;
padding:1px 5px 3px 5px;
margin-right:0.5rem;
}
.left-footer{
width:35%;
float:left;
box-sizing:border-box;
margin-right:15%;
text-align: right;
}
.right-footer{
width:35%;
float:right;
position: static;
box-sizing:border-box;
text-align: left;
margin-right:15%;
}
.bottomBorderL a,.bottomBorderR a{
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease;
}
.bottomBorderL a:hover{
border-bottom: solid 2px white;
border-left:solid 2px white;
width: 100%;
}
.bottomBorderR a:hover{
border-bottom: solid 2px white;
border-right:solid 2px white;
width: 100%;
}
//nav바 추적
var lastScrollTop = 0;
var delta = 5; // 동작의 구현이 시작되는 위치
var navbarHeight = 70; // 영향을 받을 요소를 선택
var didScroll; // 스크롤시에 사용자가 스크롤했다는 것을 알림
$(window).scroll(function(event){ didScroll = true; }); // hasScrolled()를 실행하고 didScroll 상태를 재설정
setInterval(function() { if (didScroll) { hasScrolled(); didScroll = false; } }, 250);
function hasScrolled() {
var st = $(this).scrollTop();
if (Math.abs(lastScrollTop-st) <= delta) return;
// If current position > last position AND scrolled past navbar...
if (st > lastScrollTop && st > navbarHeight){
// Scroll Down
if($(`#navbarHeader`).hasClass(`show`))
{
n=$(`#navbarHeader`);
n.removeClass(`show`);
}
$(`header`).removeClass(`nav-down`).addClass(`nav-up`);
}
else { // Scroll Up
// If did not scroll past the document (possible on mac)...
if($(`#navbarHeader`).hasClass(`show`))
{
n=$(`#navbarHeader`)
n.removeClass(`show`)
return
}
if(st + $(window).height() < $(document).height())
{
$(`header`).removeClass(`nav-up`).addClass(`nav-down`);
}
}
lastScrollTop = st;
}
const navbutton=document.querySelector('.navButton')
const maindiv=document.querySelector('.belowHeader')
navbutton.addEventListener('onclick',pushMain)
function pushMain(){
maindiv.style.transform="translate3d(0px, px, 0px)";
}
function zoomIn(event) {
event.target.style.transform = "scale(1.1)";
event.target.style.zIndex = 1;
event.target.style.transition = "all 0.5s";
div=event.target.parentNode.parentNode;
content=div.querySelector(`.card-body`);
content.style.transform = "translate3d(0px, -150px, 0px)";
content.style.transition = "all 0.5s";
}
function zoomOut(event) {
event.target.style.transform = "scale(1)";
event.target.style.zIndex = 1;
event.target.style.transition = "all 0.5s";
div=event.target.parentNode.parentNode;
content=div.querySelector(`.card-body`);
content.style.transform = "translate3d(0px, 0px, 0px)";
content.style.transition = "all 0.5s";
}
This diff is collapsed. Click to expand it.
$(".box").each(function(){
var box=$(this)
var boxImgs=$(this).children('img')
var myAnimation = new hoverEffect({
parent: box[0],
intensity: 1.0,
speedIn:1.3,
speedOut:1.3,
angle:Math.PI/2,
image1: boxImgs[0].getAttribute('src'),
image2: boxImgs[1].getAttribute('src'),
displacementImage: box[0].getAttribute('displacementImage')
});
})
\ No newline at end of file
const strong=document.querySelector('strong.textblack')
const card=document.querySelector('svg.bi-card-list')
const playBtn=document.querySelector('svg.bi-file-play')
strong.classList.remove('textblack')
strong.classList.add('text-white')
card.setAttribute('fill','white')
function playBtnOverHandler(){
playBtn.setAttribute('fill','black');
playBtn.style.transform= "scale(1.3)";
playBtn.style.transition= 'all 0.3s';
}
function playBtnOutHandler(){
playBtn.setAttribute('fill','white');
playBtn.style.transform= "scale(1.0)";
playBtn.style.transition= 'all 0.3s';
}
playBtn.addEventListener("mouseover",playBtnOverHandler)
playBtn.addEventListener("mouseout",playBtnOutHandler)
//playBtn.addEventListener("onclick",playBtnHandler)어디서 컨트롤할지 안정함
\ No newline at end of file
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("three"),require("gsap/TweenMax")):"function"==typeof define&&define.amd?define(["three","gsap/TweenMax"],t):e.hoverEffect=t(e.THREE,e.TweenMax)}(this,function(e,t){return t=t&&t.hasOwnProperty("default")?t.default:t,function(n){function i(){for(var e=arguments,t=0;t<arguments.length;t++)if(void 0!==e[t])return e[t]}console.log("%c Hover effect by Robin Delaporte: https://github.com/robin-dela/hover-effect ","color: #bada55; font-size: 0.8rem");var r=n.parent,o=n.displacementImage,a=n.image1,s=n.image2,f=i(n.imagesRatio,1),d=i(n.intensity1,n.intensity,1),l=i(n.intensity2,n.intensity,1),u=i(n.angle,Math.PI/4),v=i(n.angle1,u),m=i(n.angle2,3*-u),c=i(n.speedIn,n.speed,1.6),p=i(n.speedOut,n.speed,1.2),g=i(n.hover,!0),h=i(n.easing,Expo.easeOut),y=i(n.video,!1);if(r)if(a&&s&&o){var x=new e.Scene,F=new e.OrthographicCamera(r.offsetWidth/-2,r.offsetWidth/2,r.offsetHeight/2,r.offsetHeight/-2,1,1e3);F.position.z=1;var w=new e.WebGLRenderer({antialias:!1,alpha:!0});w.setPixelRatio(2),w.setClearColor(16777215,0),w.setSize(r.offsetWidth,r.offsetHeight),r.appendChild(w.domElement);var L=function(){w.render(x,F)},H=new e.TextureLoader;H.crossOrigin="";var E,W,V=H.load(o,L);if(V.magFilter=V.minFilter=e.LinearFilter,y){var M=function(){requestAnimationFrame(M),w.render(x,F)};M(),(y=document.createElement("video")).autoplay=!0,y.loop=!0,y.src=a,y.load();var P=document.createElement("video");P.autoplay=!0,P.loop=!0,P.src=s,P.load();var R=new e.VideoTexture(y),T=new e.VideoTexture(P);R.magFilter=T.magFilter=e.LinearFilter,R.minFilter=T.minFilter=e.LinearFilter,P.addEventListener("loadeddata",function(){P.play(),(T=new e.VideoTexture(P)).magFilter=e.LinearFilter,T.minFilter=e.LinearFilter,C.uniforms.texture2.value=T},!1),y.addEventListener("loadeddata",function(){y.play(),(R=new e.VideoTexture(y)).magFilter=e.LinearFilter,R.minFilter=e.LinearFilter,C.uniforms.texture1.value=R},!1)}else R=H.load(a,L),T=H.load(s,L),R.magFilter=T.magFilter=e.LinearFilter,R.minFilter=T.minFilter=e.LinearFilter;var U=f;r.offsetHeight/r.offsetWidth<U?(E=1,W=r.offsetHeight/r.offsetWidth/U):(E=r.offsetWidth/r.offsetHeight*U,W=1);var C=new e.ShaderMaterial({uniforms:{intensity1:{type:"f",value:d},intensity2:{type:"f",value:l},dispFactor:{type:"f",value:0},angle1:{type:"f",value:v},angle2:{type:"f",value:m},texture1:{type:"t",value:R},texture2:{type:"t",value:T},disp:{type:"t",value:V},res:{type:"vec4",value:new e.Vector4(r.offsetWidth,r.offsetHeight,E,W)},dpr:{type:"f",value:window.devicePixelRatio}},vertexShader:"\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}\n",fragmentShader:"\nvarying vec2 vUv;\n\nuniform float dispFactor;\nuniform float dpr;\nuniform sampler2D disp;\n\nuniform sampler2D texture1;\nuniform sampler2D texture2;\nuniform float angle1;\nuniform float angle2;\nuniform float intensity1;\nuniform float intensity2;\nuniform vec4 res;\nuniform vec2 parent;\n\nmat2 getRotM(float angle) {\n float s = sin(angle);\n float c = cos(angle);\n return mat2(c, -s, s, c);\n}\n\nvoid main() {\n vec4 disp = texture2D(disp, vUv);\n vec2 dispVec = vec2(disp.r, disp.g);\n\n vec2 uv = 0.5 * gl_FragCoord.xy / (res.xy) ;\n vec2 myUV = (uv - vec2(0.5))*res.zw + vec2(0.5);\n\n\n vec2 distortedPosition1 = myUV + getRotM(angle1) * dispVec * intensity1 * dispFactor;\n vec2 distortedPosition2 = myUV + getRotM(angle2) * dispVec * intensity2 * (1.0 - dispFactor);\n vec4 _texture1 = texture2D(texture1, distortedPosition1);\n vec4 _texture2 = texture2D(texture2, distortedPosition2);\n gl_FragColor = mix(_texture1, _texture2, dispFactor);\n}\n",transparent:!0,opacity:1}),b=new e.PlaneBufferGeometry(r.offsetWidth,r.offsetHeight,1),D=new e.Mesh(b,C);x.add(D),g&&(r.addEventListener("mouseenter",_),r.addEventListener("touchstart",_),r.addEventListener("mouseleave",z),r.addEventListener("touchend",z)),window.addEventListener("resize",function(t){r.offsetHeight/r.offsetWidth<U?(E=1,W=r.offsetHeight/r.offsetWidth/U):(E=r.offsetWidth/r.offsetHeight*U,W=1),D.material.uniforms.res.value=new e.Vector4(r.offsetWidth,r.offsetHeight,E,W),w.setSize(r.offsetWidth,r.offsetHeight),L()}),this.next=_,this.previous=z}else console.warn("One or more images are missing");else console.warn("Parent missing");function _(){t.to(C.uniforms.dispFactor,c,{value:1,ease:h,onUpdate:L,onComplete:L})}function z(){t.to(C.uniforms.dispFactor,p,{value:0,ease:h,onUpdate:L,onComplete:L})}}});
//# sourceMappingURL=hover-effect.umd.js.map
This diff is collapsed. Click to expand it.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>CVpage</title>
<!-- Bootstrap core CSS -->
<link href="{{url_for('static',filename='css/bootstrap.min.css')}}" rel="stylesheet">
<link href="{{url_for('static',filename='css/about.css')}}" rel="stylesheet">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
<!-- Custom styles for this template -->
</head>
<body>
{%include 'navbar.html'%}
<div class="sub" style="z-index: 9999999;">
CopyWrite KHJ<br>
<br>
<br>
<br>
<br>
<br>
<br>
</div>
<div class=belowHeader>
<main role="main">
<div class="album py-5">
<div class="container">
<div class="row">
<!-- project card start-->
{%if g.user%}
<div class="col-md-4">
<a class="card mb-4 nooutline" href="{{url_for('about.add')}}">
<img src="{{url_for('static',filename='img/plus.png')}}" onmouseenter=zoomIn(event) onmouseleave=zoomOut(event)>
</img>
</a>
<div class="card-body">
<p class="card-text">Project Name</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">btn</button>
</div>
<small class="text-muted">date</small>
</div>
</div>
</div>
{%endif%}
{%if w_list%}
{%for work in w_list%}
<div class="col-md-4">
<a class="card mb-4 nooutline" href="{{url_for('detail.detail',project_id=work.id)}}">
<div class='box' onmouseenter=zoomIn(event) onmouseleave=zoomOut(event) displacementImage="{{url_for('static',filename='img/distortion.jpg')}}">
<img class=front src="{{url_for('static',filename='img/'+work.pictureName+'1.jpg')}}" width=100% height=225 alt="" onmouseenter=zoomIn(event) onmouseleave=zoomOut(event)>
<img class=back src="{{url_for('static',filename='img/'+work.pictureName+'2.jpg')}}" width=100% height=225 alt="" onmouseenter=zoomIn(event) onmouseleave=zoomOut(event)>
</div>
</a>
<div class="card-body">
<p class="card-text">{{work.projectName}}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" onclick="location.href='{{work.gitAddress}}'" class="btn btn-sm btn-outline-secondary">git</button>
<a href="{{url_for('detail.video',project_id=work.id)}}">
<button type="button" class="btn btn-sm btn-outline-secondary btnvideo">video</button>
</a>
{%if g.user%}
<a href="{{url_for('about.delete',project_id=work.id)}}">
<button type="button" class="btn btn-sm btn-outline-secondary btnvideo">delete</button>
</a>
<a href="{{url_for('about.modify',project_id=work.id)}}">
<button type="button" class="btn btn-sm btn-outline-secondary btnvideo">modify</button>
</a>
{%endif%}
</div>
<small class="text-muted">{{work.create_date|datetime}}</small>
</div>
</div>
</div>
{%endfor%}
{%endif%}
<!-- project card end-->
</div>
</div>
</div>
</main>
<br><br><br><br><br><br><br>
<footer class="text-muted" style="margin-bottom:30rem">
<div class="container">
<p class="float-right">
<a href="#" class="textblack">Back to top</a>
</p>
<p>
<ul>
<h2 id=aboutme>
About Me
</h2>
<br>
<li>
HyungJin Kweon
</li>
<li>
KyungHee Univ.
</li>
<li>
email : intimate0305@khu.ac.kr
</li>
<li>
I'm junior programmer interested in Computer Vision.
</li>
</ul>
</p>
</footer>
</div>
<script src="{{url_for('static',filename='js/jquery-3.5.1.min.js')}}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r120/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script>
<script src="{{url_for('static',filename='js/hover-effect.umd.js')}}"></script>
<script src="{{url_for('static',filename='js/about.js')}}"></script>
<script src="{{url_for('static',filename='js/custom.js')}}"></script>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="{{url_for('static',filename='js/bootstrap.bundle.min.js')}}"></script>
</body>
</html>
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>CVpage</title>
<link href="{{url_for('static',filename='css/bootstrap.min.css')}}" rel="stylesheet">
<link href="{{url_for('static',filename='css/about.css')}}" rel="stylesheet">
<link href="{{url_for('static',filename='css/detail.css')}}" rel="stylesheet">
</head>
<body>
{% block content %}
{% endblock %}
<script src="{{url_for('static',filename='js/jquery-3.5.1.min.js')}}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r120/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script>
<script src="{{url_for('static',filename='js/hover-effect.umd.js')}}"></script>
<script src="{{url_for('static',filename='js/about.js')}}"></script>
<script src="{{url_for('static',filename='js/custom.js')}}"></script>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="{{url_for('static',filename='js/bootstrap.bundle.min.js')}}"></script>
<script src="{{url_for('static',filename='js/detail.js')}}"></script>
</body>
</html>
\ No newline at end of file
<header class="nav-container">
<div class="collapse bg-white" id="navbarHeader" >
<div class="container">
<div class="row">
<div class="col-sm-8 col-md-7 py-4"><p>
<h4 class="text-black">CVpage</p>
<p class="text-muted">
This site is designed for personally read papers and project management.</p></h4>
</div>
<div class="col-sm-4 offset-md-1 py-4">
<h4 class="text-black">Menu</h4>
<ul class="list-unstyled">
<li><a href="{{url_for('about.about')}}#aboutme" class="textblack">About me</a></li>
<li><a href="https://wikidocs.net/book/5049" class="textblack">Paper Read</a></li>
<li><a href="https://github.com/AIcodingmaster" class="textblack">Github</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="wrap_gnb navbar navbar-black bg-black shadow-sm">
<div class="container d-flex justify-content-between">
<a href="{{url_for('about.about')}}" class="navbar-brand d-flex align-items-center">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-card-list" fill="black" xmlns="http://www.w3.org/2000/svg" style="margin:10px;">
<path fill-rule="evenodd" d="M14.5 3h-13a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/>
<path fill-rule="evenodd" d="M5 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 5 8zm0-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5z"/>
<circle cx="3.5" cy="5.5" r=".5"/>
<circle cx="3.5" cy="8" r=".5"/>
<circle cx="3.5" cy="10.5" r=".5"/>
</svg>
<strong class=textblack>Project</strong>
</a>
{%if g.user%}
<a href="{{url_for('main.logout')}}">
<strong class="textblack" style="opacity: 0.5;margin-left:-30rem">({{g.user}})</strong>
</a>
{%endif%}
<button class=navButton style="background-color:white;border:0;outline:0" type="button" data-toggle="collapse" data-target="#navbarHeader" aria-controls="navbarHeader" aria-expanded="false" aria-label="Toggle navigation">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-caret-down-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
</svg>
</button>
</div>
</div>
</header>
\ No newline at end of file
{%extends 'base.html'%}
{%block content%}
<form class="form" method="POST" enctype="multipart/form-data">
<div>
{{ form.csrf_token }}
{{form.projectName.label}}
{{form.projectName(class="form-control")}}
{{form.projectType.label}}
{{form.projectType(class="form-control")}}
{{form.pictureName.label}}
{{form.pictureName(class="form-control")}}
{{form.content.label}}
{{form.content(class="form-control")}}
{{form.gitAddress.label}}
{{form.gitAddress(class="form-control")}}
비디오: <input type="file" name="video" id="">
이미지1:<input type="file" name="pic1" id="">
이미지2:<input type="file" name="pic2" id="">
<br>
<button type="submit" class="btn btn-primary">submit</button>
</div>
</form>
{%endblock%}
\ No newline at end of file
{%extends 'base.html'%}
{%block content%}
<video src="{{url_for('static',filename='video/{}.mp4'.format(p.pictureName))}}" width="100%" height="100%" controls></video>
{%endblock%}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>CVpage</title>
<link href="{{url_for('static',filename='css/detail.css')}}" rel="stylesheet" type="text/css" />
<link href="{{url_for('static',filename='css/bootstrap.min.css')}}" rel="stylesheet">
<link href="{{url_for('static',filename='css/about.css')}}" rel="stylesheet">
</head>
<body>
{%include 'navbar.html'%}
<div class="detail-container">
<a class="side" href="{{url_for('detail.video',project_id=p.id)}}">
<svg width="5em" height="5em" viewBox="0 0 16 16" class="bi bi-file-play" fill="white" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4 0h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H4z"/>
<path d="M6 10.117V5.883a.5.5 0 0 1 .757-.429l3.528 2.117a.5.5 0 0 1 0 .858l-3.528 2.117a.5.5 0 0 1-.757-.43z"/>
</svg>
</a>
<div class="main">
<div class="id_workType">
<h5 class="text-white">
# {{p.id}} / {{p.projectType}}
</h5>
</div>
<br>
<h1 class="text-white">
<div class="projectName">
<strong>
{{p.projectName}}
</strong>
</div>
</h1>
<br>
<h6 class="text-white">
<div class="projectContent">
{{p.content}}
</div>
</h6>
<br>
<span>
<strong class="text-white" style="margin-right: 0.5em">
More :
</strong>
</span>
<span class="gitAddress">
<strong>
<a class="textwhite" href="{{p.gitAddress}}">git</a>
</strong>
</span>
<span class="video">
<strong>
<a class="textwhite" href="{{url_for('detail.video',project_id=p.id)}}">video</a>
</strong>
</span>
</div>
<div class="footer" >
<h5>
<span class="left-footer">
{%if bp%}
<div class="text-white">
<strong>
#{{bp.id}}
</strong>
</div>
<div class=bottomBorderL>
<a class="textwhitebx" href="{{url_for('detail.detail',project_id=bp.id,last_picture=p.pictureName)}}">
<strong>
{{bp.projectName}}
</strong>
</a>
</div>
{%endif%}
</span>
<span class="right-footer">
{%if np%}
<div class="text-white">
<strong>
#{{np.id}}
</strong>
</div>
<div>
<strong class=bottomBorderR>
<a class="textwhitebx" href="{{url_for('detail.detail',project_id=np.id,last_picture=p.pictureName)}}">
{{np.projectName}}
</a>
</strong>
</div>
{%endif%}
</span>
</h5>
</div>
</div>
<!--
{%if last_picture%}
<div class='box' displacementImage="{{url_for('static',filename='img/distortion.jpg')}}">
<img class="front bgImg" src="{{url_for('static',filename='img/'+last_picture+'2.jpg')}}" alt="" >
<img class="back bgImg" src="{{url_for('static',filename='img/'+p.pictureName+'2.jpg')}}" alt="">
</div>
{%else%}
{%endif%}
-->
<img class="bgImage" src="{{url_for('static',filename='img/{}1.jpg'.format(p.pictureName))}}" alt="">
<script src="{{url_for('static',filename='js/detail.js')}}"></script>
<script src="{{url_for('static',filename='js/jquery-3.5.1.min.js')}}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r120/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script>
<script src="{{url_for('static',filename='js/hover-effect.umd.js')}}"></script>
<script src="{{url_for('static',filename='js/about.js')}}"></script>
<script src="{{url_for('static',filename='js/custom.js')}}"></script>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="{{url_for('static',filename='js/bootstrap.bundle.min.js')}}"></script>
</body>
</html>
\ No newline at end of file
{%extends 'base.html'%}
{%block content%}
{% include "form_errors.html" %}
{%endblock%}
\ No newline at end of file
from flask import Blueprint,render_template,url_for,session,g,redirect,request
from werkzeug.utils import secure_filename
from CVpage.models import Project,db
from CVpage.form import ProjectForm
from datetime import datetime
import os
bp = Blueprint('about', __name__, url_prefix='/about/')
@bp.route('/')
def about():
work_list=Project.query.order_by(Project.create_date.desc())
return render_template('about/about.html',w_list=work_list)
@bp.route('/add/',methods=('GET','POST'))
def add():
form=ProjectForm()
if request.method =="POST" and form.validate_on_submit():
p=Project(projectName=form.projectName.data,
projectType=form.projectType.data,
pictureName=form.pictureName.data,
content=form.content.data,
gitAddress=form.content.data,
create_date=datetime.now())
pic1=request.files['pic1']
pic2=request.files['pic2']
video=request.files['video']
pictureName=form.pictureName.data
pic1.save('/home/ubuntu/projects/CVpage/CVpage/static/img/'+secure_filename(pictureName+'1.jpg'))
pic2.save('/home/ubuntu/projects/CVpage/CVpage/static/img/'+secure_filename(pictureName+'2.jpg'))
video.save('/home/ubuntu/projects/CVpage/CVpage/static/video/'+secure_filename(pictureName+'.mp4'))
db.session.add(p)
db.session.commit()
return redirect(url_for('about.about'))
else:
return render_template('project_form.html', form=form)
@bp.route('/delete/<int:project_id>')
def delete(project_id):
p=Project.query.get(project_id)
if g.user is "admin":
import os
os.remove(f'./CVpage/static/img/{p.pictureName}1.jpg')
os.remove(f'./CVpage/static/img/{p.pictureName}2.jpg')
os.remove(f'./CVpage/static/video/{p.pictureName}.mp4')
db.session.delete(p)
db.session.commit()
return redirect(url_for('about.about'))
@bp.route('/modify/<int:project_id>', methods=('GET','POST'))
def modify(project_id):
p=Project.query.get(project_id)
if request.method =="POST":#작성을 하고 form태그의 post요청으로 들어간 것
form = ProjectForm()
bpicN=p.pictureName
if g.user is "admin" and form.validate_on_submit():
pic1=request.files['pic1']
pic2=request.files['pic2']
video=request.files['video']
form.populate_obj(p)
if pic1 is None or pic2 is None or video is None:
return render_template('project_form.html', form=form)
os.remove('/home/ubuntu/projects/CVpage/CVpage/static/img/'+secure_filename(bpicN+'1.jpg'))
os.remove('/home/ubuntu/projects/CVpage/CVpage/static/img/'+secure_filename(bpicN+'2.jpg'))
os.remove('/home/ubuntu/projects/CVpage/CVpage/static/video/'+secure_filename(bpicN+'.mp4'))
pic1.save('/home/ubuntu/projects/CVpage/CVpage/static/img/'+secure_filename(form.pictureName.data+'1.jpg'))
pic2.save('/home/ubuntu/projects/CVpage/CVpage/static/img/'+secure_filename(form.pictureName.data+'2.jpg'))
video.save('/home/ubuntu/projects/CVpage/CVpage/static/video/'+secure_filename(form.pictureName.data+'.mp4'))
db.session.commit()
return redirect(url_for('about.about'))
else:#다른 html에서 접근한 것(GET요청일 경우)
form = ProjectForm(obj=p)#기존 데이터를 대입시켜서 리턴
return render_template('project_form.html', form=form)
@bp.route('/admin/<int:pw>')
def admin(pw):
if pw==123:
session.clear()
session['pw']=123
return redirect(url_for('about.about'))
else:
return redirect(url_for('about.about'))
from flask import Blueprint,redirect,url_for,session,g
bp = Blueprint('main', __name__, url_prefix='/')
@bp.before_app_request
def load_logged_in_user():
pw= session.get('pw')
if pw is 123:
g.user = "admin"
@bp.route('/logout/')
def logout():
session.clear()
return redirect(url_for('about.about'))
@bp.route('/admin/<int:pw>')
def admin(pw):
if pw==123:
session.clear()
session['pw']=123
return redirect(url_for('about.about'))
else:
return redirect(url_for('about.about'))
@bp.route('/')
def index():
return redirect(url_for('about.about'))
\ No newline at end of file
from flask import Blueprint,render_template,request
from CVpage.models import Project
bp = Blueprint('detail', __name__, url_prefix='/detail/')
@bp.route('/<int:project_id>')
def detail(project_id):
p=Project.query.get(project_id)
bp=Project.query.get(project_id+1)
np=Project.query.get(project_id-1)
last_picture=request.args.get('last_picture',None)#key값 p를 찾아보고 없으면 None반환
if last_picture==None:
return render_template('work/work_detail.html',p=p,bp=bp,np=np)
else:
return render_template('work/work_detail.html',p=p,bp=bp,np=np,last_picture=last_picture)
@bp.route('/video/<int:project_id>')
def video(project_id):
p=Project.query.get(project_id)
return render_template('/video.html',p=p)
\ No newline at end of file
# CVpage with Flask, Vanilla JS, JQuery
![화면1](1.png)
![화면2](2.png)
requirements : Flask, Flask-Migrate
CVpage에서 flask run 실행
http://localhost:5000/에서 확인 가능
url : http://15.165.162.81:5000 or http://khj.kr
No preview for this file type
No preview for this file type
import os
BASE_DIR = os.path.dirname(__file__)
SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(BASE_DIR, 'CVpage.db'))
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "dev"
\ No newline at end of file
Generic single-database configuration.
\ No newline at end of file
No preview for this file type
No preview for this file type
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
"""empty message
Revision ID: 415b13ebc457
Revises:
Create Date: 2020-08-26 02:18:08.862101
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '415b13ebc457'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('article',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('subject', sa.String(length=200), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('create_date', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('project',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('projectName', sa.String(length=200), nullable=False),
sa.Column('projectType', sa.String(length=200), nullable=False),
sa.Column('pictureName', sa.String(length=200), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('gitAddress', sa.String(length=200), nullable=False),
sa.Column('create_date', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=150), nullable=False),
sa.Column('password', sa.String(length=200), nullable=False),
sa.Column('email', sa.String(length=120), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('username')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('user')
op.drop_table('project')
op.drop_table('article')
# ### end Alembic commands ###
"""empty message
Revision ID: 47168028e699
Revises: 8692d2d51066
Create Date: 2020-08-31 05:13:48.638348
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '47168028e699'
down_revision = '8692d2d51066'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('article')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('article',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('subject', sa.VARCHAR(length=200), nullable=False),
sa.Column('content', sa.TEXT(), nullable=False),
sa.Column('create_date', sa.DATETIME(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
"""empty message
Revision ID: 8692d2d51066
Revises: 415b13ebc457
Create Date: 2020-08-31 05:13:14.858317
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8692d2d51066'
down_revision = '415b13ebc457'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('user')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('username', sa.VARCHAR(length=150), nullable=False),
sa.Column('password', sa.VARCHAR(length=200), nullable=False),
sa.Column('email', sa.VARCHAR(length=120), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('username')
)
# ### end Alembic commands ###