Feature: Add pin-creation from remote-url

+ Add CSRF protection for every request
This commit is contained in:
winkidney
2019-12-04 22:48:23 +08:00
committed by Isaac Bythewood
parent 5282ed5e53
commit bb24ed3ecf
9 changed files with 279 additions and 24 deletions

View File

@@ -13,5 +13,6 @@
</noscript> </noscript>
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
<link rel="stylesheet" href="https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css">
</body> </body>
</html> </html>

View File

@@ -27,16 +27,15 @@
Create Create
</a> </a>
<div class="navbar-dropdown"> <div class="navbar-dropdown">
<router-link <a
to="/" @click="createPin"
class="navbar-item"> class="navbar-item">
Pin Pin
</router-link> </a>
<router-link <a
to="/"
class="navbar-item"> class="navbar-item">
Board Board
</router-link> </a>
</div> </div>
</div> </div>
<div <div
@@ -104,8 +103,7 @@
<script> <script>
import api from './api'; import api from './api';
import LoginForm from './LoginForm.vue'; import modals from './modals';
import SignUpForm from './SignUpForm.vue';
export default { export default {
name: 'p-header', name: 'p-header',
@@ -128,6 +126,8 @@ export default {
onSignUpSucceed() { onSignUpSucceed() {
this.initializeUser(true); this.initializeUser(true);
}, },
onPinCreated() {
},
logOut() { logOut() {
api.User.logOut().then( api.User.logOut().then(
() => { () => {
@@ -136,24 +136,13 @@ export default {
); );
}, },
logIn() { logIn() {
this.$buefy.modal.open({ modals.openLogin(this, this.onLoginSucceed);
parent: this, },
component: LoginForm, createPin() {
hasModalCard: true, modals.openPinCreate(this, this.onPinCreated);
events: {
'login.succeed': this.onLoginSucceed,
},
});
}, },
signUp() { signUp() {
this.$buefy.modal.open({ modals.openSignUp(this, this.onSignUpSucceed);
parent: this,
component: SignUpForm,
hasModalCard: true,
events: {
'signup.succeed': this.onSignUpSucceed,
},
});
}, },
initializeUser(force = false) { initializeUser(force = false) {
const self = this; const self = this;

View File

@@ -3,6 +3,17 @@ import storage from './utils/storage';
const API_PREFIX = '/api/v2/'; const API_PREFIX = '/api/v2/';
const Pin = {
createFromURL(jsonData) {
const url = `${API_PREFIX}pins/`;
return axios.post(
url,
jsonData,
);
},
};
function fetchPins(offset, tagFilter, userFilter) { function fetchPins(offset, tagFilter, userFilter) {
const url = `${API_PREFIX}pins/`; const url = `${API_PREFIX}pins/`;
const queryArgs = { const queryArgs = {
@@ -160,6 +171,7 @@ const User = {
}; };
export default { export default {
Pin,
fetchPin, fetchPin,
fetchPins, fetchPins,
fetchPinsForBoard, fetchPinsForBoard,

View File

@@ -0,0 +1,45 @@
import PinCreateModal from './pin_create/PinCreateModal.vue';
import LoginForm from './LoginForm.vue';
import SignUpForm from './SignUpForm.vue';
function openPinCreate(vm, onCreate) {
vm.$buefy.modal.open(
{
parent: vm,
component: PinCreateModal,
hasModalCard: true,
events: {
'create.succeed': onCreate,
},
},
);
}
function openLogin(vm, onSucceed) {
vm.$buefy.modal.open({
parent: vm,
component: LoginForm,
hasModalCard: true,
events: {
'login.succeed': onSucceed,
},
});
}
function openSignUp(vm, onSignUpSucceed) {
vm.$buefy.modal.open({
parent: vm,
component: SignUpForm,
hasModalCard: true,
events: {
'signup.succeed': onSignUpSucceed,
},
});
}
export default {
openPinCreate,
openLogin,
openSignUp,
};

View File

@@ -0,0 +1,63 @@
<template>
<div class="image-upload">
<div
v-show="previewExists"
class="has-text-centered is-center preview">
<img :src="previewImageURL">
</div>
<div v-show="!previewExists">
<b-field>
<b-upload v-model="dropFiles"
multiple
drag-drop>
<section class="section">
<div class="content has-text-centered">
<p>
<b-icon
icon="upload"
size="is-medium">
</b-icon>
</p>
<p>Drop your files here or click to upload</p>
</div>
</section>
</b-upload>
</b-field>
</div>
</div>
</template>
<script>
export default {
name: 'FileUpload',
props: {
previewImageURL: {
type: String,
default: null,
},
},
computed: {
previewExists() {
return this.previewImageURL !== null && this.previewImageURL !== '';
},
},
data() {
return {
dropFiles: [],
};
},
methods: {},
};
</script>
<style lang="scss" scoped>
@import '../utils/pin';
@import '../utils/loader';
.preview > img {
width: $pin-preview-width;
height: auto;
@include loader('../../assets/loader.gif');
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div class="pin-create-modal">
<form action="">
<div class="modal-card" style="width: auto">
<header class="modal-card-head">
<p class="modal-card-title">New Pin</p>
</header>
<section class="modal-card-body">
<div class="columns">
<div class="column">
<FileUpload :previewImageURL="form.url.value"></FileUpload>
</div>
<div class="column">
<b-field label="Image URL"
:type="form.url.type"
:message="form.url.error">
<b-input
type="text"
v-model="form.url.value"
placeholder="where to fetch the image"
maxlength="256"
>
</b-input>
</b-field>
<b-field label="Image Referer"
:type="form.referer.type"
:message="form.referer.error">
<b-input
type="text"
v-model="form.referer.value"
placeholder="where to find the pin"
maxlength="256"
>
</b-input>
</b-field>
<b-field label="Descripton"
:type="form.description.type"
:message="form.description.error">
<b-input
type="textarea"
v-model="form.description.value"
placeholder="idea from this pin"
maxlength="1024"
>
</b-input>
</b-field>
<b-field label="Tags">
<b-taginput
v-model="form.tags.value"
ellipsis
icon="label"
placeholder="Add a tag">
</b-taginput>
</b-field>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button" type="button" @click="$parent.close()">Close</button>
<button
@click="createPin"
class="button is-primary">Create Pin
</button>
</footer>
</div>
</form>
</div>
</template>
<script>
import api from '../api';
import FileUpload from './FileUpload.vue';
function isURLBlank(url) {
return url !== null && url !== '';
}
export default {
name: 'PinCreateModal',
components: {
FileUpload,
},
data() {
return {
form: {
url: { value: null, error: null, type: null },
referer: { value: null, error: null, type: null },
description: { value: null, error: null, type: null },
tags: { value: [], error: null, type: null },
},
};
},
methods: {
createPin() {
const self = this;
if (isURLBlank(isURLBlank)) {
const data = {
url: this.form.url.value,
referer: this.form.referer.value,
description: this.form.description.value,
tags: this.form.tags.value,
};
const promise = api.Pin.createFromURL(data);
promise.then(
(resp) => {
self.$emit('pin.created', resp);
self.$parent.close();
},
);
}
},
},
};
</script>

View File

@@ -0,0 +1,17 @@
<template>
<div class="pin-create">
<div></div>
</div>
</template>
<script>
// import PinCreateModal from './PinCreateModal.vue';
export default {
name: 'PinCreate',
};
</script>
<style scoped>
</style>

11
pinry/middleware.py Normal file
View File

@@ -0,0 +1,11 @@
from django.middleware.csrf import get_token
class ForceCSRFCookieMiddleware:
def process_request(self, request):
if "CSRF_TOKEN" not in request.META:
get_token(request)
else:
if request.method != "GET":
get_token(request)
return

View File

@@ -26,12 +26,15 @@ INSTALLED_APPS = [
ROOT_URLCONF = 'pinry.urls' ROOT_URLCONF = 'pinry.urls'
MIDDLEWARE_CLASSES = [ MIDDLEWARE_CLASSES = [
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'pinry.middleware.ForceCSRFCookieMiddleware',
'users.middleware.Public', 'users.middleware.Public',
] ]