mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2025-08-15 01:14:53 +03:00
Add Vue project generation options
This commit is contained in:
parent
ca4b3262fd
commit
7d993c5db1
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2013-2020, Daniel Roy Greenfeld
|
||||
Copyright (c) 2013-2020, Daniel Roy Greenfeld, Mike Hoolehan
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
|
|
327
README.rst
327
README.rst
|
@ -1,323 +1,100 @@
|
|||
Cookiecutter Django
|
||||
Cookiecutter Vue Django
|
||||
=======================
|
||||
|
||||
.. image:: https://travis-ci.org/pydanny/cookiecutter-django.svg?branch=master
|
||||
:target: https://travis-ci.org/pydanny/cookiecutter-django?branch=master
|
||||
.. image:: https://travis-ci.com/ilikerobots/cookiecutter-vue-django.svg?branch=master
|
||||
:target: https://travis-ci.com/ilikerobots/cookiecutter-vue-django?branch=master
|
||||
:alt: Build Status
|
||||
|
||||
.. image:: https://readthedocs.org/projects/cookiecutter-django/badge/?version=latest
|
||||
:target: https://cookiecutter-django.readthedocs.io/en/latest/?badge=latest
|
||||
.. image:: https://readthedocs.org/projects/cookiecutter-vue-django/badge/?version=latest
|
||||
:target: https://cookiecutter-vue-django.readthedocs.io/en/latest/?badge=latest
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://pyup.io/repos/github/pydanny/cookiecutter-django/shield.svg
|
||||
:target: https://pyup.io/repos/github/pydanny/cookiecutter-django/
|
||||
.. image:: https://pyup.io/repos/github/ilikerobots/cookiecutter-vue-django/shield.svg
|
||||
:target: https://pyup.io/repos/github/ilikerobots/cookiecutter-vue-django/
|
||||
:alt: Updates
|
||||
|
||||
.. image:: https://img.shields.io/badge/cookiecutter-Join%20on%20Slack-green?style=flat&logo=slack
|
||||
:target: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U
|
||||
Vue + Django with no compromise.
|
||||
|
||||
.. image:: https://www.codetriage.com/pydanny/cookiecutter-django/badges/users.svg
|
||||
:target: https://www.codetriage.com/pydanny/cookiecutter-django
|
||||
:alt: Code Helpers Badge
|
||||
Cookiecutter Vue Django is a framework for jumpstarting production-ready Django + Vue projects quickly. Expanding on the the wonderful Cookiecutter Django, this project template allows the intermingling of both Django Templates and Vue, even on the same page, without compromising the full power of either.
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/ambv/black
|
||||
:alt: Code style: black
|
||||
Typical solutions to integrating Django and Vue forgo much of the strengths of one lieu of the other. For example, a common approach is using Django Rest Framework as backend and writing the entire front end in Vue, making it difficult to utilize Django templates in places it could be expedient. A second approach is to use Vue within Django templates using browser `<script>` includes, but then lost is the ability to use Vue's Single File Components.
|
||||
|
||||
Powered by Cookiecutter_, Cookiecutter Django is a framework for jumpstarting
|
||||
production-ready Django projects quickly.
|
||||
This project utilizes a different approach, melding these two technologies more naturally. As a result, not only are the typical compromises eliminated, but additional distinct advantages are realized:
|
||||
|
||||
* Documentation: https://cookiecutter-django.readthedocs.io/en/latest/
|
||||
* See Troubleshooting_ for common errors and obstacles
|
||||
* If you have problems with Cookiecutter Django, please open issues_ don't send
|
||||
emails to the maintainers.
|
||||
|
||||
.. _Troubleshooting: https://cookiecutter-django.readthedocs.io/en/latest/troubleshooting.html
|
||||
|
||||
.. _528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373
|
||||
.. _issues: https://github.com/pydanny/cookiecutter-django/issues/new
|
||||
* Increased flexibility: The developer is free to use Django Templates or Vue as appropriate, choosing the right tool for the job
|
||||
* Increased development speed: Reduce time spent fighting the framework by using Django and Vue where each excels
|
||||
* Increased performance: Leverage Django's powerful caching backend to deliver content-rich pages quickly with little or no Javascript, while deferring complex and interactive Vue functionality until after page load
|
||||
|
||||
Features
|
||||
---------
|
||||
|
||||
* For Django 3.0
|
||||
* Works with Python 3.8
|
||||
* Renders Django projects with 100% starting test coverage
|
||||
* Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available)
|
||||
* 12-Factor_ based settings via django-environ_
|
||||
* Secure by default. We believe in SSL.
|
||||
* Optimized development and production settings
|
||||
* Registration via django-allauth_
|
||||
* Comes with custom user model ready to go
|
||||
* Optional basic ASGI setup for Websockets
|
||||
* Optional custom static build using Gulp and livereload
|
||||
* Send emails via Anymail_ (using Mailgun_ by default or Amazon SES if AWS is selected cloud provider, but switchable)
|
||||
* Media storage using Amazon S3 or Google Cloud Storage
|
||||
* Docker support using docker-compose_ for development and production (using Traefik_ with LetsEncrypt_ support)
|
||||
* Procfile_ for deploying to Heroku
|
||||
* Instructions for deploying to PythonAnywhere_
|
||||
* Run tests with unittest or pytest
|
||||
* Customizable PostgreSQL version
|
||||
* Default integration with pre-commit_ for identifying simple issues before submission to code review
|
||||
* All the features of the wonderful cookiecutter-django_
|
||||
* Harmonious coexistence of Django templates and Vue components
|
||||
* Vue Single File Components (SFCs)
|
||||
* Multi-page App (MPA) layout
|
||||
* Vue Loader Hot Reload
|
||||
* Property passing from Django Template -> Vue Component
|
||||
* Sass/SCSS pre-compilation of Vue Components
|
||||
* Vue DevTools support
|
||||
* Chunked resource loading via webpack
|
||||
* Deferred loading of Vue and/or Vue components
|
||||
* Shared Vuex state across components on the same page
|
||||
* Persistent state across page loads
|
||||
* REST support via Axios -> DRF
|
||||
* Sample application illustrating all of the above
|
||||
|
||||
.. _`maintained Foundation fork`: https://github.com/Parbhat/cookiecutter-django-foundation
|
||||
|
||||
|
||||
Optional Integrations
|
||||
---------------------
|
||||
|
||||
*These features can be enabled during initial project setup.*
|
||||
|
||||
* Serve static files from Amazon S3, Google Cloud Storage or Whitenoise_
|
||||
* Configuration for Celery_ and Flower_ (the latter in Docker setup only)
|
||||
* Integration with MailHog_ for local email testing
|
||||
* Integration with Sentry_ for error logging
|
||||
|
||||
.. _Bootstrap: https://github.com/twbs/bootstrap
|
||||
.. _django-environ: https://github.com/joke2k/django-environ
|
||||
.. _12-Factor: http://12factor.net/
|
||||
.. _django-allauth: https://github.com/pennersr/django-allauth
|
||||
.. _django-avatar: https://github.com/grantmcconnaughey/django-avatar
|
||||
.. _Procfile: https://devcenter.heroku.com/articles/procfile
|
||||
.. _Mailgun: http://www.mailgun.com/
|
||||
.. _Whitenoise: https://whitenoise.readthedocs.io/
|
||||
.. _Celery: http://www.celeryproject.org/
|
||||
.. _Flower: https://github.com/mher/flower
|
||||
.. _Anymail: https://github.com/anymail/django-anymail
|
||||
.. _MailHog: https://github.com/mailhog/MailHog
|
||||
.. _Sentry: https://sentry.io/welcome/
|
||||
.. _docker-compose: https://github.com/docker/compose
|
||||
.. _PythonAnywhere: https://www.pythonanywhere.com/
|
||||
.. _Traefik: https://traefik.io/
|
||||
.. _LetsEncrypt: https://letsencrypt.org/
|
||||
.. _pre-commit: https://github.com/pre-commit/pre-commit
|
||||
|
||||
Constraints
|
||||
-----------
|
||||
|
||||
* Only maintained 3rd party libraries are used.
|
||||
* Uses PostgreSQL everywhere (9.4 - 11.3)
|
||||
* Environment variables for configuration (This won't work with Apache/mod_wsgi).
|
||||
|
||||
Support this Project!
|
||||
----------------------
|
||||
|
||||
This project is run by volunteers. Please support them in their efforts to maintain and improve Cookiecutter Django:
|
||||
|
||||
* Daniel Roy Greenfeld, Project Lead (`GitHub <https://github.com/pydanny>`_, `Patreon <https://www.patreon.com/danielroygreenfeld>`_): expertise in Django and AWS ELB.
|
||||
|
||||
* Nikita Shupeyko, Core Developer (`GitHub <https://github.com/webyneter>`_): expertise in Python/Django, hands-on DevOps and frontend experience.
|
||||
|
||||
Projects that provide financial support to the maintainers:
|
||||
|
||||
Django Crash Course
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: https://cdn.shopify.com/s/files/1/0304/6901/files/Django-Crash-Course-300x436.jpg
|
||||
:name: Django Crash Course: Covers Django 3.0 and Python 3.8
|
||||
:align: center
|
||||
:alt: Django Crash Course
|
||||
:target: https://www.roygreenfeld.com/products/django-crash-course
|
||||
|
||||
Django Crash Course for Django 3.0 and Python 3.8 is the best cheese-themed Django reference in the universe!
|
||||
|
||||
pyup
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: https://pyup.io/static/images/logo.png
|
||||
:name: pyup
|
||||
:align: center
|
||||
:alt: pyup
|
||||
:target: https://pyup.io/
|
||||
|
||||
Pyup brings you automated security and dependency updates used by Google and other organizations. Free for open source projects!
|
||||
.. _cookiecutter-django: https://github.com/pydanny/cookiecutter-django
|
||||
|
||||
Usage
|
||||
------
|
||||
|
||||
Let's pretend you want to create a Django project called "redditclone". Rather than using ``startproject``
|
||||
and then editing the results to include your name, email, and various configuration issues that always get forgotten until the worst possible moment, get cookiecutter_ to do all the work.
|
||||
|
||||
First, get Cookiecutter. Trust me, it's awesome::
|
||||
|
||||
$ pip install "cookiecutter>=1.7.0"
|
||||
|
||||
Now run it against this repo::
|
||||
|
||||
$ cookiecutter https://github.com/pydanny/cookiecutter-django
|
||||
|
||||
You'll be prompted for some values. Provide them, then a Django project will be created for you.
|
||||
|
||||
**Warning**: After this point, change 'Daniel Greenfeld', 'pydanny', etc to your own information.
|
||||
|
||||
Answer the prompts with your own desired options_. For example::
|
||||
|
||||
Cloning into 'cookiecutter-django'...
|
||||
remote: Counting objects: 550, done.
|
||||
remote: Compressing objects: 100% (310/310), done.
|
||||
remote: Total 550 (delta 283), reused 479 (delta 222)
|
||||
Receiving objects: 100% (550/550), 127.66 KiB | 58 KiB/s, done.
|
||||
Resolving deltas: 100% (283/283), done.
|
||||
project_name [Project Name]: Reddit Clone
|
||||
project_slug [reddit_clone]: reddit
|
||||
author_name [Daniel Roy Greenfeld]: Daniel Greenfeld
|
||||
email [you@example.com]: pydanny@gmail.com
|
||||
description [Behold My Awesome Project!]: A reddit clone.
|
||||
domain_name [example.com]: myreddit.com
|
||||
version [0.1.0]: 0.0.1
|
||||
timezone [UTC]: America/Los_Angeles
|
||||
use_whitenoise [n]: n
|
||||
use_celery [n]: y
|
||||
use_mailhog [n]: n
|
||||
use_sentry [n]: y
|
||||
use_pycharm [n]: y
|
||||
windows [n]: n
|
||||
use_docker [n]: n
|
||||
use_heroku [n]: y
|
||||
use_compressor [n]: y
|
||||
Select postgresql_version:
|
||||
1 - 12.3
|
||||
2 - 11.8
|
||||
3 - 10.8
|
||||
4 - 9.6
|
||||
5 - 9.5
|
||||
Choose from 1, 2, 3, 4, 5 [1]: 1
|
||||
Select js_task_runner:
|
||||
1 - None
|
||||
2 - Gulp
|
||||
Choose from 1, 2 [1]: 1
|
||||
Select cloud_provider:
|
||||
1 - AWS
|
||||
2 - GCP
|
||||
3 - None
|
||||
Choose from 1, 2, 3 [1]: 1
|
||||
custom_bootstrap_compilation [n]: n
|
||||
Select open_source_license:
|
||||
1 - MIT
|
||||
2 - BSD
|
||||
3 - GPLv3
|
||||
4 - Apache Software License 2.0
|
||||
5 - Not open source
|
||||
Choose from 1, 2, 3, 4, 5 [1]: 1
|
||||
keep_local_envs_in_vcs [y]: y
|
||||
debug[n]: n
|
||||
|
||||
Enter the project and take a look around::
|
||||
|
||||
$ cd reddit/
|
||||
$ ls
|
||||
|
||||
Create a git repo and push it there::
|
||||
|
||||
$ git init
|
||||
$ git add .
|
||||
$ git commit -m "first awesome commit"
|
||||
$ git remote add origin git@github.com:pydanny/redditclone.git
|
||||
$ git push -u origin master
|
||||
|
||||
Now take a look at your repo. Don't forget to carefully look at the generated README. Awesome, right?
|
||||
|
||||
For local development, see the following:
|
||||
|
||||
* `Developing locally`_
|
||||
* `Developing locally using docker`_
|
||||
|
||||
.. _options: http://cookiecutter-django.readthedocs.io/en/latest/project-generation-options.html
|
||||
.. _`Developing locally`: http://cookiecutter-django.readthedocs.io/en/latest/developing-locally.html
|
||||
.. _`Developing locally using docker`: http://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html
|
||||
|
||||
Community
|
||||
-----------
|
||||
|
||||
* Have questions? **Before you ask questions anywhere else**, please post your question on `Stack Overflow`_ under the *cookiecutter-django* tag. We check there periodically for questions.
|
||||
* If you think you found a bug or want to request a feature, please open an issue_.
|
||||
* For anything else, you can chat with us on `Slack`_.
|
||||
|
||||
.. _`Stack Overflow`: http://stackoverflow.com/questions/tagged/cookiecutter-django
|
||||
.. _`issue`: https://github.com/pydanny/cookiecutter-django/issues
|
||||
.. _`Slack`: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U
|
||||
|
||||
For Readers of Two Scoops of Django
|
||||
--------------------------------------------
|
||||
|
||||
You may notice that some elements of this project do not exactly match what we describe in chapter 3. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored.
|
||||
|
||||
For pyup.io Users
|
||||
-----------------
|
||||
|
||||
If you are using `pyup.io`_ to keep your dependencies updated and secure, use the code *cookiecutter* during checkout to get 15% off every month.
|
||||
|
||||
.. _`pyup.io`: https://pyup.io
|
||||
|
||||
"Your Stuff"
|
||||
-------------
|
||||
|
||||
Scattered throughout the Python and HTML of this project are places marked with "your stuff". This is where third-party libraries are to be integrated with your project.
|
||||
|
||||
Releases
|
||||
--------
|
||||
|
||||
Need a stable release? You can find them at https://github.com/pydanny/cookiecutter-django/releases
|
||||
$ cookiecutter https://github.com/ilikerobots/cookiecutter-vue-django
|
||||
|
||||
|
||||
Not Exactly What You Want?
|
||||
---------------------------
|
||||
You'll be prompted for some values. Provide them, then a Django project will be created for you. Don't forget to carefully look at the generated README.
|
||||
|
||||
This is what I want. *It might not be what you want.* Don't worry, you have options:
|
||||
|
||||
Fork This
|
||||
~~~~~~~~~~
|
||||
|
||||
If you have differences in your preferred setup, I encourage you to fork this to create your own version.
|
||||
Once you have your fork working, let me know and I'll add it to a '*Similar Cookiecutter Templates*' list here.
|
||||
It's up to you whether or not to rename your fork.
|
||||
|
||||
If you do rename your fork, I encourage you to submit it to the following places:
|
||||
|
||||
* cookiecutter_ so it gets listed in the README as a template.
|
||||
* The cookiecutter grid_ on Django Packages.
|
||||
For more detailed instructions, see upstream cookiecutter-django_
|
||||
|
||||
.. _cookiecutter: https://github.com/cookiecutter/cookiecutter
|
||||
.. _grid: https://www.djangopackages.com/grids/g/cookiecutters/
|
||||
.. _cookiecutter-django: https://github.com/pydanny/cookiecutter-django
|
||||
|
||||
Submit a Pull Request
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
Issues
|
||||
-----------
|
||||
|
||||
We accept pull requests if they're small, atomic, and make our own project development
|
||||
experience better.
|
||||
* If you think you found a bug or want to request a feature, please open an issue_.
|
||||
|
||||
.. _`issue`: https://github.com/ilikerobots/cookiecutter-vue-django/issues
|
||||
|
||||
Articles
|
||||
---------
|
||||
|
||||
* `Using cookiecutter-django with Google Cloud Storage`_ - Mar. 12, 2019
|
||||
* `cookiecutter-django with Nginx, Route 53 and ELB`_ - Feb. 12, 2018
|
||||
* `cookiecutter-django and Amazon RDS`_ - Feb. 7, 2018
|
||||
* `Using Cookiecutter to Jumpstart a Django Project on Windows with PyCharm`_ - May 19, 2017
|
||||
* `Exploring with Cookiecutter`_ - Dec. 3, 2016
|
||||
* `Introduction to Cookiecutter-Django`_ - Feb. 19, 2016
|
||||
* `Django and GitLab - Running Continuous Integration and tests with your FREE account`_ - May. 11, 2016
|
||||
* `Development and Deployment of Cookiecutter-Django on Fedora`_ - Jan. 18, 2016
|
||||
* `Development and Deployment of Cookiecutter-Django via Docker`_ - Dec. 29, 2015
|
||||
* `How to create a Django Application using Cookiecutter and Django 1.8`_ - Sept. 12, 2015
|
||||
This cookiecutter is based on the methods described in the following articles
|
||||
|
||||
Have a blog or online publication? Write about your cookiecutter-django tips and tricks, then send us a pull request with the link.
|
||||
* `Vue + Django — Best of Both Frontends`_ - 26 May 2019 by Mike Hoolehan
|
||||
* `Vue + Django — Best of Both Frontends, Part 2`_ - 4 Dec 2019 by Mike Hoolehan
|
||||
* `Django + Vue — Blazing Content, Rich Interactivity`_ - 23 Apr 2020 by Mike Hoolehan
|
||||
|
||||
.. _`Using cookiecutter-django with Google Cloud Storage`: https://ahhda.github.io/cloud/gce/django/2019/03/12/using-django-cookiecutter-cloud-storage.html
|
||||
.. _`cookiecutter-django with Nginx, Route 53 and ELB`: https://msaizar.com/blog/cookiecutter-django-nginx-route-53-and-elb/
|
||||
.. _`cookiecutter-django and Amazon RDS`: https://msaizar.com/blog/cookiecutter-django-and-amazon-rds/
|
||||
.. _`Exploring with Cookiecutter`: http://www.snowboardingcoder.com/django/2016/12/03/exploring-with-cookiecutter/
|
||||
.. _`Using Cookiecutter to Jumpstart a Django Project on Windows with PyCharm`: https://joshuahunter.com/posts/using-cookiecutter-to-jumpstart-a-django-project-on-windows-with-pycharm/
|
||||
.. _`Vue + Django — Best of Both Frontends`: https://medium.com/js-dojo/vue-django-best-of-both-frontends-701307871478
|
||||
.. _`Vue + Django — Best of Both Frontends, Part 2`: https://medium.com/js-dojo/django-vue-vuex-best-of-both-frontends-part-2-1dcb78215575
|
||||
.. _`Django + Vue — Blazing Content, Rich Interactivity`: https://medium.com/js-dojo/django-vue-blazing-content-rich-interactivity-b34e45d8c602
|
||||
|
||||
|
||||
Show your Support
|
||||
-----------------
|
||||
|
||||
If you find this repository useful, then please consider leaving a star so this project can reach more people. Also, if the articles above were helpful, then a clap on those platforms would also be appreciated. Thanks!
|
||||
|
||||
.. _`Development and Deployment of Cookiecutter-Django via Docker`: https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-via-docker/
|
||||
.. _`Development and Deployment of Cookiecutter-Django on Fedora`: https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-on-fedora/
|
||||
.. _`How to create a Django Application using Cookiecutter and Django 1.8`: https://www.swapps.io/blog/how-to-create-a-django-application-using-cookiecutter-and-django-1-8/
|
||||
.. _`Introduction to Cookiecutter-Django`: http://krzysztofzuraw.com/blog/2016/django-cookiecutter.html
|
||||
.. _`Django and GitLab - Running Continuous Integration and tests with your FREE account`: http://dezoito.github.io/2016/05/11/django-gitlab-continuous-integration-phantomjs.html
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Everyone interacting in the Cookiecutter project's codebases, issue trackers, chat
|
||||
Everyone interacting in the this project's codebases, issue trackers, chat
|
||||
rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_.
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"project_name": "My Awesome Project",
|
||||
"project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}",
|
||||
"description": "Behold My Awesome Project!",
|
||||
"author_name": "Daniel Roy Greenfeld",
|
||||
"author_name": "Mordecai Brown",
|
||||
"domain_name": "example.com",
|
||||
"email": "{{ cookiecutter.author_name.lower()|replace(' ', '-') }}@example.com",
|
||||
"version": "0.1.0",
|
||||
|
@ -45,8 +45,11 @@
|
|||
"Other SMTP"
|
||||
],
|
||||
"use_async": "n",
|
||||
"use_drf": "n",
|
||||
"use_drf": "y",
|
||||
"custom_bootstrap_compilation": "n",
|
||||
"use_vue": "y",
|
||||
"use_vuex": "y",
|
||||
"use_fruit_demo": "y",
|
||||
"use_compressor": "n",
|
||||
"use_celery": "n",
|
||||
"use_mailhog": "n",
|
||||
|
|
|
@ -115,6 +115,49 @@ def remove_async_files():
|
|||
os.remove(file_name)
|
||||
|
||||
|
||||
def remove_fruit_files():
|
||||
shutil.rmtree("fruit")
|
||||
shutil.rmtree("vue_frontend/src/fruit")
|
||||
|
||||
|
||||
def remove_vuex_files():
|
||||
shutil.rmtree("vue_frontend/src/store")
|
||||
shutil.rmtree("vue_frontend/src/rest")
|
||||
|
||||
|
||||
def remove_vue_drf_files():
|
||||
shutil.rmtree("vue_frontend/src/rest")
|
||||
file_names = [
|
||||
os.path.join(
|
||||
"vue_frontend", "src", "fruit", "components", "FruitInspector.vue"
|
||||
),
|
||||
os.path.join("vue_frontend", "src", "fruit", "entry", "fruit_list.js"),
|
||||
os.path.join("fruit", "templates", "fruit", "fruit_list.html"),
|
||||
]
|
||||
for file_name in file_names:
|
||||
os.remove(file_name)
|
||||
|
||||
|
||||
def remove_vue_files():
|
||||
shutil.rmtree("vue_frontend")
|
||||
shutil.rmtree("{{ cookiecutter.project_slug }}/templates/webpack_bundle")
|
||||
shutil.rmtree("{{ cookiecutter.project_slug }}/webpack_bundle")
|
||||
os.remove(
|
||||
os.path.join(
|
||||
"{{ cookiecutter.project_slug }}", "static", "images", "django_logo.png"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def remove_vue_pycharm_files():
|
||||
file_names = [
|
||||
os.path.join(".idea", "runConfigurations", "vue_build.xml"),
|
||||
os.path.join(".idea", "runConfigurations", "vue_serve.xml"),
|
||||
]
|
||||
for file_name in file_names:
|
||||
os.remove(file_name)
|
||||
|
||||
|
||||
def remove_dottravisyml_file():
|
||||
os.remove(".travis.yml")
|
||||
|
||||
|
@ -397,10 +440,26 @@ def main():
|
|||
|
||||
if "{{ cookiecutter.use_drf }}".lower() == "n":
|
||||
remove_drf_starter_files()
|
||||
remove_vue_drf_files()
|
||||
|
||||
if "{{ cookiecutter.use_async }}".lower() == "n":
|
||||
remove_async_files()
|
||||
|
||||
if "{{ cookiecutter.use_fruit_demo }}".lower() == "n":
|
||||
remove_fruit_files()
|
||||
|
||||
if "{{ cookiecutter.use_vuex }}".lower() == "n":
|
||||
remove_vuex_files()
|
||||
|
||||
if "{{ cookiecutter.use_vue }}".lower() == "n":
|
||||
remove_vue_files()
|
||||
|
||||
if (
|
||||
"{{ cookiecutter.use_vue }}".lower() == "n"
|
||||
and "{{cookiecutter.use_pycharm }}".lower() == "y"
|
||||
):
|
||||
remove_vue_pycharm_files()
|
||||
|
||||
print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)
|
||||
|
||||
|
||||
|
|
|
@ -80,3 +80,14 @@ if (
|
|||
"You should either use AWS or select a different Mail Service for sending emails."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if "{{ cookiecutter.use_vue }}".lower() == "n" and "{{ cookiecutter.use_vuex }}" == "y":
|
||||
print("Use of Vuex requires use of Vue.")
|
||||
sys.exit(1)
|
||||
|
||||
if (
|
||||
"{{ cookiecutter.use_vuex }}".lower() == "n"
|
||||
or "{{ cookiecutter.use_vue }}".lower() == "n"
|
||||
) and "{{ cookiecutter.use_fruit_demo }}" == "y":
|
||||
print("The fruit demo app requires use of Vue, Vuex, and DRF.")
|
||||
sys.exit(1)
|
||||
|
|
|
@ -77,6 +77,9 @@ SUPPORTED_COMBINATIONS = [
|
|||
{"use_async": "n"},
|
||||
{"use_drf": "y"},
|
||||
{"use_drf": "n"},
|
||||
{"use_vue": "y", "use_vuex": "y", "use_fruit_demo": "y"},
|
||||
{"use_vue": "y", "use_vuex": "y", "use_fruit_demo": "n"},
|
||||
{"use_vue": "y", "use_vuex": "n", "use_fruit_demo": "n"},
|
||||
{"js_task_runner": "None"},
|
||||
{"js_task_runner": "Gulp"},
|
||||
{"custom_bootstrap_compilation": "y"},
|
||||
|
@ -106,6 +109,8 @@ UNSUPPORTED_COMBINATIONS = [
|
|||
{"cloud_provider": "None", "use_whitenoise": "n"},
|
||||
{"cloud_provider": "GCP", "mail_service": "Amazon SES"},
|
||||
{"cloud_provider": "None", "mail_service": "Amazon SES"},
|
||||
{"use_vue": "n", "use_vuex": "y", "use_fruit_demo": "y"},
|
||||
{"use_vuex": "n", "use_fruit_demo": "y"},
|
||||
]
|
||||
|
||||
|
||||
|
|
6
{{cookiecutter.project_slug}}/.gitignore
vendored
6
{{cookiecutter.project_slug}}/.gitignore
vendored
|
@ -53,6 +53,12 @@ coverage.xml
|
|||
# Django stuff:
|
||||
staticfiles/
|
||||
|
||||
{% if cookiecutter.use_vue == 'y' -%}
|
||||
# Vue stuff:
|
||||
!/{{ cookiecutter.project_slug }}/static/vue/
|
||||
|
||||
{% endif -%}
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Vue build" type="js.build_tools.npm">
|
||||
<package-json value="$PROJECT_DIR$/vue_frontend/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="build" />
|
||||
</scripts>
|
||||
<node-interpreter value="/usr/bin/node" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,12 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Vue serve" type="js.build_tools.npm">
|
||||
<package-json value="$PROJECT_DIR$/vue_frontend/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="serve" />
|
||||
</scripts>
|
||||
<node-interpreter value="/usr/bin/node" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
|
@ -170,4 +170,27 @@ Bootstrap's javascript as well as its dependencies is concatenated into a single
|
|||
.. _in the bootstrap source: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss
|
||||
.. _Bootstrap docs: https://getbootstrap.com/docs/4.1/getting-started/theming/
|
||||
|
||||
{% endif %}
|
||||
{% if cookiecutter.use_vue == "y" %}
|
||||
Vue
|
||||
^^^
|
||||
|
||||
This app integrates with a Vue multi-page app (MPA) located in ``vue_frontend``.
|
||||
|
||||
To initialize the frontend, from the ``vue_frontend`` directory, run::
|
||||
|
||||
$ npm install
|
||||
|
||||
To serve the Vue frontend in hot-reloading development mode::
|
||||
|
||||
$ npm run serve
|
||||
|
||||
And to build for deployment::
|
||||
|
||||
|
||||
$ npm run serve
|
||||
|
||||
For more information, see ``vue_frontend/README.md``.
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
from django.conf import settings
|
||||
from rest_framework.routers import DefaultRouter, SimpleRouter
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet
|
||||
from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet # isort:skip
|
||||
{% if cookiecutter.use_fruit_demo == "y" -%}
|
||||
from fruit.api.views import FruitViewSet # isort:skip
|
||||
|
||||
{% endif -%}
|
||||
|
||||
if settings.DEBUG:
|
||||
router = DefaultRouter()
|
||||
|
@ -9,7 +13,9 @@ else:
|
|||
router = SimpleRouter()
|
||||
|
||||
router.register("users", UserViewSet)
|
||||
|
||||
{% if cookiecutter.use_fruit_demo == "y" -%}
|
||||
router.register("fruits", FruitViewSet)
|
||||
{%- endif %}
|
||||
|
||||
app_name = "api"
|
||||
urlpatterns = router.urls
|
||||
|
|
|
@ -81,10 +81,19 @@ THIRD_PARTY_APPS = [
|
|||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
{%- endif %}
|
||||
{%- if cookiecutter.use_vue == "y" %}
|
||||
"webpack_loader",
|
||||
{%- endif %}
|
||||
]
|
||||
|
||||
LOCAL_APPS = [
|
||||
"{{ cookiecutter.project_slug }}.users.apps.UsersConfig",
|
||||
{%- if cookiecutter.use_vue == "y" %}
|
||||
"{{ cookiecutter.project_slug }}.webpack_bundle.apps.WebpackBundleConfig",
|
||||
{%- endif %}
|
||||
{%- if cookiecutter.use_fruit_demo == "y" %}
|
||||
"fruit.apps.FruitConfig",
|
||||
{%- endif %}
|
||||
# Your stuff: custom apps go here
|
||||
]
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||
|
@ -322,5 +331,22 @@ REST_FRAMEWORK = {
|
|||
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
|
||||
}
|
||||
{%- endif %}
|
||||
{% if cookiecutter.use_vue == "y" -%}
|
||||
|
||||
# Vue
|
||||
# -------------------------------------------------------------------------------
|
||||
VUE_FRONTEND_DIR = Path(ROOT_DIR, "vue_frontend")
|
||||
WEBPACK_LOADER = {
|
||||
"DEFAULT": {
|
||||
"CACHE": not DEBUG,
|
||||
"BUNDLE_DIR_NAME": "vue/", # must end with slash
|
||||
"STATS_FILE": Path(VUE_FRONTEND_DIR, "webpack-stats.json"),
|
||||
"POLL_INTERVAL": 0.1,
|
||||
"TIMEOUT": None,
|
||||
"IGNORE": [r".+\.hot-update.js", r".+\.map"],
|
||||
}
|
||||
}
|
||||
|
||||
{% endif -%}
|
||||
# Your stuff...
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -16,6 +16,10 @@ urlpatterns = [
|
|||
path(
|
||||
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
|
||||
),
|
||||
{%- if cookiecutter.use_fruit_demo == 'y' %}
|
||||
# Fruit demo
|
||||
path("fruits/", include("fruit.urls", namespace="fruits")),
|
||||
{%- endif %}
|
||||
# Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %}
|
||||
path(settings.ADMIN_URL, admin.site.urls),
|
||||
# User management
|
||||
|
|
0
{{cookiecutter.project_slug}}/fruit/__init__.py
Normal file
0
{{cookiecutter.project_slug}}/fruit/__init__.py
Normal file
8
{{cookiecutter.project_slug}}/fruit/admin.py
Normal file
8
{{cookiecutter.project_slug}}/fruit/admin.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django.contrib import admin
|
||||
from fruit.models import Fruit
|
||||
|
||||
|
||||
@admin.register(Fruit)
|
||||
class FruitAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "description"]
|
||||
search_fields = ["name"]
|
0
{{cookiecutter.project_slug}}/fruit/api/__init__.py
Normal file
0
{{cookiecutter.project_slug}}/fruit/api/__init__.py
Normal file
14
{{cookiecutter.project_slug}}/fruit/api/serializers.py
Normal file
14
{{cookiecutter.project_slug}}/fruit/api/serializers.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from fruit.models import Fruit
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class FruitDefaultSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = Fruit
|
||||
fields = ["id", "name", "description"]
|
||||
|
||||
|
||||
class FruitDetailSerializer(FruitDefaultSerializer):
|
||||
class Meta:
|
||||
model = FruitDefaultSerializer.Meta.model
|
||||
fields = FruitDefaultSerializer.Meta.fields + ["detail", "detail_link"]
|
20
{{cookiecutter.project_slug}}/fruit/api/views.py
Normal file
20
{{cookiecutter.project_slug}}/fruit/api/views.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from fruit.api.serializers import FruitDefaultSerializer, FruitDetailSerializer
|
||||
from fruit.models import Fruit
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import SAFE_METHODS, BasePermission, IsAuthenticated
|
||||
|
||||
|
||||
class ReadOnly(BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
return request.method in SAFE_METHODS
|
||||
|
||||
|
||||
class FruitViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
permission_classes = [IsAuthenticated | ReadOnly]
|
||||
queryset = Fruit.objects.all()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "retrieve":
|
||||
return FruitDetailSerializer
|
||||
else:
|
||||
return FruitDefaultSerializer
|
5
{{cookiecutter.project_slug}}/fruit/apps.py
Normal file
5
{{cookiecutter.project_slug}}/fruit/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FruitConfig(AppConfig):
|
||||
name = "fruit"
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 3.0.7 on 2020-07-13 08:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Fruit',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('description', models.CharField(max_length=1024)),
|
||||
('detail', models.TextField()),
|
||||
('detail_link', models.URLField(max_length=512)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,39 @@
|
|||
from django.db import migrations
|
||||
|
||||
def add_fruits(apps, schema_editor):
|
||||
Fruit = apps.get_model('fruit', 'Fruit')
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
Fruit.objects.using(db_alias).create(
|
||||
name="Apple", description="Round, usually red or green",
|
||||
detail_link="https://en.wikipedia.org/wiki/Apple",
|
||||
detail="An apple is an edible fruit produced by an apple tree (Malus domestica). Apple trees are cultivated worldwide and are the most widely grown species in the genus Malus. The tree originated in Central Asia, where its wild ancestor, Malus sieversii, is still found today. Apples have been grown for thousands of years in Asia and Europe and were brought to North America by European colonists. Apples have religious and mythological significance in many cultures, including Norse, Greek and European Christian tradition.\n\nSource: Wikipedia article \"Apple\", released under the Creative Commons Attribution-Share-Alike License 3.0.")
|
||||
|
||||
Fruit.objects.using(db_alias).create(
|
||||
name="Pear", description="Tapered top, light green",
|
||||
detail_link="https://en.wikipedia.org/wiki/Pear",
|
||||
detail="The pear tree and shrub are a species of genus Pyrus, in the family Rosaceae, bearing the pomaceous fruit of the same name. Several species of pear are valued for their edible fruit and juices while others are cultivated as trees. The tree is medium-sized and native to coastal as well as mildly temperate regions of Europe, north Africa and Asia. Pear wood is one of the preferred materials in the manufacture of high-quality woodwind instruments and furniture.\n\nSource: Wikipedia article \"Pear\", released under the Creative Commons Attribution-Share-Alike License 3.0.")
|
||||
|
||||
Fruit.objects.using(db_alias).create(
|
||||
name="Strawberry", description="Bright red, soft, covered in tiny seeds",
|
||||
detail_link="https://en.wikipedia.org/wiki/Strawberry",
|
||||
detail="The garden strawberry (or simply strawberry) is a widely grown hybrid species of the genus Fragaria, collectively known as the strawberries, which are cultivated worldwide for their fruit. The fruit is widely appreciated for its characteristic aroma, bright red color, juicy texture, and sweetness. It is consumed in large quantities, either fresh or in such prepared foods as jam, juice, pies, ice cream, milkshakes, and chocolates. Artificial strawberry flavorings and aromas are also widely used in products such as candy, soap, lip gloss, perfume, and many others.\n\nSource: Wikipedia article \"Strawberry\", released under the Creative Commons Attribution-Share-Alike License 3.0.")
|
||||
|
||||
def remove_fruits(apps, schema_editor):
|
||||
Fruit = apps.get_model('fruit', 'Fruit')
|
||||
db_alias = schema_editor.connection.alias
|
||||
Fruit.objects.filter(name='Apple').using(db_alias).delete()
|
||||
Fruit.objects.filter(name='Pear').using(db_alias).delete()
|
||||
Fruit.objects.filter(name='Strawberry').using(db_alias).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fruit', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_fruits, remove_fruits),
|
||||
|
||||
]
|
8
{{cookiecutter.project_slug}}/fruit/models.py
Normal file
8
{{cookiecutter.project_slug}}/fruit/models.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class Fruit(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
description = models.CharField(max_length=1024)
|
||||
detail = models.TextField()
|
||||
detail_link = models.URLField(max_length=512)
|
|
@ -0,0 +1,23 @@
|
|||
{% raw %}{% extends "base.html" %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-around mt-5">
|
||||
<div class="col-9 text-center">
|
||||
<div id="app">
|
||||
<counter msg="{{ counter_message }}"></counter>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9 text-center">
|
||||
<div id="counter_banner">
|
||||
<counter-banner></counter-banner>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% render_bundle 'chunk-common' %}
|
||||
{% render_bundle 'chunk-state' %}
|
||||
{% render_bundle 'fruit-counter' %}
|
||||
|
||||
{% endblock content %}{% endraw %}
|
|
@ -0,0 +1,36 @@
|
|||
{% raw %}{% extends "base.html" %}
|
||||
{% load lazy_render_bundle from webpack_bundle %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Directory of Fruits</h2>
|
||||
<p>The directory contains {{ object_list.count }} fruit{{ object_list.count|pluralize }}.</p>
|
||||
{% if object_list %}
|
||||
<ul>
|
||||
{% for fruit in object_list %}
|
||||
<li>{{ fruit.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div class="row justify-content-around mt-5">
|
||||
<div id="button_loader" class="col-9 text-center">
|
||||
<input class="btn btn-primary" id="z_button_loader" type="button" value="Load Fruit Inspector">
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<div id="app">
|
||||
<fruit-inspector title="{{ title }}"></fruit-inspector>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let loaderBtn = document.getElementById('button_loader');
|
||||
loaderBtn.addEventListener('click', function (e) {
|
||||
loaderBtn.style.display = "none";
|
||||
{% lazy_render_bundle 'chunk-common' %}
|
||||
{% lazy_render_bundle 'chunk-state' %}
|
||||
{% lazy_render_bundle 'fruit-list' %}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}{% endraw %}
|
1
{{cookiecutter.project_slug}}/fruit/tests.py
Normal file
1
{{cookiecutter.project_slug}}/fruit/tests.py
Normal file
|
@ -0,0 +1 @@
|
|||
# Create your tests here.
|
25
{{cookiecutter.project_slug}}/fruit/urls.py
Normal file
25
{{cookiecutter.project_slug}}/fruit/urls.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from django.urls import path
|
||||
from django.views.generic import TemplateView
|
||||
{%- if cookiecutter.use_drf == "y" %}
|
||||
from fruit.views import FruitList
|
||||
{%- endif %}
|
||||
|
||||
app_name = "fruit"
|
||||
|
||||
urlpatterns = [
|
||||
{%- if cookiecutter.use_drf == "y" %}
|
||||
path(
|
||||
"fruits/",
|
||||
FruitList.as_view(extra_context={"title": "Fruit Inspector"}),
|
||||
name="list",
|
||||
),
|
||||
{% endif -%}
|
||||
path(
|
||||
"fruit-counter/",
|
||||
TemplateView.as_view(
|
||||
template_name="fruit/counter.html",
|
||||
extra_context={"counter_message": "How many fruits could you eat?"},
|
||||
),
|
||||
name="counter",
|
||||
),
|
||||
]
|
6
{{cookiecutter.project_slug}}/fruit/views.py
Normal file
6
{{cookiecutter.project_slug}}/fruit/views.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.views.generic import ListView
|
||||
from fruit.models import Fruit
|
||||
|
||||
|
||||
class FruitList(ListView):
|
||||
model = Fruit
|
|
@ -42,3 +42,6 @@ django-redis==4.12.1 # https://github.com/jazzband/django-redis
|
|||
# Django REST Framework
|
||||
djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework
|
||||
{%- endif %}
|
||||
{%- if cookiecutter.use_vue == "y" %}
|
||||
django-webpack-loader==0.6.0 # https://github.com/owais/django-webpack-loader
|
||||
{%- endif %}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
3
{{cookiecutter.project_slug}}/vue_frontend/.env
Normal file
3
{{cookiecutter.project_slug}}/vue_frontend/.env
Normal file
|
@ -0,0 +1,3 @@
|
|||
VUE_APP_STATIC_ROOT=http://localhost:8000/static
|
||||
VUE_APP_API_ROOT=http://localhost:8000/api
|
||||
|
17
{{cookiecutter.project_slug}}/vue_frontend/.eslintrc.js
Normal file
17
{{cookiecutter.project_slug}}/vue_frontend/.eslintrc.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'eslint:recommended'
|
||||
],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
||||
}
|
||||
}
|
23
{{cookiecutter.project_slug}}/vue_frontend/.gitignore
vendored
Normal file
23
{{cookiecutter.project_slug}}/vue_frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
!.env
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
24
{{cookiecutter.project_slug}}/vue_frontend/README.md
Normal file
24
{{cookiecutter.project_slug}}/vue_frontend/README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# vue_frontend
|
||||
|
||||
## Project setup
|
||||
`yarn install` or `npm install`
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
`yarn serve` or `npm run serve`
|
||||
|
||||
{% if cookiecutter.use_pycharm == 'y' -%}
|
||||
Or use the PyCharm run configuration *Vue serve*.
|
||||
{%- endif %}
|
||||
|
||||
### Compiles and minifies for production
|
||||
`yarn build` or `npm run build`
|
||||
|
||||
{% if cookiecutter.use_pycharm == 'y' -%}
|
||||
Or use the PyCharm run configuration *Vue build*.
|
||||
{%- endif %}
|
||||
|
||||
### Lints and fixes files
|
||||
`yarn lint` or `npm run lint`
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
31
{{cookiecutter.project_slug}}/vue_frontend/package.json
Normal file
31
{{cookiecutter.project_slug}}/vue_frontend/package.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "vue_frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.2",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11"{% if cookiecutter.use_vuex == 'y' -%},
|
||||
"vuex": "^3.4.0",
|
||||
"vuex-persistedstate": "^3.0.1"
|
||||
{%- endif %}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.4.0",
|
||||
"@vue/cli-plugin-eslint": "~4.4.0",
|
||||
"@vue/cli-plugin-vuex": "~4.4.0",
|
||||
"@vue/cli-service": "~4.4.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"node-sass": "^4.12.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack-bundle-tracker": "^0.4.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1>{% raw %}{{ msg }}{% endraw %}</h1>
|
||||
<br/>
|
||||
|
||||
<div class="adjustor d-inline-block">
|
||||
<counter-adjustor mode="dec"></counter-adjustor>
|
||||
</div>
|
||||
<div class="display d-inline-block">
|
||||
<counter-display></counter-display>
|
||||
</div>
|
||||
<div class="adjustor d-inline-block">
|
||||
<counter-adjustor mode="inc"></counter-adjustor>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CounterAdjustor from "../components/CounterAdjustor";
|
||||
import CounterDisplay from "../components/CounterDisplay";
|
||||
|
||||
export default {
|
||||
name: 'Counter',
|
||||
components: {CounterAdjustor, CounterDisplay},
|
||||
props: {
|
||||
msg: String
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' -%}
|
||||
/* We may import partials from our source to use variables, mixins, etc */
|
||||
@import '../../../../{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars';
|
||||
|
||||
{% endif -%}
|
||||
h1 {
|
||||
margin: 40px 0 0;
|
||||
{%- if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||
color: $vue-green;
|
||||
{% else %}
|
||||
color: #42b883;
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
.adjustor {
|
||||
height: 5rem;
|
||||
padding-left:2rem;
|
||||
padding-right:2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
<!-- Because project css is loaded on our base tempalte, we can use those styles -->
|
||||
<div class="btn-group" role="group">
|
||||
<button v-if="this.showDecrement()" class="btn btn-outline-primary" @click="decrement">-</button>
|
||||
<button v-if="this.showIncrement()" class="btn btn-outline-primary" @click="increment">+</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ACTION_DECREMENT_COUNTER, ACTION_INCREMENT_COUNTER} from "../store/module_fruit";
|
||||
|
||||
export default {
|
||||
name: 'CounterAdjustor',
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'all'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showDecrement() {
|
||||
return this.mode === 'dec' || this.mode === 'all'
|
||||
},
|
||||
showIncrement() {
|
||||
return this.mode === 'inc' || this.mode === 'all'
|
||||
},
|
||||
increment() {
|
||||
this.$store.dispatch(ACTION_INCREMENT_COUNTER)
|
||||
},
|
||||
decrement() {
|
||||
this.$store.dispatch(ACTION_DECREMENT_COUNTER)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
button {
|
||||
font-size: 2rem;
|
||||
line-height: 2rem;
|
||||
font-weight: bold;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
color: #5a5a5a;
|
||||
border-color: #5a5a5a;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div>
|
||||
<p class="">{% raw %}{{ message }}{% endraw %}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CounterBanner',
|
||||
props: { },
|
||||
computed: {
|
||||
count() { return this.$store.state.fruit.count },
|
||||
message() {
|
||||
if (this.count <= 3) {
|
||||
return "You like fruit, don't you?";
|
||||
} else if (this.count <= 6) {
|
||||
return "Just right.";
|
||||
} else if (this.count <= 12) {
|
||||
return "You must be hungry!";
|
||||
} else {
|
||||
return "You must be very hungry!";
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
p {
|
||||
font-style: italic;
|
||||
font-size: 2rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Because our project css is loaded from our Django base template, we may use those inherited styles -->
|
||||
<div id="display" class="display-4 rounded-circle">{% raw %}{{ count }}{% endraw %}</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CounterDisplay',
|
||||
props: {
|
||||
msg: String
|
||||
},
|
||||
computed: {
|
||||
count() { return this.$store.state.fruit.count }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' -%}
|
||||
/* We may import partials from our source to use variables, mixins, etc */
|
||||
@import '../../../../{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars';
|
||||
|
||||
{% endif -%}
|
||||
#display {
|
||||
{%- if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||
background-color: $vue-green;
|
||||
{% else %}
|
||||
background-color: #42b883;
|
||||
{%- endif %}
|
||||
color: white;
|
||||
font-size: 4rem;
|
||||
font-weight: bold;
|
||||
line-height: 6rem;
|
||||
height: 6rem;
|
||||
width: 6rem;
|
||||
text-align: center;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="card bg-light mb-3">
|
||||
|
||||
<div class="card-header"><h3>{% raw %}{{ title }}{% endraw %}</h3></div>
|
||||
<div class="card-body">
|
||||
|
||||
<form>
|
||||
<div class="form-group row">
|
||||
<label for="fruitChoice" class="col-sm-3 col-form-label font-weight-bold">Fruit</label>
|
||||
<div class="col-sm-9">
|
||||
<select id="fruitChoice"
|
||||
class="form-control"
|
||||
@change="setActiveFruit"
|
||||
:value="activeFruit ? activeFruit.id: -1"
|
||||
>
|
||||
<option v-if="activeFruit == null" :value="-1">Select a fruit</option>
|
||||
<option
|
||||
v-for="f in fruits"
|
||||
:key="f.id"
|
||||
:selected="activeFruit != null ? f.id === activeFruit.id: false"
|
||||
:value="f.id"
|
||||
>
|
||||
{% raw %}{{ f.name }}{% endraw %}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row" v-if="activeFruit">
|
||||
<label for="fruitDescription" class="col-sm-3 col-form-label font-weight-bold">Description</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" readonly class="form-control-plaintext" id="fruitDescription"
|
||||
:value="activeFruit.description"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row" v-if="activeFruit">
|
||||
<label for="fruitDetail" class="col-sm-3 col-form-label font-weight-bold">Detail</label>
|
||||
<div class="col-sm-9">
|
||||
<textarea type="text" readonly class="form-control-plaintext"
|
||||
rows=8
|
||||
id="fruitDetail"
|
||||
:value="activeFruit.detail"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row" v-if="activeFruit">
|
||||
<label for="fruitLink" class="col-sm-3 col-form-label font-weight-bold">Detail Source Link</label>
|
||||
<div class="col-sm-9">
|
||||
<a id="fruitLink" :href="activeFruit.detail_link"
|
||||
target="_blank">{% raw %}{{ activeFruit.detail_link }}{% endraw %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {ACTION_GET_FRUITS, ACTION_SET_ACTIVE_FRUIT} from "../store/module_fruit";
|
||||
|
||||
export default {
|
||||
name: 'FruitInspector',
|
||||
components: {},
|
||||
mounted() {
|
||||
this.$store.dispatch(ACTION_GET_FRUITS);
|
||||
|
||||
},
|
||||
props: {
|
||||
title: String,
|
||||
},
|
||||
computed: {
|
||||
fruits() {
|
||||
return this.$store.state.fruit.fruits
|
||||
},
|
||||
activeFruit() {
|
||||
// TODO: should check if active fruit is in list of fruits
|
||||
return this.$store.state.fruit.activeFruit
|
||||
},
|
||||
|
||||
},
|
||||
methods: {
|
||||
setActiveFruit(e) {
|
||||
this.$store.dispatch(ACTION_SET_ACTIVE_FRUIT, e.target.value);
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' -%}
|
||||
/* We may import partials from our source to use variables, mixins, etc */
|
||||
@import '../../../../{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars';
|
||||
|
||||
{% endif -%}
|
||||
.card-header {
|
||||
{%- if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||
background-color: $vue-green;
|
||||
{% else %}
|
||||
background-color: #42b883;
|
||||
{%- endif %}
|
||||
color: white;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,34 @@
|
|||
import Vue from "vue/dist/vue.js";
|
||||
import storePlugin from "../../../../vue_frontend/src/store/vuex_store_as_plugin";
|
||||
import createPersistedState from "vuex-persistedstate";
|
||||
import FruitModule from "../store/module_fruit"
|
||||
const Counter = () => import( /* webpackChunkName: "chunk-counter" */ "../components/Counter.vue");
|
||||
const CounterBanner = () => import( /* webpackChunkName: "chunk-counter-banner" */ "../components/CounterBanner.vue");
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// Vuex state will be used in this entry point
|
||||
Vue.use(storePlugin);
|
||||
|
||||
// Include Vuex modules as needed for this entry point
|
||||
Vue.prototype.$store.registerModule('fruit', FruitModule);
|
||||
|
||||
// Designate what state should persist across page loads
|
||||
createPersistedState({
|
||||
paths: [
|
||||
"fruit.count",
|
||||
"fruit.activeFruit",
|
||||
]
|
||||
}
|
||||
)(Vue.prototype.$store);
|
||||
|
||||
// Mount top level components
|
||||
new Vue({
|
||||
el: "#app",
|
||||
components: {Counter}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
el: "#counter_banner",
|
||||
components: {CounterBanner}
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
import Vue from "vue/dist/vue.js";
|
||||
import storePlugin from "../../store/vuex_store_as_plugin";
|
||||
import createPersistedState from "vuex-persistedstate";
|
||||
import FruitModule from "../store/module_fruit"
|
||||
const FruitInspector = () => import( /* webpackChunkName: "chunk-fruit-inspector" */ "../components/FruitInspector.vue");
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// Vuex state will be used in this entry point
|
||||
Vue.use(storePlugin);
|
||||
|
||||
// Include Vuex modules as needed for this entry point
|
||||
Vue.prototype.$store.registerModule('fruit', FruitModule);
|
||||
|
||||
|
||||
// Designate what state should persist across page loads
|
||||
createPersistedState({
|
||||
paths: [
|
||||
"fruit.count",
|
||||
"fruit.activeFruit",
|
||||
]
|
||||
}
|
||||
)(Vue.prototype.$store);
|
||||
|
||||
// Mount top level components
|
||||
new Vue({
|
||||
el: "#app",
|
||||
components: {FruitInspector}
|
||||
});
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{%- if cookiecutter.use_drf == 'y' -%}
|
||||
import api from "../../rest/rest";
|
||||
|
||||
{%- endif -%}
|
||||
export const MAX_COUNT = 42
|
||||
export const MIN_COUNT = 0
|
||||
|
||||
{%- if cookiecutter.use_drf == 'y' %}
|
||||
export const ACTION_GET_FRUITS = 'ACT_GET_FRUITS'
|
||||
export const ACTION_SET_ACTIVE_FRUIT = 'ACT_SET_ACTIVE_FRUIT'
|
||||
{%- endif %}
|
||||
export const ACTION_INCREMENT_COUNTER = 'ACT_INC_COUNT'
|
||||
export const ACTION_DECREMENT_COUNTER = 'ACT_DEC_COUNT'
|
||||
|
||||
{%- if cookiecutter.use_drf == 'y' %}
|
||||
const MUTATION_SET_FRUITS = 'MUT_SET_FRUITS'
|
||||
const MUTATION_SET_ACTIVE_FRUIT = 'MUT_SET_ACTIVE_FRUIT'
|
||||
{%- endif %}
|
||||
const MUTATION_INCREMENT_COUNTER = 'MUT_INC_COUNT'
|
||||
const MUTATION_DECREMENT_COUNTER = 'MUT_DEC_COUNT'
|
||||
|
||||
export default {
|
||||
state: {
|
||||
count: 0,
|
||||
{%- if cookiecutter.use_drf == 'y' %}
|
||||
fruits: [],
|
||||
activeFruit: null,
|
||||
{%- endif %}
|
||||
},
|
||||
mutations: {
|
||||
{%- if cookiecutter.use_drf == 'y' %}
|
||||
[MUTATION_SET_FRUITS](state, fruitList) {
|
||||
state.fruits = fruitList;
|
||||
},
|
||||
[MUTATION_SET_ACTIVE_FRUIT](state, fruit) {
|
||||
state.activeFruit = fruit;
|
||||
},
|
||||
{%- endif %}
|
||||
[MUTATION_INCREMENT_COUNTER]: state => state.count++,
|
||||
[MUTATION_DECREMENT_COUNTER]: state => state.count--
|
||||
},
|
||||
actions: {
|
||||
{%- if cookiecutter.use_drf == 'y' %}
|
||||
[ACTION_GET_FRUITS](context) {
|
||||
api.getFruits()
|
||||
.then(function (response) {
|
||||
context.commit(MUTATION_SET_FRUITS, response.data);
|
||||
})
|
||||
.catch(function (error) {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
[ACTION_SET_ACTIVE_FRUIT](context, fruitId) {
|
||||
api.getFruit(fruitId)
|
||||
.then(function (response) {
|
||||
context.commit(MUTATION_SET_ACTIVE_FRUIT, response.data);
|
||||
})
|
||||
.catch(function (error) {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
{%- endif %}
|
||||
[ACTION_INCREMENT_COUNTER](context) {
|
||||
if (context.state.count < MAX_COUNT) {
|
||||
context.commit(MUTATION_INCREMENT_COUNTER);
|
||||
}
|
||||
},
|
||||
[ACTION_DECREMENT_COUNTER](context) {
|
||||
if (context.state.count > MIN_COUNT) {
|
||||
context.commit(MUTATION_DECREMENT_COUNTER);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
23
{{cookiecutter.project_slug}}/vue_frontend/src/rest/rest.js
Normal file
23
{{cookiecutter.project_slug}}/vue_frontend/src/rest/rest.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import axios from "axios";
|
||||
|
||||
// axios settings
|
||||
axios.defaults.baseURL = process.env.VUE_APP_API_ROOT;
|
||||
axios.defaults.xsrfHeaderName = "X-CSRFToken";
|
||||
axios.defaults.xsrfCookieName = 'csrftoken';
|
||||
|
||||
|
||||
const api = axios.create({});
|
||||
|
||||
|
||||
export default {
|
||||
{%- if cookiecutter.use_fruit_demo == "y" %}
|
||||
getFruits: function () {
|
||||
return api.get('/fruits/')
|
||||
},
|
||||
getFruit: function (id) {
|
||||
return api.get('/fruits/' + id + '/')
|
||||
}
|
||||
{%- endif %}
|
||||
/* Include additional API calls here */
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import Vue from "vue/dist/vue.js";
|
||||
import Vuex from "vuex";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
let store = new Vuex.Store({
|
||||
modules: {
|
||||
},
|
||||
strict: process.env.NODE_ENV !== "production",
|
||||
});
|
||||
|
||||
export default {
|
||||
store,
|
||||
install(Vue) { //resetting the default store to use this plugin store
|
||||
Vue.prototype.$store = store;
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
|
||||
<div id="app" class="text-center">
|
||||
<img class="logo" alt="Vue logo" src="../assets/logo.png">
|
||||
<span class="plus">+</span>
|
||||
<img class="logo" alt="Django logo" :src="`${staticRoot}/images/django_logo.png`">
|
||||
<div class="hello">
|
||||
<h1>{% raw %}{{ msg }}{% endraw %}</h1>
|
||||
<p>
|
||||
This cookiecutter implements the techniques discussed in the series of articles "Vue + Django: The Best of Both Frontends."
|
||||
</p>
|
||||
<ul class="articles">
|
||||
<li><a href="https://medium.com/js-dojo/vue-django-best-of-both-frontends-701307871478" target="_blank" rel="noopener">Part 1: Basics</a></li>
|
||||
<li><a href="https://medium.com/js-dojo/django-vue-vuex-best-of-both-frontends-part-2-1dcb78215575" target="_blank" rel="noopener">Part 2: Persistent State</a></li>
|
||||
<li><a href="https://medium.com/js-dojo/django-vue-blazing-content-rich-interactivity-b34e45d8c602" target="_blank" rel="noopener">Part 3: Deferred Loading</a></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
In addition to the base options provided by the wonderful <a href="https://github.com/pydanny/cookiecutter-django">Cookiecutter Django</a>, this cookiecutter providees the following options:
|
||||
</p>
|
||||
<div class="container col-md-8 text-left">
|
||||
<ul class="options">
|
||||
|
||||
<li>
|
||||
<span class="opt">use_vue</span> -- <span class="expl">provides Vue integration using <a href="https://github.com/owais/django-webpack-loader">django-webpack-loader</a>, featuring:</span>
|
||||
<ul>
|
||||
<li>Harmonious coexistence of Django templates and Vue components</li>
|
||||
<li>Vue Single File Components (SFCs)</li>
|
||||
<li>Multi-page App (MPA) layout</li>
|
||||
<li>Vue Loader Hot Reload</li>
|
||||
<li>Property passing from Django -> Vue</li>
|
||||
<li>Sass/SCSS pre-compilation</li>
|
||||
<li>Vue DevTools support</li>
|
||||
<li>Chunked resource loading</li>
|
||||
<li>Deferred loading of Vue and/or Vue components</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span class="opt">use_vuex</span> -- <span class="expl">provides <a href="https://vuex.vuejs.org/">Vuex</a> integration, featuring:</span>
|
||||
<ul>
|
||||
<li>Shared state across components on the same page</li>
|
||||
<li>Persistent state across pages</li>
|
||||
<li>Rest support via <a href="https://github.com/axios/axios">Axios</a> -> <a href="https://www.django-rest-framework.org/">DRF</a> (if <i>use_drf</i> enabled)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<span class="opt">use_fruit_demo</span> -- <span class="expl">provides a sample app, demonstrating:</span>
|
||||
<ul>
|
||||
<li>All basic features from selected options above</li>
|
||||
<li>Deferred loading </li>
|
||||
<li>Static asset loading from Django or Vue</li>
|
||||
<li>If <i>use_drf</i> enabled, a sample API integration</li>
|
||||
<li>If <i>custom_bootstrap_compilation</i> enabled, SCSS partials import in SFCs</li>
|
||||
<li>Useless information about fruit</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
staticRoot: process.env.VUE_APP_STATIC_ROOT
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
h1 {
|
||||
margin: 40px 0 0;
|
||||
font-style: italic;
|
||||
}
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul.articles {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
.opt {
|
||||
font-weight: bold;
|
||||
}
|
||||
p {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
.logo {
|
||||
height: 150px;
|
||||
margin-top: 25px;
|
||||
}
|
||||
.plus {
|
||||
font-size: 44px;
|
||||
padding-left: 10px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,10 @@
|
|||
import Vue from "vue/dist/vue.js";
|
||||
const HelloWorld = () => import( /* webpackChunkName: "chunk-hello-world" */ "../components/HelloWorld.vue");
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// Mount top level components
|
||||
new Vue({
|
||||
el: "#app",
|
||||
components: {HelloWorld}
|
||||
});
|
88
{{cookiecutter.project_slug}}/vue_frontend/vue.config.js
Normal file
88
{{cookiecutter.project_slug}}/vue_frontend/vue.config.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
const BundleTracker = require("webpack-bundle-tracker");
|
||||
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
||||
|
||||
|
||||
const pages = {
|
||||
'main': {
|
||||
entry: './src/{{cookiecutter.project_slug}}/entry/main.js',
|
||||
chunks: ['chunk-common']
|
||||
},
|
||||
{%- if cookiecutter.use_fruit_demo == 'y' %}
|
||||
'fruit-counter': {
|
||||
entry: './src/fruit/entry/fruit_counter.js',
|
||||
chunks: ['chunk-common', 'chunk-state']
|
||||
},
|
||||
{%- endif %}{%- if cookiecutter.use_fruit_demo == 'y' and cookiecutter.use_drf == 'y' %}
|
||||
'fruit-list': {
|
||||
entry: './src/fruit/entry/fruit_list.js',
|
||||
chunks: ['chunk-common', 'chunk-state']
|
||||
},
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
pages: pages,
|
||||
filenameHashing: true,
|
||||
productionSourceMap: false,
|
||||
publicPath: process.env.NODE_ENV === 'production'
|
||||
? '/static/vue'
|
||||
: 'http://localhost:8080/',
|
||||
outputDir: '../{{cookiecutter.project_slug}}/static/vue/',
|
||||
|
||||
chainWebpack: config => {
|
||||
|
||||
config.optimization
|
||||
.splitChunks({
|
||||
cacheGroups: {
|
||||
state: {
|
||||
/* As vuex state is not needed in all our entry points, we isolate it
|
||||
* in a separate chunk to be loaded only where needed.
|
||||
*/
|
||||
test: /[\\/]node_modules[\\/](vuex|vuex-persisted-state)/,
|
||||
name: "chunk-state",
|
||||
chunks: "all",
|
||||
priority: 5
|
||||
},
|
||||
vendor: {
|
||||
/* This chunk contains modules that may be used in all entry points,
|
||||
* including Vue itself
|
||||
*/
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "chunk-common",
|
||||
chunks: "all",
|
||||
priority: 1
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Object.keys(pages).forEach(page => {
|
||||
config.plugins.delete(`html-${page}`);
|
||||
config.plugins.delete(`preload-${page}`);
|
||||
config.plugins.delete(`prefetch-${page}`);
|
||||
})
|
||||
|
||||
config
|
||||
.plugin('BundleTracker')
|
||||
.use(BundleTracker, [{
|
||||
path: '../vue_frontend/',
|
||||
filename: 'webpack-stats.json'
|
||||
}]);
|
||||
|
||||
// Uncomment below to analyze bundle sizes
|
||||
// config.plugin("BundleAnalyzerPlugin").use(BundleAnalyzerPlugin);
|
||||
|
||||
// config.resolve.alias
|
||||
// .set('__STATIC__', 'static')
|
||||
|
||||
config.devServer
|
||||
.public('http://localhost:8080')
|
||||
.host('localhost')
|
||||
.port(8080)
|
||||
.hotOnly(true)
|
||||
.watchOptions({poll: 1000})
|
||||
.https(false)
|
||||
.headers({"Access-Control-Allow-Origin": ["*"]})
|
||||
|
||||
}
|
||||
};
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
|
@ -1 +1,19 @@
|
|||
{%- if cookiecutter.use_vue == 'y' %}
|
||||
function load_bundle_file(url, filetype) {
|
||||
let fileref;
|
||||
if (filetype === "js") { // build js script tag
|
||||
fileref = document.createElement('script');
|
||||
fileref.setAttribute("type", "text/javascript");
|
||||
fileref.setAttribute("src", url);
|
||||
} else if (filetype === "css") { // build css link tag
|
||||
fileref = document.createElement("link");
|
||||
fileref.setAttribute("rel", "stylesheet");
|
||||
fileref.setAttribute("type", "text/css");
|
||||
fileref.setAttribute("href", url);
|
||||
}
|
||||
if (typeof fileref != "undefined")
|
||||
document.getElementsByTagName("head")[0].appendChild(fileref);
|
||||
}
|
||||
|
||||
{% endif -%}
|
||||
/* Project specific Javascript goes here. */
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{%- if cookiecutter.use_vue == 'y' -%}
|
||||
$vue-blue: #34495E;
|
||||
$vue-green: #41B883;
|
||||
{% endif -%}
|
|
@ -50,7 +50,13 @@
|
|||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'about' %}">About</a>
|
||||
</li>
|
||||
</li>{% endraw %}{% if cookiecutter.use_fruit_demo == "y" %}{% raw %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'fruit:counter' %}">Fruit Counter</a>
|
||||
</li>{% endraw %}{% endif %}{% if cookiecutter.use_fruit_demo == "y" and cookiecutter.use_drf == "y"%}{% raw %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'fruit:list' %}">Fruit List</a>
|
||||
</li>{% endraw %}{% endif %}{% raw %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
{# URL provided by django-allauth/account/urls.py #}
|
||||
|
|
|
@ -1 +1,14 @@
|
|||
{% raw %}{% extends "base.html" %}{% endraw %}
|
||||
{% raw %}{% extends "base.html" %}{% endraw %}{%- if cookiecutter.use_vue == 'y' %}{% raw %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="app">
|
||||
<hello-world msg="Hello from Django and Vue!"></hello-world>
|
||||
</div>
|
||||
|
||||
{% render_bundle 'chunk-common' %}
|
||||
{% render_bundle 'main' %}
|
||||
|
||||
{% endblock content %}{% endraw %}{% endif -%}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{% raw %}{% load get_files from webpack_loader %}
|
||||
|
||||
{% get_files bundle as bundle_files %}
|
||||
{% for f in bundle_files %}
|
||||
{% if f.url|default:""|slice:"-2:" == "js" %}
|
||||
load_bundle_file("{{ f.url }}", "js");
|
||||
{% else %}
|
||||
load_bundle_file("{{ f.url }}", "css");
|
||||
{% endif %}
|
||||
{% endfor %}{% endraw %}
|
|
@ -0,0 +1,7 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class WebpackBundleConfig(AppConfig):
|
||||
name = "{{ cookiecutter.project_slug }}.webpack_bundle"
|
||||
verbose_name = _("Webpack Bundle")
|
|
@ -0,0 +1,10 @@
|
|||
from typing import Dict
|
||||
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag("webpack_bundle/lazy_render_bundle.html", takes_context=False)
|
||||
def lazy_render_bundle(bundle: Dict[str, str]) -> Dict[str, Dict[str, str]]:
|
||||
return {"bundle": bundle}
|
Loading…
Reference in New Issue
Block a user