django4.0+vue3对接

使用corsheaders包跨域

设置参考:https://blog.csdn.net/m0_62520968/article/details/124376513?spm=1001.2014.3001.5501https://blog.csdn.net/m0_62520968/article/details/124376513?spm=1001.2014.3001.5501

Django后端,rest_framework+token登录验证,使用默认用户表

建立模型models.py

from django.db import models
from io import BytesIO
from PIL import Image
from django.core.files import File


# Create your models here.
class Category(models.Model):
    name = models.CharField(max_length=255)
    slug = models.SlugField()  # 仅包含数字字母下划线,常用于网址

    class Meta:
        ordering = ('name',)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return f'/{self.slug}/'


class Product(models.Model):
    category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE)
    name = models.CharField(max_length=255)
    slug = models.SlugField()
    description = models.TextField(blank=True, null=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    image = models.ImageField(upload_to='uploads/', blank=True, null=True)
    thumbnail = models.ImageField(upload_to='uploads/', blank=True, null=True)
    date_add = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('-date_add',)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return f'/{self.category.slug}/{self.slug}/'

    def get_image(self):
        if self.image:
            return 'http://127.0.0.1:8000' + self.image.url
        return ''

    def get_thumbnail(self):
        if self.thumbnail:
            return 'http://127.0.0.1:8000' + self.thumbnail.url
        else:
            if self.image:
                self.thumbnail = self.make_thumbnail(self.image)
                self.save()

                return 'http://127.0.0.1:8000' + self.thumbnail.url
            else:
                return ''

    def make_thumbnail(self, image, size=(300, 200)):
        img = Image.open(image)
        img.convert('RGB')
        img.thumbnail(size)

        thumb_io = BytesIO()
        img.save(thumb_io, 'JPEG', quality=85)

        thumbnail = File(thumb_io, name=image.name)

        return thumbnail

模型序列化serializers.py

from rest_framework import serializers

from .models import *


class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = (
            'id',
            'name',
            'get_absolute_url',
            'description',
            'price',
            'get_image',
            'get_thumbnail',

        )

class CategorySerializer(serializers.ModelSerializer):
    # Category是Product的外键,这样可以调用Product序列化的数据
    products = ProductSerializer(many=True)

    class Meta:
        model = Category
        fields = (
            'id',
            'name',
            'get_absolute_url',
            'products',
        )

视图函数view.py

from django.db.models import Q
from django.http import Http404
from django.shortcuts import render
from .serializers import *
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import *


# Create your views here.
class LatestProductList(APIView):
    def get(self, request, format=None):
        products = Product.objects.all()
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)


class ProductDetail(APIView):
    def get_object(self, category_slug, product_slug):
        try:
            return Product.objects.filter(category__slug=category_slug).get(slug=product_slug)
        except Product.DoesNotExist:
            raise Http404

    def get(self, request, category_slug, product_slug, format=None):
        product = self.get_object(category_slug, product_slug)
        serializer = ProductSerializer(product)
        return Response(serializer.data)


class CategoryDetail(APIView):
    def get_object(self, category_slug):
        try:
            return Category.objects.get(slug=category_slug)
        except Product.DoesNotExist:
            raise Http404

    def get(self, request, category_slug, format=None):
        category = self.get_object(category_slug)
        serializer = CategorySerializer(category)
        return Response(serializer.data)


@api_view(['POST'])
def search(request):
    query = request.data.get('query', '')

    if query:
        products = Product.objects.filter(Q(name__icontains=query) | Q(description__icontains=query))
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

    else:
        return Response({'products': []})

主urls.py

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static  # static函数添加静态文件

urlpatterns = [
                  path('admin/', admin.site.urls),
                  path('api/v1/', include('djoser.urls')),
                  path('api/v1/', include('djoser.urls.authtoken')),
                  path('api/v1/', include('products.urls'), name='products'),
              ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

应用urls.py

from django.urls import path, include
from products import views
urlpatterns = [
    path('latest-products/', views.LatestProductList.as_view(), name='latest-products'),
    path('products/search/', views.search),
    path('products/<slug:category_slug>/<slug:product_slug>/', views.ProductDetail.as_view()),
    path('products/<slug:category_slug>/', views.CategoryDetail.as_view()),
]

Vue3:

图标引入:public->index.html

      <!-- Font Awesome CSS-->
    <link href="https://cdn.bootcss.com/font-awesome/5.13.0/css/all.css" rel="stylesheet">

main.js设置默认访问url:axios.defaults.baseURL

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from "axios"

axios.defaults.baseURL = `http://127.0.0.1:8000`

createApp(App).use(store).use(router, axios).mount('#app')

 在views创建视图HomeView.vue   ProductView.vue......

 router->index.js加入设置路由,导入视图,默认进入home,对某个视图进行权限认证增加

meta:{
  requireLogin:true
}
router.beforeEach对每个视图检验权限,无权限的自动进入home,有权限的放行
import HomeView from '../views/HomeView.vue'
import ProductView from "../views/ProductView.vue";
import Category from "@/views/Category";
import Search from "@/views/Search";
import SignUp from "@/views/SignUp";
import Login from "@/views/Login";
import MyAccount from "@/views/MyAccount";
const routes = [
{
    path: '/',
    name: 'home',
    component: HomeView
  },
{
    path: '/:category_slug/:product_slug/',
    name: 'productview',
    component: ProductView
  },
  {
    path: '/:category_slug',
    name: 'Category',
    component: Category
  },
  {
    path: '/search',
    name: 'Search',
    component: Search
  },
{
    path: '/sign-up',
    name: 'SignUp',
    component: SignUp
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '/myaccount',
    name: 'MyAccount',
    component: MyAccount,
    meta:{
      requireLogin:true
    }
  },]
router.beforeEach((to, from, next)=>{
  if (to.matched.some(record=>record.meta.requireLogin) && !store.state.isAuthenticated) {
    next({name:'Login', query:{to:to.path}});
  }else {
    next()
  }
})

 store->index.js保存状态加入通用方法

state: {
    isAuthenticated:false,
    token:'',
    isLoading:false,
  },
  getters: {
  },
  mutations: {
    initializeStore(state){
      if (localStorage.getItem('token')){
        state.token = localStorage.getItem('token')
        state.isAuthenticated = true
      }else {
        state.token= ''
        state.isAuthenticated = false
      }
    },

    //设置加载框
    setIsLoading(state,status){
      state.isLoading = status
    },
    setToken(state, token){
      state.token = token
      state.isAuthenticated = true
    },
    removeToken(state, token){
      state.token = ''
      state.isAuthenticated = false
    },
  },

App.vue主模块,类似模板,可当做模板页面使用,其他页面都是在它基础上添加组件,需要返回渲染页面的数据在data->return声明,调用 store->index.js的初始化方法,设置ajax请求头,在'Token '+ token拼接时一定要加一个空格否则验证不通过

axios.defaults.headers.common['Authorization'] = 'Token '+ token
<template>
  <div id="wrapper">
    <nav class="navbar is-dark">
      <div class="navbar-brand">
        <router-link to="/" class="navbar-item"><strong>JACKETS</strong></router-link>

        <a class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbar-menu" @click="showMobileMenu = !showMobileMenu">
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
        </a>
      </div>
      <div class="navbar-menu" id="navbar-menu" v-bind:class="{'is-active':showMobileMenu}">
        <div class="navbar-start">
          <div class="navbar-item">
            <form action="/search" method="get">
              <div class="field has-addons">
                <div class="control">
                  <input type="text" class="input" placeholder="What are you looking for?" name="query">
                </div>

                <div class="control">
                  <button class="button is-success">
                    <span class="icon">
                      <i class="fas fa-search"></i>
                    </span>
                  </button>
                </div>
              </div>
            </form>
          </div>
        </div>
        <div class="navbar-end">
          <router-link to="/summer" class="navbar-item">Summer</router-link>
          <router-link to="/winter" class="navbar-item">Winter</router-link>

          <div class="navbar-item">
            <div class="buttons">
              <template v-if="$store.state.isAuthenticated">
                <router-link to="/myaccount" class="button is-light">My Account</router-link>
                <button @click="logout()" class="button is-danger">Logout</button>
              </template>

              <template v-else>
                <router-link to="/login" class="button is-light" >Login</router-link>
                <router-link to="/sign-up" class="button is-light" >SignUp</router-link>
              </template>

              <router-link to="/cart" class="button is-success">
                <span class="icon"><i class="fas fa-shopping-cart"></i></span>
                <span>Cart ({{cartTotalLength}}) </span>
              </router-link>
            </div>
          </div>
        </div>
      </div>
    </nav>

    <div class="is-loading-bar has-text-centered" v-bind:class="{'is-loading': $store.state.isLoading}">
      <div class="lds-dual-ring"></div>
    </div>

    <section class="section">
      <router-view/>
    </section>

    <footer class="footer">
      <p class="has-text-centered">Copyright(c)2022</p>
    </footer>
  </div>
</template>

<script>
  import axios from "axios";

  export default {
    data() {
      return{
        showMobileMenu: false,

      }
    },
    beforeCreate() {
      this.$store.commit('initializeStore')

      const token = this.$store.state.token

      if (token){
        axios.defaults.headers.common['Authorization'] = 'Token '+ token

      }else {
        axios.defaults.headers.common['Authorization'] = ''
      }
    },
    mounted() {
    },
    computed:{
      //cartTotalLength(){
        let totalLength = 0

        for (let i=0;i<this.cart.items.length;i++){
          totalLength += this.cart.items[i].quantity
        }
        return totalLength
      }
    },
    methods:{
      logout(){
        axios.defaults.headers.common['Authorization'] = ''

        localStorage.removeItem('token')
        localStorage.removeItem('username')
        localStorage.removeItem('userid')

        this.$store.commit('removeToken')

        this.$router.push('/')
      }
    }
  }
</script>

<style lang="scss">
@import '../node_modules/bulma';
.lds-dual-ring{
  display: inline-block;
  width: 80px;
  height: 80px;

}
.lds-dual-ring:after{
  content: " ";
  display: block;
  width: 64px;
  height: 64px;
  margin: 8px;
  border-radius: 50%;
  border: 6px solid #ccc;
  border-color: #ccc transparent #ccc transparent;
  animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
.is-loading-bar{
  height: 0;
  overflow: hidden;

  -webkit-transition: all 0.3s;
  transition: all 0.3s;

  &.is-loading{
    height: 80px;
  }
}
</style>

HomeView.vue,mounted部分在网页初始化调用,ajax的get请求及返回数据接收,url使用反引号(英文状态下的1左边的~)。一些自定义组件(例如商品介绍卡片)可以在components部分调用,但是需要在components文件夹自定义(如ProductBox组件)

HomeView.vue

<template>
  <div class="home">
    <section class="hero is-medium is-dark mb-6">
      <div class="hero-body has-text-centered">
        <p class="title mb-6">
          Welcome to jacket
        </p>
        <p class="subtitle">
          The best jacket store online
        </p>
      </div>
    </section>

    <div class="columns is-multiline">
      <div class="column is-12">
        <h2 class="is-size-2 has-text-centered"> Latest products</h2>
      </div>

<!--      <div class="column is-3" v-for="product in latestProducts"-->
<!--           v-bind:key="product.id">-->

<!--        <div class="box">-->
<!--          <figure class="image mb-4">-->
<!--            <img v-bind:src="product.get_thumbnail">-->
<!--          </figure>-->

<!--          <h3 class="is-size-6">{{product.name}}</h3>-->
<!--          <p class="is-size-6 has-text-grey">${{product.price}}</p>-->

<!--          <router-link v-bind:to="product.get_absolute_url" class="button is-dark mt-4"> View details</router-link>-->
<!--        </div>-->
<!--      </div>-->
<!--      调用自定义组件-->
      <ProductBox
        v-for="product in latestProducts"
        v-bind:key="product.id"
        v-bind:product="product"/>
    </div>
  </div>
</template>

<script>
import axios from 'axios'

//导入自己封装的组件
import ProductBox from "@/components/ProductBox";

export default {
  name: 'HomeView',
  data(){
    return{
      latestProducts:[]
    }
  },
  components: {
    //调用自定义组件
    ProductBox
  },
  mounted() {
    this.getLatestProducts()

    //网页修改标题
    document.title = 'Home | JACKETS-SHOP'
  },
  methods:{
    async getLatestProducts(){
      //添加加载框
      this.$store.commit('setIsLoading',true)

      await axios
        .get(`/api/v1/latest-products/`)
      .then(response=>{
        this.latestProducts=response.data
      })
      .catch(error=>{
        console.log(error)
      })

      //移除加载框
      this.$store.commit('setIsLoading',false)
    }
  }
}
</script>

components->ProductBox.vue,组件声明,

props:{
  product:Object
}

然后在其他vue里导入,就可以在页面这样调用

<ProductBox
        v-for="product in latestProducts"//操作
        v-bind:key="product.id"
        v-bind:product="product"/>//绑定

<template>
  <div class="column is-3">
    <div class="box">
      <figure class="image mb-4">
        <img v-bind:src="product.get_thumbnail">
      </figure>

      <h3 class="is-size-6">{{product.name}}</h3>
      <p class="is-size-6 has-text-grey">${{product.price}}</p>

      <router-link v-bind:to="product.get_absolute_url" class="button is-dark mt-4"> View details</router-link>
    </div>
  </div>
</template>

<script>
export default {
  name: "ProductBox",
  //将一个通用组件封装在需要的地方调用
  props:{
    product:Object
  }
}
</script>

<style scoped>
.image{
  margin-top: -1.25rem;
  margin-left: -1.25rem;
  margin-right: -1.25rem;
}
</style>
Search.vue,post方法举例
<template>
  <div class="page-search">
    <div class="columns is-multiline">
      <div class="column is-12">
        <h1 class="title">Search</h1>

        <h2 class="is-size-5 has-text-grey">Search term:"{{query}}"</h2>
      </div>

      <!--      调用自定义组件-->
      <ProductBox
          v-for="product in products"
          v-bind:key="product.id"
          v-bind:product="product"/>
    </div>
  </div>
</template>

<script>
import axios from "axios";
import ProductBox from "@/components/ProductBox";

export default {
  name: "Search",
  components:{
    ProductBox
  },
  data(){
    return{
      products:[],
      query:''
    }
  },
  mounted() {
    document .title = 'Search | JACKETS-SHOP'

    let url = window.location.search.substring(1)
    let params = new URLSearchParams(url)

    if (params.get('query')){
      this.query = params.get('query')

      this.performSearch()
    }
  },
  methods:{
    async performSearch(){
      //添加加载框
      this.$store.commit('setIsLoading',true)

      await axios
          .post(`/api/v1/products/search/`, {'query':this.query})
          .then(response=>{
            this.products=response.data
          })
          .catch(error=>{
            console.log(error)
          })

      //移除加载框
      this.$store.commit('setIsLoading',false)
    }
  }
}
</script>

<style scoped>

</style>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于 Ubuntu 20.04 操作系统的 nginx+uwsgi+docker+django4.0+python3.9+mysql8.0 部署步骤: 1. 首先安装必要的软件和依赖: ``` sudo apt-get update sudo apt-get install -y git python3-pip python3-dev python3-venv build-essential libssl-dev libffi-dev nginx docker.io docker-compose mysql-server ``` 2. 创建并激活 Python 虚拟环境: ``` python3 -m venv myprojectenv source myprojectenv/bin/activate ``` 3. 安装 Django 和 uWSGI: ``` pip install django==4.0 uwsgi ``` 4. 创建 Django 项目: ``` django-admin startproject myproject cd myproject ``` 5. 配置 Django 数据库设置: 打开 `myproject/settings.py` 文件,在 `DATABASES` 中添加以下内容: ``` DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'myprojectdb', 'USER': 'myprojectuser', 'PASSWORD': 'mypassword', 'HOST': 'localhost', 'PORT': '3306', } } ``` 6. 创建 MySQL 数据库和用户: 登录 MySQL: ``` sudo mysql ``` 创建数据库: ``` CREATE DATABASE myprojectdb CHARACTER SET UTF8; ``` 创建用户: ``` CREATE USER 'myprojectuser'@'localhost' IDENTIFIED BY 'mypassword'; ``` 授权用户访问数据库: ``` GRANT ALL PRIVILEGES ON myprojectdb.* TO 'myprojectuser'@'localhost'; ``` 刷新权限: ``` FLUSH PRIVILEGES; ``` 退出 MySQL: ``` exit ``` 7. 测试 Django 项目是否能够正常运行: ``` python manage.py runserver ``` 在浏览器中访问 `http://localhost:8000`,如果能够正常显示 Django 的欢迎页面,说明 Django 项目已经成功搭建。 8. 配置 uWSGI: 创建 `myproject/uwsgi.ini` 文件,添加以下内容: ``` [uwsgi] socket = :8001 chdir = /path/to/myproject module = myproject.wsgi:application master = true pidfile = /tmp/myproject-master.pid processes = 4 threads = 2 vacuum = true max-requests = 1000 harakiri = 60 ``` 9. 启动 uWSGI: ``` uwsgi --ini myproject/uwsgi.ini ``` 10. 配置 nginx: 创建 `/etc/nginx/sites-available/myproject` 文件,添加以下内容: ``` server { listen 80; server_name myproject.com; access_log /var/log/nginx/myproject.access.log; error_log /var/log/nginx/myproject.error.log; client_max_body_size 20M; location /static/ { alias /path/to/myproject/static/; } location /media/ { alias /path/to/myproject/media/; } location / { uwsgi_pass 127.0.0.1:8001; include /etc/nginx/uwsgi_params; } } ``` 11. 创建软链接: ``` sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/ ``` 12. 测试 nginx 配置是否正确: ``` sudo nginx -t ``` 如果没有错误,重启 nginx: ``` sudo systemctl restart nginx ``` 13. 创建 Dockerfile: 在 Django 项目根目录下创建 `Dockerfile` 文件,添加以下内容: ``` FROM python:3.9 RUN apt-get update \ && apt-get install -y nginx \ && rm -rf /var/lib/apt/lists/* RUN pip install uwsgi COPY ./requirements.txt /app/requirements.txt RUN pip install -r /app/requirements.txt COPY . /app WORKDIR /app RUN python manage.py collectstatic --noinput COPY ./myproject-nginx.conf /etc/nginx/sites-available/myproject RUN ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/ RUN rm /etc/nginx/sites-enabled/default EXPOSE 80 CMD service nginx start && uwsgi --ini /app/uwsgi.ini ``` 14. 创建 docker-compose.yml 文件: 在 Django 项目根目录下创建 `docker-compose.yml` 文件,添加以下内容: ``` version: '3' services: web: build: . ports: - "80:80" depends_on: - db volumes: - ./static:/app/static - ./media:/app/media environment: - DB_HOST=db - DB_NAME=myprojectdb - DB_USER=myprojectuser - DB_PASSWORD=mypassword db: image: mysql:8.0 volumes: - db_data:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=rootpassword - MYSQL_DATABASE=myprojectdb - MYSQL_USER=myprojectuser - MYSQL_PASSWORD=mypassword volumes: db_data: ``` 15. 构建和运行 Docker 容器: ``` sudo docker-compose up --build ``` 16. 测试 Django 项目是否能够正常运行: 在浏览器中访问 `http://localhost`,如果能够正常显示 Django 的欢迎页面,说明 Django 项目已经成功部署到 Docker 容器中。 至此,nginx+uwsgi+docker+django4.0+python3.9+mysql8.0 部署完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值