├── .coveragerc ├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── djvue ├── __init__.py ├── apps.py ├── defaults.py ├── fields.py ├── serializers.py ├── static │ └── vue │ │ └── js │ │ ├── axios.js │ │ ├── lodash.js │ │ ├── script.js │ │ ├── vee-validate.full.js │ │ └── vue.js ├── templates │ └── vue │ │ ├── default │ │ ├── checkbox.html │ │ ├── checkbox_multiple.html │ │ ├── dict_field.html │ │ ├── fieldset.html │ │ ├── file.html │ │ ├── file_multiple.html │ │ ├── form.html │ │ ├── input.html │ │ ├── list_field.html │ │ ├── list_fieldset.html │ │ ├── radio.html │ │ ├── select.html │ │ ├── select_multiple.html │ │ └── textarea.html │ │ └── starter.html ├── templatetags │ ├── __init__.py │ └── vue_tags.py ├── urls.py ├── validators.py ├── views.py └── vv_locale.py ├── example ├── README.md ├── __init__.py ├── example │ ├── __init__.py │ ├── urls.py │ └── wsgi.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_profileattachment.py │ └── __init__.py ├── models.py ├── requirements.txt ├── serializers.py ├── settings.py ├── static │ ├── css │ │ └── main.css │ └── js │ │ └── script.js ├── templates │ ├── base.html │ ├── login.html │ └── profile.html ├── urls.py └── views.py ├── manage.py ├── requirements_dev.txt ├── setup.cfg └── setup.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = true 3 | 4 | [report] 5 | omit = 6 | *site-packages* 7 | *tests* 8 | *.tox* 9 | show_missing = True 10 | exclude_lines = 11 | raise NotImplementedError 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,css,scss,json,yml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [Makefile] 23 | indent_style = tab 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * djvue version: 2 | * Django version: 3 | * Python version: 4 | * Operating System: 5 | 6 | ### Description 7 | 8 | Describe what you were trying to get done. 9 | Tell us what happened, what went wrong, and what you expected to happen. 10 | 11 | ### What I Did 12 | 13 | ``` 14 | Paste the command(s) you ran and the output. 15 | If there was a crash, please include the traceback here. 16 | ``` 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__ 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | htmlcov 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | # Pycharm/Intellij 40 | .idea 41 | 42 | # Complexity 43 | output/*.html 44 | output/*/index.html 45 | 46 | # Sphinx 47 | docs/_build 48 | 49 | media/* 50 | db.sqlite3 -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Dacian Popute 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/KuwaitNET/djvue/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | djvue could always use more documentation, whether as part of the 40 | official djvue docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/KuwaitNET/djvue/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `djvue` for local development. 59 | 60 | 1. Fork the `djvue` repo on GitHub. 61 | 2. Clone your fork locally:: 62 | 63 | $ git clone git@github.com:your_name_here/djvue.git 64 | 65 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 66 | 67 | $ mkvirtualenv djvue 68 | $ cd djvue/ 69 | $ python setup.py develop 70 | 71 | 4. Create a branch for local development:: 72 | 73 | $ git checkout -b name-of-your-bugfix-or-feature 74 | 75 | Now you can make your changes locally. 76 | 77 | 5. When you're done making changes, check that your changes pass flake8 and the 78 | tests, including testing other Python versions with tox:: 79 | 80 | $ flake8 djvue tests 81 | $ python setup.py test 82 | $ tox 83 | 84 | To get flake8 and tox, just pip install them into your virtualenv. 85 | 86 | 6. Commit your changes and push your branch to GitHub:: 87 | 88 | $ git add . 89 | $ git commit -m "Your detailed description of your changes." 90 | $ git push origin name-of-your-bugfix-or-feature 91 | 92 | 7. Submit a pull request through the GitHub website. 93 | 94 | Pull Request Guidelines 95 | ----------------------- 96 | 97 | Before you submit a pull request, check that it meets these guidelines: 98 | 99 | 1. The pull request should include tests. 100 | 2. If the pull request adds functionality, the docs should be updated. Put 101 | your new functionality into a function with a docstring, and add the 102 | feature to the list in README.rst. 103 | 3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check 104 | https://travis-ci.org/KuwaitNET/djvue/pull_requests 105 | and make sure that the tests pass for all supported Python versions. 106 | 107 | Tips 108 | ---- 109 | 110 | To run a subset of tests:: 111 | 112 | $ python -m unittest tests.test_djvue 113 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | ======= 4 | History 5 | ======= 6 | 7 | ****************** 8 | 0.2.1 (2020-11-07) 9 | ****************** 10 | 11 | * File upload now supports `multiple` via `ListFileField` 12 | * Ability to define different upload URL for each file input 13 | * Small bugs. 14 | 15 | ****************** 16 | 0.1.1 (2020-08-01) 17 | ****************** 18 | 19 | * Reactive files object 20 | * Add docs for render_vue_field 21 | * Small bugs. 22 | 23 | ****************** 24 | 0.1.0 (2020-07-21) 25 | ****************** 26 | 27 | * First release on PyPI. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2020, Dacian Popute 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | recursive-include djvue *.html *.png *.gif *js *.css *jpg *jpeg *svg *py 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | DjVue 3 | ===== 4 | 5 | *Handle Django forms with Vue.js and Django REST Framework* 6 | 7 | 8 | This project aims to help to build hybrid views that handle both templates renders as well as the REST API. Useful when the client doesn't have a SPA for the frontend but does need mobile apps or an API or developing an API in an existing project that is required. I suggest checking the ``example`` app for seeing a concrete implementation. 9 | 10 | The trade-off made is getting rid of `Django Forms `_ and replace them with `Django REST Framework form renderer. `_ 11 | 12 | ============ 13 | Requirements 14 | ============ 15 | 16 | * `Django `__ (2.2+) 17 | * `DRF `_ 18 | 19 | For the form validation and submission: 20 | 21 | * `Vue.js 2.6 `_ 22 | * `VeeValidate `_ 23 | 24 | 25 | ============ 26 | Installation 27 | ============ 28 | 29 | Install ``djvue`` (or `download from PyPI `__): 30 | 31 | .. code-block:: bash 32 | 33 | pip install djvue 34 | 35 | Add ``djvue`` to ``INSTALLED_APPS`` in ``settings.py``: 36 | 37 | .. code-block:: python 38 | 39 | INSTALLED_APPS = ( 40 | # other apps 41 | "djvue", 42 | ) 43 | 44 | Enable session authentication for DRF. 45 | 46 | .. warning:: 47 | Failing in doing this will make all your views csrf exempted. 48 | 49 | 50 | .. code-block:: python 51 | 52 | REST_FRAMEWORK = { 53 | # ... 54 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 55 | 'rest_framework.authentication.SessionAuthentication', 56 | # ... 57 | ), 58 | # ... 59 | } 60 | 61 | 62 | If there are files to be uploaded via forms, it's required to include ``djvue.urls`` in your root ``urls.py``: 63 | 64 | .. code-block:: python 65 | 66 | # For Django >= 2.2 67 | urlpatterns += [ 68 | path('', include('djvue.urls')) 69 | ] 70 | 71 | 72 | ============ 73 | How it works 74 | ============ 75 | 76 | *********************** 77 | Rendering a simple form 78 | *********************** 79 | 80 | 81 | `DjVue` allows you to easily render straightforward forms. 82 | 83 | Define the serializer. 84 | 85 | .. code-block:: python 86 | 87 | class LoginSerializer(serializers.Serializer): 88 | email = serializers.EmailField(required=True) 89 | password = serializers.CharField(write_only=True, style={"input_type": "password"}) 90 | 91 | 92 | Your views will require to return the serializer definition and the rendered template on the GET requests, exactly like Django CBVs are doing. 93 | 94 | .. code-block:: python 95 | 96 | from rest_framework.generics import CreateAPIView 97 | from rest_framework.renderers import TemplateHTMLRenderer, JSONRenderer 98 | from rest_framework.response import Response 99 | 100 | from .serializers import LoginSerializer 101 | 102 | 103 | class LoginView(CreateAPIView): 104 | renderer_classes = [TemplateHTMLRenderer, JSONRenderer] 105 | serializer_class = LoginSerializer 106 | template_name = "login.html" 107 | 108 | def get(self, request, *args, **kwargs): 109 | return Response({"serializer": self.get_serializer()}) 110 | 111 | 112 | Include the prerequisites at the bottom of ``base.html`` 113 | 114 | .. code-block:: HTML 115 | 116 | {% load vue_tags %} 117 | .... other scripts 118 | {% vue_starter %} 119 | 120 | 121 | ******* 122 | Vue app 123 | ******* 124 | 125 | 126 | Each form is another Vue instance. 127 | 128 | .. code-block:: HTML 129 | 130 | 133 | 134 | .. note:: 135 | Hide the div holding the Vue.js app to avoid showing to the user un-rendered HTML using the ``hidden`` HTML attribute. It can be removed once the DOM has loaded, placed usually at the end of the script. 136 | 137 | 138 | *************** 139 | Form definition 140 | *************** 141 | 142 | validation-observer 143 | ------------------- 144 | 145 | Define the form using VeeValidates's `ValidationObserver `_ component. 146 | 147 | render_vue_form 148 | --------------- 149 | 150 | Render the serializer as an HTML form using ``render_vue_form``. This template tag is a wrapper around the original DRF `render_form `_ template tag which changes the field style. 151 | 152 | 153 | .. code-block:: HTML 154 | 155 | 156 |
157 |
158 |
159 | {% render_vue_form serializer %} 160 |
161 |
162 | 163 |
164 |
165 | 166 | render_vue_field 167 | ---------------- 168 | 169 | For a more granular control ``render_vue_field`` template tag can be used. 170 | 171 | 172 | .. code-block:: HTML 173 | 174 | 175 |
176 |
177 |
178 | {% render_vue_field serializer.username %} 179 | {% render_vue_field serializer.password %} 180 |
181 |
182 | 183 |
184 |
185 | 186 | 187 | ********** 188 | djVueMixin 189 | ********** 190 | 191 | * Create a new Vue app and use djVueMixin which handles the form validation, file upload, and submission. 192 | * Define the form fields inside ``data`` method ``form`` object. Note that you need to define manually every form field that has to be passed to the server, excepting file fields, which will cover in another example later. 193 | 194 | * **Mandatory implementation input** 195 | * **actionURL**: defines where the form has to be sent via a POST request to the server. 196 | * **success**: method is called when the server returns a success response (status 200). 197 | 198 | .. code-block:: javascript 199 | 200 | 201 | new Vue({ 202 | el: '#login-app', 203 | mixins: [djVueMixin], 204 | data() { 205 | return { 206 | actionURL: '{% url "login" %}', 207 | form: { 208 | email: null, 209 | password: null 210 | } 211 | } 212 | }, 213 | methods: { 214 | success(response) { 215 | window.location.href = "{% url 'user:dashboard' %}" 216 | } 217 | } 218 | 219 | }) 220 | // remove hidden 221 | let appEl = document.getElementById('login-app'); 222 | appEl.removeAttribute("hidden"); 223 | 224 | 225 | 226 | ***************************** 227 | Display the validation errors 228 | ***************************** 229 | 230 | * At this step, live validation is setup. Each form field is validated individually in the partial HTML field. It can be customized by creating a new `template pack `_. Add a placeholder anywhere on the page for rendering forms global validation error like ``Server Error`` or better use a `toastr `_ or SnackBar. 231 | * Displaying server side field errors is implemented only for one nesting level, if you need more you should override ``error`` method from ``djVueMixin``. 232 | 233 | .. code-block:: HTML 234 | 235 |

{( error )}

236 | 237 | 238 | ************** 239 | Advanced usage 240 | ************** 241 | 242 | .. code-block:: python 243 | 244 | from djvue.fields import FileField 245 | 246 | class WorkSerializer(serializers.Serializer): 247 | CHOICES = ( 248 | ("cc", "Chocolate Tested"), 249 | ("dreamer", "Dreamer"), 250 | ("sp", "Smokes packing"), 251 | ) 252 | job = serializers.ChoiceField(choices=CHOICES) 253 | position = serializers.CharField(required=False) 254 | 255 | 256 | class ProfileSerializer(serializers.ModelSerializer): 257 | username = serializers.CharField(max_length=25, min_length=3, required=True,) 258 | email = serializers.EmailField(required=True) 259 | password1 = serializers.CharField( 260 | write_only=True, 261 | style={"input_type": "password", "rules": "password:@password2"}, 262 | ) 263 | password2 = serializers.CharField(write_only=True, style={"input_type": "password"}) 264 | file = FileField(required=True) 265 | working_place = WorkSerializer(write_only=True) 266 | 267 | class Meta: 268 | model = Profile 269 | fields = ( 270 | "username", 271 | "email", 272 | "password1", 273 | "password2", 274 | "file", 275 | "working_place", 276 | ) 277 | 278 | 279 | File upload 280 | ----------- 281 | 282 | * File upload starts as soon as ``onchange`` DOM event occurs. Behind the scene, a global view is uploading the file to a temporary location and returns to the client the ``path`` and the original ``filename`` which will be sent together with the form data upon submission. If you want to enforce special validation, DjVue batteries can be subclasses to create your custom logic. 283 | * To enable file upload, it's required to use DjVue's ``FileField`` instead of the default one. 284 | 285 | FileField 286 | ^^^^^^^^^ 287 | 288 | A hybrid file field. Renders an input type, accepts as input a dictionary containing the filename and the file path and it serializes the representation like a native serializer.FileField. 289 | 290 | ``serializers.py`` 291 | 292 | .. code-block:: python 293 | 294 | from django.core.validators import FileExtensionValidator 295 | 296 | from djvue.serializers import FileUploadSerializer 297 | 298 | 299 | class PDFUploadSerializer(FileUploadSerializer): 300 | """ 301 | Allows only PDF files to be uploaded 302 | """ 303 | def __init__(self, *args, **kwargs): 304 | super().__init__(*args, **kwargs) 305 | self.fields["file"].validators.append(FileExtensionValidator(allowed_extensions=['pdf'])) 306 | 307 | ``views.py`` 308 | 309 | .. code-block:: python 310 | 311 | from djvue.views import FileUploadView 312 | 313 | class PDFUploadView(FileUploadView): 314 | serializer_class = PDFUploadSerializer 315 | 316 | ``urls.py`` 317 | 318 | .. code-block:: python 319 | 320 | urlpatterns = [ 321 | path('', PDFUploadView.as_view(), name="pdf_upload") 322 | ] 323 | 324 | 325 | Once the backend is implemented, the Vue.js app is left to be updated and that's all. 326 | 327 | .. code-block:: javascript 328 | 329 | new Vue({ 330 | // ... 331 | uploadURL: "{% url 'pdf_upload' %}" 332 | // ... 333 | }) 334 | 335 | 336 | Upon form submission, the uploaded files must be linked with some model or pushed somewhere else. Let's see a trivial example of how that can be done, ``filename`` and ``path`` are always returned by the view using ``FileUploadSerializer`` and ``djVueMixin`` does the job of POSTing them to the ``actionURL`` together with the rest of the form fields. 337 | 338 | The current example considers one url for all files which belong to the same form, for handling different validations per file, each field can have it's own upload url by defining ``upload_url`` in field style. 339 | 340 | .. code-block:: python 341 | 342 | any_file = FileField() # uses the uploadURL defined in the Vue instance 343 | pdf = FileField(style={"upload_url": reverse_lazy("pdf_upload")}) 344 | image = FileField(style={"upload_url": reverse_lazy("image_upload")}) 345 | 346 | ``serializers.py`` 347 | 348 | .. code-block:: python 349 | 350 | class ProfileSerializer(serializers.ModelSerializer): 351 | def create(self, validated_data): 352 | user_file = validated_data.pop("file", None) 353 | profile = Profile.objects.create(**validated_data) 354 | # # fetch the file from temporary dir 355 | if user_file is not None and all( 356 | [user_file.get("path", False), user_file.get("filename", False)] 357 | ): 358 | with open(user_file["path"], "rb") as f: 359 | profile.file.save(user_file["filename"], f) 360 | return profile 361 | 362 | 363 | ListFileField 364 | ^^^^^^^^^^^^^ 365 | Implements `multiple `_ upload, a list of FileField, inheriting all its features. 366 | Once a file has been uploaded, the filename will be rendered under it. 367 | 368 | .. code-block:: python 369 | 370 | multiple_file = ListFileField() # uses the uploadURL defined in the Vue instance 371 | multiple_pdf = ListFileField(style={"upload_url": reverse_lazy("example:pdf_upload")}) 372 | 373 | ``serializers.py`` 374 | 375 | .. code-block:: python 376 | 377 | from django.core.files.base import ContentFile 378 | 379 | class ProfileSerializer(serializers.ModelSerializer): 380 | def create(self, validated_data): 381 | # ... 382 | multiple_file = validated_data.pop("multiple_file", None) 383 | profile = Profile(**validated_data) 384 | profile.save() 385 | # fetch the file from temporary dir 386 | if multiple_file is not None and all( 387 | [a_file.get("path", False) and a_file.get("filename", False) for a_file in multiple_file] 388 | ): 389 | for a_file in multiple_file: 390 | with open(a_file["path"], "rb") as f: 391 | ProfileAttachment.objects.create(profile=profile, file=ContentFile(f.read(), a_file["filename"])) 392 | 393 | 394 | 395 | Fieldsets 396 | --------- 397 | 398 | By default, DjVue can handle also nested serializers with one nesting level, though if you need more, this behavior can be easily changed. Child serializer fields will be rendered in the same format that parent fields are. The only adjustment required to support them is to modify the ``form`` key from the ``data`` method to include an object which defines the child serializer fields rather than a key-value pair. 399 | 400 | .. code-block:: javascript 401 | 402 | new Vue({ 403 | // ... 404 | data() { 405 | return { 406 | form: { 407 | // ... 408 | working_place: { 409 | job: null, 410 | position: null 411 | } 412 | } 413 | } 414 | }, 415 | 416 | // ... 417 | }) 418 | 419 | 420 | Formsets 421 | -------- 422 | 423 | At this moment formset are indeed supported, but they have to be written by hand. It's on the road map to provide utilities for them also. Here's a naive implementation of how they can be done: 424 | 425 | ``serializers.py`` 426 | 427 | .. code-block:: python 428 | 429 | class AddressSerializer(serializers.Serializer): 430 | COUNTRY_CHOICES = (("ro", "Romania"), ("de", "Germany"), ("kw", "Kuwait")) 431 | country = serializers.ChoiceField(choices=COUNTRY_CHOICES) 432 | zip_code = serializers.CharField() 433 | address = serializers.CharField(required=False) 434 | 435 | class Meta: 436 | list_serializer_class = serializers.ListSerializer 437 | 438 | 439 | class ProfileSerializer(serializers.ModelSerializer): 440 | # ... 441 | addresses = AddressSerializer(many=True) 442 | 443 | ``script.js`` 444 | 445 | .. code-block:: javascript 446 | 447 | let addressIndex = 0 448 | 449 | new Vue({ 450 | // .. 451 | data() { 452 | return { 453 | formsetReady: false, 454 | formsetDefinition: {}, 455 | form: { 456 | // .. 457 | addresses: [ 458 | { 459 | id: `address-${addressIndex}`, 460 | country: null, 461 | zip_code: null, 462 | address: null 463 | } 464 | ] 465 | }, 466 | } 467 | }, 468 | watch: { 469 | options() { 470 | // set the formset definitions 471 | this.formsetDefinition = this.options.addresses.child.children 472 | this.formsetReady = true 473 | } 474 | }, 475 | methods: { 476 | addAddress() { 477 | addressIndex++ 478 | this.form.addresses.push({ 479 | id: `address-${addressIndex}`, 480 | country: null, 481 | zip_code: null, 482 | address: null, 483 | }) 484 | }, 485 | deleteAddress(index) { 486 | this.form.addresses.splice(index, 1) 487 | }, 488 | 489 | } 490 | }) 491 | 492 | Place the formset anywhere inside the form definition wrapped with its own ``validation-observer``. 493 | 494 | ``index.html`` 495 | 496 | .. code-block:: HTML 497 | 498 | 499 |
500 |
501 | x 503 |

Address

504 |
505 | 506 | 509 | 522 |

{( error )}

523 |
524 | 525 | 528 | 533 |

{( error )}

534 |
535 | 536 | 539 | 544 |

{( error )}

545 |
546 | 547 |
548 |
549 |
550 |
551 | 552 | 553 | i18n and custom field error messages 554 | ------------------------------------ 555 | 556 | By default, error messages are rendered in the English language. In order to change them, add in ``settings.py`` the path of the file where new messages are located. 557 | 558 | ``settings.py`` 559 | 560 | .. code-block:: python 561 | 562 | LANGUAGE_CODE = "en-us" 563 | 564 | DJVUE_VV_LOCALE_PATH = "example.locale.djvue_messages" 565 | 566 | This file must contain a dictionary that matches the language codes defined in `LANGUAGES `_ or if your project is not multilingual and if you need to override the default messages, define a dictionary with one key which is matching `LANGUAGE_CODE `_ value. The children key which holds the messages must match the `VeeValidate keys `_. 567 | 568 | ``djvue_messages.py`` 569 | 570 | .. code-block:: python 571 | 572 | vv_locale = { 573 | "en-us": { 574 | "en": { 575 | "messages": { 576 | "alpha": "This field may only contain alphabetic characters.", 577 | } 578 | } 579 | }, 580 | } 581 | 582 | 583 | ===== 584 | TODOs 585 | ===== 586 | 587 | * Generate form object from the serializer definition. 588 | * Provide utilities for dynamic formsets. 589 | * Handle unlimited levels of nested serializers? 590 | 591 | ======= 592 | Credits 593 | ======= 594 | 595 | 596 | Tools used in rendering this package: 597 | 598 | * Cookiecutter_ 599 | * `cookiecutter-djangopackage`_ 600 | 601 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 602 | .. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackagetoin 603 | -------------------------------------------------------------------------------- /djvue/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.2.1" 2 | -------------------------------------------------------------------------------- /djvue/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | from django.apps import AppConfig 3 | from django.utils.translation import ugettext_lazy as _ 4 | 5 | 6 | class DjVueConfig(AppConfig): 7 | name = "djvue" 8 | verbose_name = _("DjVue") 9 | -------------------------------------------------------------------------------- /djvue/defaults.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def _get(key, default): 5 | return getattr(settings, key, default) 6 | 7 | 8 | VV_LOCALE_PATH = _get("DJVUE_VV_LOCALE_PATH", "djvue.vv_locale.vv_locale") 9 | -------------------------------------------------------------------------------- /djvue/fields.py: -------------------------------------------------------------------------------- 1 | from rest_framework.fields import DictField, ListField 2 | from rest_framework.settings import api_settings 3 | 4 | from .validators import RequiredFileValidator 5 | 6 | 7 | class FileFieldMixin: 8 | default_validators = [RequiredFileValidator()] 9 | base_template = None 10 | 11 | def __init__(self, **kwargs): 12 | kwargs["style"] = kwargs.get("style", {}) 13 | if "base_template" not in kwargs["style"]: 14 | kwargs["style"]["base_template"] = self.base_template 15 | super().__init__(**kwargs) 16 | 17 | def to_representation(self, value): 18 | if not value: 19 | return None 20 | 21 | use_url = getattr(self, "use_url", api_settings.UPLOADED_FILES_USE_URL) 22 | if use_url: 23 | try: 24 | url = value.url 25 | except AttributeError: 26 | return None 27 | request = self.context.get("request", None) 28 | if request is not None: 29 | return request.build_absolute_uri(url) 30 | return url 31 | 32 | return value.name 33 | 34 | 35 | class FileField(FileFieldMixin, DictField): 36 | """ 37 | A hybrid file field. Renders an input type, 38 | accepts as input a dictionary containing the filename and the file path 39 | and it serializes the representation like a native serializer.FileField 40 | """ 41 | base_template = "file.html" 42 | 43 | 44 | class ListFileField(FileFieldMixin, ListField): 45 | """ 46 | List of FileField 47 | """ 48 | base_template = "file_multiple.html" 49 | -------------------------------------------------------------------------------- /djvue/serializers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from pathlib import Path 4 | from tempfile import NamedTemporaryFile 5 | 6 | from django.conf import settings 7 | 8 | from rest_framework import serializers 9 | 10 | 11 | class FileUploadSerializer(serializers.Serializer): 12 | """ 13 | Uploads a file to a temporary directory and 14 | returns the absolute path and original file name 15 | """ 16 | 17 | file = serializers.FileField(style={"base_template": "file.html"}, required=False) 18 | 19 | def create(self, validated_data): 20 | uploaded_file = validated_data["file"] 21 | path = os.path.join(settings.MEDIA_ROOT, "tmp/uploaded_files") 22 | Path(path).mkdir(parents=True, exist_ok=True) 23 | with NamedTemporaryFile(delete=False, dir=path) as temp_file: 24 | temp_file.write(uploaded_file.read()) 25 | return {"path": temp_file.name, "filename": uploaded_file.name} 26 | -------------------------------------------------------------------------------- /djvue/static/vue/js/axios.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["axios"] = factory(); 8 | else 9 | root["axios"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | /******/ 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ (function(module, exports, __webpack_require__) { 56 | 57 | module.exports = __webpack_require__(1); 58 | 59 | /***/ }), 60 | /* 1 */ 61 | /***/ (function(module, exports, __webpack_require__) { 62 | 63 | 'use strict'; 64 | 65 | var utils = __webpack_require__(2); 66 | var bind = __webpack_require__(3); 67 | var Axios = __webpack_require__(4); 68 | var mergeConfig = __webpack_require__(22); 69 | var defaults = __webpack_require__(10); 70 | 71 | /** 72 | * Create an instance of Axios 73 | * 74 | * @param {Object} defaultConfig The default config for the instance 75 | * @return {Axios} A new instance of Axios 76 | */ 77 | function createInstance(defaultConfig) { 78 | var context = new Axios(defaultConfig); 79 | var instance = bind(Axios.prototype.request, context); 80 | 81 | // Copy axios.prototype to instance 82 | utils.extend(instance, Axios.prototype, context); 83 | 84 | // Copy context to instance 85 | utils.extend(instance, context); 86 | 87 | return instance; 88 | } 89 | 90 | // Create the default instance to be exported 91 | var axios = createInstance(defaults); 92 | 93 | // Expose Axios class to allow class inheritance 94 | axios.Axios = Axios; 95 | 96 | // Factory for creating new instances 97 | axios.create = function create(instanceConfig) { 98 | return createInstance(mergeConfig(axios.defaults, instanceConfig)); 99 | }; 100 | 101 | // Expose Cancel & CancelToken 102 | axios.Cancel = __webpack_require__(23); 103 | axios.CancelToken = __webpack_require__(24); 104 | axios.isCancel = __webpack_require__(9); 105 | 106 | // Expose all/spread 107 | axios.all = function all(promises) { 108 | return Promise.all(promises); 109 | }; 110 | axios.spread = __webpack_require__(25); 111 | 112 | module.exports = axios; 113 | 114 | // Allow use of default import syntax in TypeScript 115 | module.exports.default = axios; 116 | 117 | 118 | /***/ }), 119 | /* 2 */ 120 | /***/ (function(module, exports, __webpack_require__) { 121 | 122 | 'use strict'; 123 | 124 | var bind = __webpack_require__(3); 125 | 126 | /*global toString:true*/ 127 | 128 | // utils is a library of generic helper functions non-specific to axios 129 | 130 | var toString = Object.prototype.toString; 131 | 132 | /** 133 | * Determine if a value is an Array 134 | * 135 | * @param {Object} val The value to test 136 | * @returns {boolean} True if value is an Array, otherwise false 137 | */ 138 | function isArray(val) { 139 | return toString.call(val) === '[object Array]'; 140 | } 141 | 142 | /** 143 | * Determine if a value is undefined 144 | * 145 | * @param {Object} val The value to test 146 | * @returns {boolean} True if the value is undefined, otherwise false 147 | */ 148 | function isUndefined(val) { 149 | return typeof val === 'undefined'; 150 | } 151 | 152 | /** 153 | * Determine if a value is a Buffer 154 | * 155 | * @param {Object} val The value to test 156 | * @returns {boolean} True if value is a Buffer, otherwise false 157 | */ 158 | function isBuffer(val) { 159 | return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) 160 | && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val); 161 | } 162 | 163 | /** 164 | * Determine if a value is an ArrayBuffer 165 | * 166 | * @param {Object} val The value to test 167 | * @returns {boolean} True if value is an ArrayBuffer, otherwise false 168 | */ 169 | function isArrayBuffer(val) { 170 | return toString.call(val) === '[object ArrayBuffer]'; 171 | } 172 | 173 | /** 174 | * Determine if a value is a FormData 175 | * 176 | * @param {Object} val The value to test 177 | * @returns {boolean} True if value is an FormData, otherwise false 178 | */ 179 | function isFormData(val) { 180 | return (typeof FormData !== 'undefined') && (val instanceof FormData); 181 | } 182 | 183 | /** 184 | * Determine if a value is a view on an ArrayBuffer 185 | * 186 | * @param {Object} val The value to test 187 | * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false 188 | */ 189 | function isArrayBufferView(val) { 190 | var result; 191 | if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { 192 | result = ArrayBuffer.isView(val); 193 | } else { 194 | result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); 195 | } 196 | return result; 197 | } 198 | 199 | /** 200 | * Determine if a value is a String 201 | * 202 | * @param {Object} val The value to test 203 | * @returns {boolean} True if value is a String, otherwise false 204 | */ 205 | function isString(val) { 206 | return typeof val === 'string'; 207 | } 208 | 209 | /** 210 | * Determine if a value is a Number 211 | * 212 | * @param {Object} val The value to test 213 | * @returns {boolean} True if value is a Number, otherwise false 214 | */ 215 | function isNumber(val) { 216 | return typeof val === 'number'; 217 | } 218 | 219 | /** 220 | * Determine if a value is an Object 221 | * 222 | * @param {Object} val The value to test 223 | * @returns {boolean} True if value is an Object, otherwise false 224 | */ 225 | function isObject(val) { 226 | return val !== null && typeof val === 'object'; 227 | } 228 | 229 | /** 230 | * Determine if a value is a Date 231 | * 232 | * @param {Object} val The value to test 233 | * @returns {boolean} True if value is a Date, otherwise false 234 | */ 235 | function isDate(val) { 236 | return toString.call(val) === '[object Date]'; 237 | } 238 | 239 | /** 240 | * Determine if a value is a File 241 | * 242 | * @param {Object} val The value to test 243 | * @returns {boolean} True if value is a File, otherwise false 244 | */ 245 | function isFile(val) { 246 | return toString.call(val) === '[object File]'; 247 | } 248 | 249 | /** 250 | * Determine if a value is a Blob 251 | * 252 | * @param {Object} val The value to test 253 | * @returns {boolean} True if value is a Blob, otherwise false 254 | */ 255 | function isBlob(val) { 256 | return toString.call(val) === '[object Blob]'; 257 | } 258 | 259 | /** 260 | * Determine if a value is a Function 261 | * 262 | * @param {Object} val The value to test 263 | * @returns {boolean} True if value is a Function, otherwise false 264 | */ 265 | function isFunction(val) { 266 | return toString.call(val) === '[object Function]'; 267 | } 268 | 269 | /** 270 | * Determine if a value is a Stream 271 | * 272 | * @param {Object} val The value to test 273 | * @returns {boolean} True if value is a Stream, otherwise false 274 | */ 275 | function isStream(val) { 276 | return isObject(val) && isFunction(val.pipe); 277 | } 278 | 279 | /** 280 | * Determine if a value is a URLSearchParams object 281 | * 282 | * @param {Object} val The value to test 283 | * @returns {boolean} True if value is a URLSearchParams object, otherwise false 284 | */ 285 | function isURLSearchParams(val) { 286 | return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; 287 | } 288 | 289 | /** 290 | * Trim excess whitespace off the beginning and end of a string 291 | * 292 | * @param {String} str The String to trim 293 | * @returns {String} The String freed of excess whitespace 294 | */ 295 | function trim(str) { 296 | return str.replace(/^\s*/, '').replace(/\s*$/, ''); 297 | } 298 | 299 | /** 300 | * Determine if we're running in a standard browser environment 301 | * 302 | * This allows axios to run in a web worker, and react-native. 303 | * Both environments support XMLHttpRequest, but not fully standard globals. 304 | * 305 | * web workers: 306 | * typeof window -> undefined 307 | * typeof document -> undefined 308 | * 309 | * react-native: 310 | * navigator.product -> 'ReactNative' 311 | * nativescript 312 | * navigator.product -> 'NativeScript' or 'NS' 313 | */ 314 | function isStandardBrowserEnv() { 315 | if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' || 316 | navigator.product === 'NativeScript' || 317 | navigator.product === 'NS')) { 318 | return false; 319 | } 320 | return ( 321 | typeof window !== 'undefined' && 322 | typeof document !== 'undefined' 323 | ); 324 | } 325 | 326 | /** 327 | * Iterate over an Array or an Object invoking a function for each item. 328 | * 329 | * If `obj` is an Array callback will be called passing 330 | * the value, index, and complete array for each item. 331 | * 332 | * If 'obj' is an Object callback will be called passing 333 | * the value, key, and complete object for each property. 334 | * 335 | * @param {Object|Array} obj The object to iterate 336 | * @param {Function} fn The callback to invoke for each item 337 | */ 338 | function forEach(obj, fn) { 339 | // Don't bother if no value provided 340 | if (obj === null || typeof obj === 'undefined') { 341 | return; 342 | } 343 | 344 | // Force an array if not already something iterable 345 | if (typeof obj !== 'object') { 346 | /*eslint no-param-reassign:0*/ 347 | obj = [obj]; 348 | } 349 | 350 | if (isArray(obj)) { 351 | // Iterate over array values 352 | for (var i = 0, l = obj.length; i < l; i++) { 353 | fn.call(null, obj[i], i, obj); 354 | } 355 | } else { 356 | // Iterate over object keys 357 | for (var key in obj) { 358 | if (Object.prototype.hasOwnProperty.call(obj, key)) { 359 | fn.call(null, obj[key], key, obj); 360 | } 361 | } 362 | } 363 | } 364 | 365 | /** 366 | * Accepts varargs expecting each argument to be an object, then 367 | * immutably merges the properties of each object and returns result. 368 | * 369 | * When multiple objects contain the same key the later object in 370 | * the arguments list will take precedence. 371 | * 372 | * Example: 373 | * 374 | * ```js 375 | * var result = merge({foo: 123}, {foo: 456}); 376 | * console.log(result.foo); // outputs 456 377 | * ``` 378 | * 379 | * @param {Object} obj1 Object to merge 380 | * @returns {Object} Result of all merge properties 381 | */ 382 | function merge(/* obj1, obj2, obj3, ... */) { 383 | var result = {}; 384 | function assignValue(val, key) { 385 | if (typeof result[key] === 'object' && typeof val === 'object') { 386 | result[key] = merge(result[key], val); 387 | } else { 388 | result[key] = val; 389 | } 390 | } 391 | 392 | for (var i = 0, l = arguments.length; i < l; i++) { 393 | forEach(arguments[i], assignValue); 394 | } 395 | return result; 396 | } 397 | 398 | /** 399 | * Function equal to merge with the difference being that no reference 400 | * to original objects is kept. 401 | * 402 | * @see merge 403 | * @param {Object} obj1 Object to merge 404 | * @returns {Object} Result of all merge properties 405 | */ 406 | function deepMerge(/* obj1, obj2, obj3, ... */) { 407 | var result = {}; 408 | function assignValue(val, key) { 409 | if (typeof result[key] === 'object' && typeof val === 'object') { 410 | result[key] = deepMerge(result[key], val); 411 | } else if (typeof val === 'object') { 412 | result[key] = deepMerge({}, val); 413 | } else { 414 | result[key] = val; 415 | } 416 | } 417 | 418 | for (var i = 0, l = arguments.length; i < l; i++) { 419 | forEach(arguments[i], assignValue); 420 | } 421 | return result; 422 | } 423 | 424 | /** 425 | * Extends object a by mutably adding to it the properties of object b. 426 | * 427 | * @param {Object} a The object to be extended 428 | * @param {Object} b The object to copy properties from 429 | * @param {Object} thisArg The object to bind function to 430 | * @return {Object} The resulting value of object a 431 | */ 432 | function extend(a, b, thisArg) { 433 | forEach(b, function assignValue(val, key) { 434 | if (thisArg && typeof val === 'function') { 435 | a[key] = bind(val, thisArg); 436 | } else { 437 | a[key] = val; 438 | } 439 | }); 440 | return a; 441 | } 442 | 443 | module.exports = { 444 | isArray: isArray, 445 | isArrayBuffer: isArrayBuffer, 446 | isBuffer: isBuffer, 447 | isFormData: isFormData, 448 | isArrayBufferView: isArrayBufferView, 449 | isString: isString, 450 | isNumber: isNumber, 451 | isObject: isObject, 452 | isUndefined: isUndefined, 453 | isDate: isDate, 454 | isFile: isFile, 455 | isBlob: isBlob, 456 | isFunction: isFunction, 457 | isStream: isStream, 458 | isURLSearchParams: isURLSearchParams, 459 | isStandardBrowserEnv: isStandardBrowserEnv, 460 | forEach: forEach, 461 | merge: merge, 462 | deepMerge: deepMerge, 463 | extend: extend, 464 | trim: trim 465 | }; 466 | 467 | 468 | /***/ }), 469 | /* 3 */ 470 | /***/ (function(module, exports) { 471 | 472 | 'use strict'; 473 | 474 | module.exports = function bind(fn, thisArg) { 475 | return function wrap() { 476 | var args = new Array(arguments.length); 477 | for (var i = 0; i < args.length; i++) { 478 | args[i] = arguments[i]; 479 | } 480 | return fn.apply(thisArg, args); 481 | }; 482 | }; 483 | 484 | 485 | /***/ }), 486 | /* 4 */ 487 | /***/ (function(module, exports, __webpack_require__) { 488 | 489 | 'use strict'; 490 | 491 | var utils = __webpack_require__(2); 492 | var buildURL = __webpack_require__(5); 493 | var InterceptorManager = __webpack_require__(6); 494 | var dispatchRequest = __webpack_require__(7); 495 | var mergeConfig = __webpack_require__(22); 496 | 497 | /** 498 | * Create a new instance of Axios 499 | * 500 | * @param {Object} instanceConfig The default config for the instance 501 | */ 502 | function Axios(instanceConfig) { 503 | this.defaults = instanceConfig; 504 | this.interceptors = { 505 | request: new InterceptorManager(), 506 | response: new InterceptorManager() 507 | }; 508 | } 509 | 510 | /** 511 | * Dispatch a request 512 | * 513 | * @param {Object} config The config specific for this request (merged with this.defaults) 514 | */ 515 | Axios.prototype.request = function request(config) { 516 | /*eslint no-param-reassign:0*/ 517 | // Allow for axios('example/url'[, config]) a la fetch API 518 | if (typeof config === 'string') { 519 | config = arguments[1] || {}; 520 | config.url = arguments[0]; 521 | } else { 522 | config = config || {}; 523 | } 524 | 525 | config = mergeConfig(this.defaults, config); 526 | 527 | // Set config.method 528 | if (config.method) { 529 | config.method = config.method.toLowerCase(); 530 | } else if (this.defaults.method) { 531 | config.method = this.defaults.method.toLowerCase(); 532 | } else { 533 | config.method = 'get'; 534 | } 535 | 536 | // Hook up interceptors middleware 537 | var chain = [dispatchRequest, undefined]; 538 | var promise = Promise.resolve(config); 539 | 540 | this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { 541 | chain.unshift(interceptor.fulfilled, interceptor.rejected); 542 | }); 543 | 544 | this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { 545 | chain.push(interceptor.fulfilled, interceptor.rejected); 546 | }); 547 | 548 | while (chain.length) { 549 | promise = promise.then(chain.shift(), chain.shift()); 550 | } 551 | 552 | return promise; 553 | }; 554 | 555 | Axios.prototype.getUri = function getUri(config) { 556 | config = mergeConfig(this.defaults, config); 557 | return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, ''); 558 | }; 559 | 560 | // Provide aliases for supported request methods 561 | utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { 562 | /*eslint func-names:0*/ 563 | Axios.prototype[method] = function(url, config) { 564 | return this.request(utils.merge(config || {}, { 565 | method: method, 566 | url: url 567 | })); 568 | }; 569 | }); 570 | 571 | utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { 572 | /*eslint func-names:0*/ 573 | Axios.prototype[method] = function(url, data, config) { 574 | return this.request(utils.merge(config || {}, { 575 | method: method, 576 | url: url, 577 | data: data 578 | })); 579 | }; 580 | }); 581 | 582 | module.exports = Axios; 583 | 584 | 585 | /***/ }), 586 | /* 5 */ 587 | /***/ (function(module, exports, __webpack_require__) { 588 | 589 | 'use strict'; 590 | 591 | var utils = __webpack_require__(2); 592 | 593 | function encode(val) { 594 | return encodeURIComponent(val). 595 | replace(/%40/gi, '@'). 596 | replace(/%3A/gi, ':'). 597 | replace(/%24/g, '$'). 598 | replace(/%2C/gi, ','). 599 | replace(/%20/g, '+'). 600 | replace(/%5B/gi, '['). 601 | replace(/%5D/gi, ']'); 602 | } 603 | 604 | /** 605 | * Build a URL by appending params to the end 606 | * 607 | * @param {string} url The base of the url (e.g., http://www.google.com) 608 | * @param {object} [params] The params to be appended 609 | * @returns {string} The formatted url 610 | */ 611 | module.exports = function buildURL(url, params, paramsSerializer) { 612 | /*eslint no-param-reassign:0*/ 613 | if (!params) { 614 | return url; 615 | } 616 | 617 | var serializedParams; 618 | if (paramsSerializer) { 619 | serializedParams = paramsSerializer(params); 620 | } else if (utils.isURLSearchParams(params)) { 621 | serializedParams = params.toString(); 622 | } else { 623 | var parts = []; 624 | 625 | utils.forEach(params, function serialize(val, key) { 626 | if (val === null || typeof val === 'undefined') { 627 | return; 628 | } 629 | 630 | if (utils.isArray(val)) { 631 | key = key + '[]'; 632 | } else { 633 | val = [val]; 634 | } 635 | 636 | utils.forEach(val, function parseValue(v) { 637 | if (utils.isDate(v)) { 638 | v = v.toISOString(); 639 | } else if (utils.isObject(v)) { 640 | v = JSON.stringify(v); 641 | } 642 | parts.push(encode(key) + '=' + encode(v)); 643 | }); 644 | }); 645 | 646 | serializedParams = parts.join('&'); 647 | } 648 | 649 | if (serializedParams) { 650 | var hashmarkIndex = url.indexOf('#'); 651 | if (hashmarkIndex !== -1) { 652 | url = url.slice(0, hashmarkIndex); 653 | } 654 | 655 | url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; 656 | } 657 | 658 | return url; 659 | }; 660 | 661 | 662 | /***/ }), 663 | /* 6 */ 664 | /***/ (function(module, exports, __webpack_require__) { 665 | 666 | 'use strict'; 667 | 668 | var utils = __webpack_require__(2); 669 | 670 | function InterceptorManager() { 671 | this.handlers = []; 672 | } 673 | 674 | /** 675 | * Add a new interceptor to the stack 676 | * 677 | * @param {Function} fulfilled The function to handle `then` for a `Promise` 678 | * @param {Function} rejected The function to handle `reject` for a `Promise` 679 | * 680 | * @return {Number} An ID used to remove interceptor later 681 | */ 682 | InterceptorManager.prototype.use = function use(fulfilled, rejected) { 683 | this.handlers.push({ 684 | fulfilled: fulfilled, 685 | rejected: rejected 686 | }); 687 | return this.handlers.length - 1; 688 | }; 689 | 690 | /** 691 | * Remove an interceptor from the stack 692 | * 693 | * @param {Number} id The ID that was returned by `use` 694 | */ 695 | InterceptorManager.prototype.eject = function eject(id) { 696 | if (this.handlers[id]) { 697 | this.handlers[id] = null; 698 | } 699 | }; 700 | 701 | /** 702 | * Iterate over all the registered interceptors 703 | * 704 | * This method is particularly useful for skipping over any 705 | * interceptors that may have become `null` calling `eject`. 706 | * 707 | * @param {Function} fn The function to call for each interceptor 708 | */ 709 | InterceptorManager.prototype.forEach = function forEach(fn) { 710 | utils.forEach(this.handlers, function forEachHandler(h) { 711 | if (h !== null) { 712 | fn(h); 713 | } 714 | }); 715 | }; 716 | 717 | module.exports = InterceptorManager; 718 | 719 | 720 | /***/ }), 721 | /* 7 */ 722 | /***/ (function(module, exports, __webpack_require__) { 723 | 724 | 'use strict'; 725 | 726 | var utils = __webpack_require__(2); 727 | var transformData = __webpack_require__(8); 728 | var isCancel = __webpack_require__(9); 729 | var defaults = __webpack_require__(10); 730 | 731 | /** 732 | * Throws a `Cancel` if cancellation has been requested. 733 | */ 734 | function throwIfCancellationRequested(config) { 735 | if (config.cancelToken) { 736 | config.cancelToken.throwIfRequested(); 737 | } 738 | } 739 | 740 | /** 741 | * Dispatch a request to the server using the configured adapter. 742 | * 743 | * @param {object} config The config that is to be used for the request 744 | * @returns {Promise} The Promise to be fulfilled 745 | */ 746 | module.exports = function dispatchRequest(config) { 747 | throwIfCancellationRequested(config); 748 | 749 | // Ensure headers exist 750 | config.headers = config.headers || {}; 751 | 752 | // Transform request data 753 | config.data = transformData( 754 | config.data, 755 | config.headers, 756 | config.transformRequest 757 | ); 758 | 759 | // Flatten headers 760 | config.headers = utils.merge( 761 | config.headers.common || {}, 762 | config.headers[config.method] || {}, 763 | config.headers 764 | ); 765 | 766 | utils.forEach( 767 | ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], 768 | function cleanHeaderConfig(method) { 769 | delete config.headers[method]; 770 | } 771 | ); 772 | 773 | var adapter = config.adapter || defaults.adapter; 774 | 775 | return adapter(config).then(function onAdapterResolution(response) { 776 | throwIfCancellationRequested(config); 777 | 778 | // Transform response data 779 | response.data = transformData( 780 | response.data, 781 | response.headers, 782 | config.transformResponse 783 | ); 784 | 785 | return response; 786 | }, function onAdapterRejection(reason) { 787 | if (!isCancel(reason)) { 788 | throwIfCancellationRequested(config); 789 | 790 | // Transform response data 791 | if (reason && reason.response) { 792 | reason.response.data = transformData( 793 | reason.response.data, 794 | reason.response.headers, 795 | config.transformResponse 796 | ); 797 | } 798 | } 799 | 800 | return Promise.reject(reason); 801 | }); 802 | }; 803 | 804 | 805 | /***/ }), 806 | /* 8 */ 807 | /***/ (function(module, exports, __webpack_require__) { 808 | 809 | 'use strict'; 810 | 811 | var utils = __webpack_require__(2); 812 | 813 | /** 814 | * Transform the data for a request or a response 815 | * 816 | * @param {Object|String} data The data to be transformed 817 | * @param {Array} headers The headers for the request or response 818 | * @param {Array|Function} fns A single function or Array of functions 819 | * @returns {*} The resulting transformed data 820 | */ 821 | module.exports = function transformData(data, headers, fns) { 822 | /*eslint no-param-reassign:0*/ 823 | utils.forEach(fns, function transform(fn) { 824 | data = fn(data, headers); 825 | }); 826 | 827 | return data; 828 | }; 829 | 830 | 831 | /***/ }), 832 | /* 9 */ 833 | /***/ (function(module, exports) { 834 | 835 | 'use strict'; 836 | 837 | module.exports = function isCancel(value) { 838 | return !!(value && value.__CANCEL__); 839 | }; 840 | 841 | 842 | /***/ }), 843 | /* 10 */ 844 | /***/ (function(module, exports, __webpack_require__) { 845 | 846 | 'use strict'; 847 | 848 | var utils = __webpack_require__(2); 849 | var normalizeHeaderName = __webpack_require__(11); 850 | 851 | var DEFAULT_CONTENT_TYPE = { 852 | 'Content-Type': 'application/x-www-form-urlencoded' 853 | }; 854 | 855 | function setContentTypeIfUnset(headers, value) { 856 | if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { 857 | headers['Content-Type'] = value; 858 | } 859 | } 860 | 861 | function getDefaultAdapter() { 862 | var adapter; 863 | if (typeof XMLHttpRequest !== 'undefined') { 864 | // For browsers use XHR adapter 865 | adapter = __webpack_require__(12); 866 | } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { 867 | // For node use HTTP adapter 868 | adapter = __webpack_require__(12); 869 | } 870 | return adapter; 871 | } 872 | 873 | var defaults = { 874 | adapter: getDefaultAdapter(), 875 | 876 | transformRequest: [function transformRequest(data, headers) { 877 | normalizeHeaderName(headers, 'Accept'); 878 | normalizeHeaderName(headers, 'Content-Type'); 879 | if (utils.isFormData(data) || 880 | utils.isArrayBuffer(data) || 881 | utils.isBuffer(data) || 882 | utils.isStream(data) || 883 | utils.isFile(data) || 884 | utils.isBlob(data) 885 | ) { 886 | return data; 887 | } 888 | if (utils.isArrayBufferView(data)) { 889 | return data.buffer; 890 | } 891 | if (utils.isURLSearchParams(data)) { 892 | setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); 893 | return data.toString(); 894 | } 895 | if (utils.isObject(data)) { 896 | setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); 897 | return JSON.stringify(data); 898 | } 899 | return data; 900 | }], 901 | 902 | transformResponse: [function transformResponse(data) { 903 | /*eslint no-param-reassign:0*/ 904 | if (typeof data === 'string') { 905 | try { 906 | data = JSON.parse(data); 907 | } catch (e) { /* Ignore */ } 908 | } 909 | return data; 910 | }], 911 | 912 | /** 913 | * A timeout in milliseconds to abort a request. If set to 0 (default) a 914 | * timeout is not created. 915 | */ 916 | timeout: 0, 917 | 918 | xsrfCookieName: 'XSRF-TOKEN', 919 | xsrfHeaderName: 'X-XSRF-TOKEN', 920 | 921 | maxContentLength: -1, 922 | 923 | validateStatus: function validateStatus(status) { 924 | return status >= 200 && status < 300; 925 | } 926 | }; 927 | 928 | defaults.headers = { 929 | common: { 930 | 'Accept': 'application/json, text/plain, */*' 931 | } 932 | }; 933 | 934 | utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { 935 | defaults.headers[method] = {}; 936 | }); 937 | 938 | utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { 939 | defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); 940 | }); 941 | 942 | module.exports = defaults; 943 | 944 | 945 | /***/ }), 946 | /* 11 */ 947 | /***/ (function(module, exports, __webpack_require__) { 948 | 949 | 'use strict'; 950 | 951 | var utils = __webpack_require__(2); 952 | 953 | module.exports = function normalizeHeaderName(headers, normalizedName) { 954 | utils.forEach(headers, function processHeader(value, name) { 955 | if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { 956 | headers[normalizedName] = value; 957 | delete headers[name]; 958 | } 959 | }); 960 | }; 961 | 962 | 963 | /***/ }), 964 | /* 12 */ 965 | /***/ (function(module, exports, __webpack_require__) { 966 | 967 | 'use strict'; 968 | 969 | var utils = __webpack_require__(2); 970 | var settle = __webpack_require__(13); 971 | var buildURL = __webpack_require__(5); 972 | var buildFullPath = __webpack_require__(16); 973 | var parseHeaders = __webpack_require__(19); 974 | var isURLSameOrigin = __webpack_require__(20); 975 | var createError = __webpack_require__(14); 976 | 977 | module.exports = function xhrAdapter(config) { 978 | return new Promise(function dispatchXhrRequest(resolve, reject) { 979 | var requestData = config.data; 980 | var requestHeaders = config.headers; 981 | 982 | if (utils.isFormData(requestData)) { 983 | delete requestHeaders['Content-Type']; // Let the browser set it 984 | } 985 | 986 | var request = new XMLHttpRequest(); 987 | 988 | // HTTP basic authentication 989 | if (config.auth) { 990 | var username = config.auth.username || ''; 991 | var password = config.auth.password || ''; 992 | requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); 993 | } 994 | 995 | var fullPath = buildFullPath(config.baseURL, config.url); 996 | request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); 997 | 998 | // Set the request timeout in MS 999 | request.timeout = config.timeout; 1000 | 1001 | // Listen for ready state 1002 | request.onreadystatechange = function handleLoad() { 1003 | if (!request || request.readyState !== 4) { 1004 | return; 1005 | } 1006 | 1007 | // The request errored out and we didn't get a response, this will be 1008 | // handled by onerror instead 1009 | // With one exception: request that using file: protocol, most browsers 1010 | // will return status as 0 even though it's a successful request 1011 | if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { 1012 | return; 1013 | } 1014 | 1015 | // Prepare the response 1016 | var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; 1017 | var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; 1018 | var response = { 1019 | data: responseData, 1020 | status: request.status, 1021 | statusText: request.statusText, 1022 | headers: responseHeaders, 1023 | config: config, 1024 | request: request 1025 | }; 1026 | 1027 | settle(resolve, reject, response); 1028 | 1029 | // Clean up request 1030 | request = null; 1031 | }; 1032 | 1033 | // Handle browser request cancellation (as opposed to a manual cancellation) 1034 | request.onabort = function handleAbort() { 1035 | if (!request) { 1036 | return; 1037 | } 1038 | 1039 | reject(createError('Request aborted', config, 'ECONNABORTED', request)); 1040 | 1041 | // Clean up request 1042 | request = null; 1043 | }; 1044 | 1045 | // Handle low level network errors 1046 | request.onerror = function handleError() { 1047 | // Real errors are hidden from us by the browser 1048 | // onerror should only fire if it's a network error 1049 | reject(createError('Network Error', config, null, request)); 1050 | 1051 | // Clean up request 1052 | request = null; 1053 | }; 1054 | 1055 | // Handle timeout 1056 | request.ontimeout = function handleTimeout() { 1057 | var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded'; 1058 | if (config.timeoutErrorMessage) { 1059 | timeoutErrorMessage = config.timeoutErrorMessage; 1060 | } 1061 | reject(createError(timeoutErrorMessage, config, 'ECONNABORTED', 1062 | request)); 1063 | 1064 | // Clean up request 1065 | request = null; 1066 | }; 1067 | 1068 | // Add xsrf header 1069 | // This is only done if running in a standard browser environment. 1070 | // Specifically not if we're in a web worker, or react-native. 1071 | if (utils.isStandardBrowserEnv()) { 1072 | var cookies = __webpack_require__(21); 1073 | 1074 | // Add xsrf header 1075 | var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? 1076 | cookies.read(config.xsrfCookieName) : 1077 | undefined; 1078 | 1079 | if (xsrfValue) { 1080 | requestHeaders[config.xsrfHeaderName] = xsrfValue; 1081 | } 1082 | } 1083 | 1084 | // Add headers to the request 1085 | if ('setRequestHeader' in request) { 1086 | utils.forEach(requestHeaders, function setRequestHeader(val, key) { 1087 | if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { 1088 | // Remove Content-Type if data is undefined 1089 | delete requestHeaders[key]; 1090 | } else { 1091 | // Otherwise add header to the request 1092 | request.setRequestHeader(key, val); 1093 | } 1094 | }); 1095 | } 1096 | 1097 | // Add withCredentials to request if needed 1098 | if (!utils.isUndefined(config.withCredentials)) { 1099 | request.withCredentials = !!config.withCredentials; 1100 | } 1101 | 1102 | // Add responseType to request if needed 1103 | if (config.responseType) { 1104 | try { 1105 | request.responseType = config.responseType; 1106 | } catch (e) { 1107 | // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2. 1108 | // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function. 1109 | if (config.responseType !== 'json') { 1110 | throw e; 1111 | } 1112 | } 1113 | } 1114 | 1115 | // Handle progress if needed 1116 | if (typeof config.onDownloadProgress === 'function') { 1117 | request.addEventListener('progress', config.onDownloadProgress); 1118 | } 1119 | 1120 | // Not all browsers support upload events 1121 | if (typeof config.onUploadProgress === 'function' && request.upload) { 1122 | request.upload.addEventListener('progress', config.onUploadProgress); 1123 | } 1124 | 1125 | if (config.cancelToken) { 1126 | // Handle cancellation 1127 | config.cancelToken.promise.then(function onCanceled(cancel) { 1128 | if (!request) { 1129 | return; 1130 | } 1131 | 1132 | request.abort(); 1133 | reject(cancel); 1134 | // Clean up request 1135 | request = null; 1136 | }); 1137 | } 1138 | 1139 | if (requestData === undefined) { 1140 | requestData = null; 1141 | } 1142 | 1143 | // Send the request 1144 | request.send(requestData); 1145 | }); 1146 | }; 1147 | 1148 | 1149 | /***/ }), 1150 | /* 13 */ 1151 | /***/ (function(module, exports, __webpack_require__) { 1152 | 1153 | 'use strict'; 1154 | 1155 | var createError = __webpack_require__(14); 1156 | 1157 | /** 1158 | * Resolve or reject a Promise based on response status. 1159 | * 1160 | * @param {Function} resolve A function that resolves the promise. 1161 | * @param {Function} reject A function that rejects the promise. 1162 | * @param {object} response The response. 1163 | */ 1164 | module.exports = function settle(resolve, reject, response) { 1165 | var validateStatus = response.config.validateStatus; 1166 | if (!validateStatus || validateStatus(response.status)) { 1167 | resolve(response); 1168 | } else { 1169 | reject(createError( 1170 | 'Request failed with status code ' + response.status, 1171 | response.config, 1172 | null, 1173 | response.request, 1174 | response 1175 | )); 1176 | } 1177 | }; 1178 | 1179 | 1180 | /***/ }), 1181 | /* 14 */ 1182 | /***/ (function(module, exports, __webpack_require__) { 1183 | 1184 | 'use strict'; 1185 | 1186 | var enhanceError = __webpack_require__(15); 1187 | 1188 | /** 1189 | * Create an Error with the specified message, config, error code, request and response. 1190 | * 1191 | * @param {string} message The error message. 1192 | * @param {Object} config The config. 1193 | * @param {string} [code] The error code (for example, 'ECONNABORTED'). 1194 | * @param {Object} [request] The request. 1195 | * @param {Object} [response] The response. 1196 | * @returns {Error} The created error. 1197 | */ 1198 | module.exports = function createError(message, config, code, request, response) { 1199 | var error = new Error(message); 1200 | return enhanceError(error, config, code, request, response); 1201 | }; 1202 | 1203 | 1204 | /***/ }), 1205 | /* 15 */ 1206 | /***/ (function(module, exports) { 1207 | 1208 | 'use strict'; 1209 | 1210 | /** 1211 | * Update an Error with the specified config, error code, and response. 1212 | * 1213 | * @param {Error} error The error to update. 1214 | * @param {Object} config The config. 1215 | * @param {string} [code] The error code (for example, 'ECONNABORTED'). 1216 | * @param {Object} [request] The request. 1217 | * @param {Object} [response] The response. 1218 | * @returns {Error} The error. 1219 | */ 1220 | module.exports = function enhanceError(error, config, code, request, response) { 1221 | error.config = config; 1222 | if (code) { 1223 | error.code = code; 1224 | } 1225 | 1226 | error.request = request; 1227 | error.response = response; 1228 | error.isAxiosError = true; 1229 | 1230 | error.toJSON = function() { 1231 | return { 1232 | // Standard 1233 | message: this.message, 1234 | name: this.name, 1235 | // Microsoft 1236 | description: this.description, 1237 | number: this.number, 1238 | // Mozilla 1239 | fileName: this.fileName, 1240 | lineNumber: this.lineNumber, 1241 | columnNumber: this.columnNumber, 1242 | stack: this.stack, 1243 | // Axios 1244 | config: this.config, 1245 | code: this.code 1246 | }; 1247 | }; 1248 | return error; 1249 | }; 1250 | 1251 | 1252 | /***/ }), 1253 | /* 16 */ 1254 | /***/ (function(module, exports, __webpack_require__) { 1255 | 1256 | 'use strict'; 1257 | 1258 | var isAbsoluteURL = __webpack_require__(17); 1259 | var combineURLs = __webpack_require__(18); 1260 | 1261 | /** 1262 | * Creates a new URL by combining the baseURL with the requestedURL, 1263 | * only when the requestedURL is not already an absolute URL. 1264 | * If the requestURL is absolute, this function returns the requestedURL untouched. 1265 | * 1266 | * @param {string} baseURL The base URL 1267 | * @param {string} requestedURL Absolute or relative URL to combine 1268 | * @returns {string} The combined full path 1269 | */ 1270 | module.exports = function buildFullPath(baseURL, requestedURL) { 1271 | if (baseURL && !isAbsoluteURL(requestedURL)) { 1272 | return combineURLs(baseURL, requestedURL); 1273 | } 1274 | return requestedURL; 1275 | }; 1276 | 1277 | 1278 | /***/ }), 1279 | /* 17 */ 1280 | /***/ (function(module, exports) { 1281 | 1282 | 'use strict'; 1283 | 1284 | /** 1285 | * Determines whether the specified URL is absolute 1286 | * 1287 | * @param {string} url The URL to test 1288 | * @returns {boolean} True if the specified URL is absolute, otherwise false 1289 | */ 1290 | module.exports = function isAbsoluteURL(url) { 1291 | // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). 1292 | // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed 1293 | // by any combination of letters, digits, plus, period, or hyphen. 1294 | return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); 1295 | }; 1296 | 1297 | 1298 | /***/ }), 1299 | /* 18 */ 1300 | /***/ (function(module, exports) { 1301 | 1302 | 'use strict'; 1303 | 1304 | /** 1305 | * Creates a new URL by combining the specified URLs 1306 | * 1307 | * @param {string} baseURL The base URL 1308 | * @param {string} relativeURL The relative URL 1309 | * @returns {string} The combined URL 1310 | */ 1311 | module.exports = function combineURLs(baseURL, relativeURL) { 1312 | return relativeURL 1313 | ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') 1314 | : baseURL; 1315 | }; 1316 | 1317 | 1318 | /***/ }), 1319 | /* 19 */ 1320 | /***/ (function(module, exports, __webpack_require__) { 1321 | 1322 | 'use strict'; 1323 | 1324 | var utils = __webpack_require__(2); 1325 | 1326 | // Headers whose duplicates are ignored by node 1327 | // c.f. https://nodejs.org/api/http.html#http_message_headers 1328 | var ignoreDuplicateOf = [ 1329 | 'age', 'authorization', 'content-length', 'content-type', 'etag', 1330 | 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 1331 | 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 1332 | 'referer', 'retry-after', 'user-agent' 1333 | ]; 1334 | 1335 | /** 1336 | * Parse headers into an object 1337 | * 1338 | * ``` 1339 | * Date: Wed, 27 Aug 2014 08:58:49 GMT 1340 | * Content-Type: application/json 1341 | * Connection: keep-alive 1342 | * Transfer-Encoding: chunked 1343 | * ``` 1344 | * 1345 | * @param {String} headers Headers needing to be parsed 1346 | * @returns {Object} Headers parsed into an object 1347 | */ 1348 | module.exports = function parseHeaders(headers) { 1349 | var parsed = {}; 1350 | var key; 1351 | var val; 1352 | var i; 1353 | 1354 | if (!headers) { return parsed; } 1355 | 1356 | utils.forEach(headers.split('\n'), function parser(line) { 1357 | i = line.indexOf(':'); 1358 | key = utils.trim(line.substr(0, i)).toLowerCase(); 1359 | val = utils.trim(line.substr(i + 1)); 1360 | 1361 | if (key) { 1362 | if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { 1363 | return; 1364 | } 1365 | if (key === 'set-cookie') { 1366 | parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); 1367 | } else { 1368 | parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 1369 | } 1370 | } 1371 | }); 1372 | 1373 | return parsed; 1374 | }; 1375 | 1376 | 1377 | /***/ }), 1378 | /* 20 */ 1379 | /***/ (function(module, exports, __webpack_require__) { 1380 | 1381 | 'use strict'; 1382 | 1383 | var utils = __webpack_require__(2); 1384 | 1385 | module.exports = ( 1386 | utils.isStandardBrowserEnv() ? 1387 | 1388 | // Standard browser envs have full support of the APIs needed to test 1389 | // whether the request URL is of the same origin as current location. 1390 | (function standardBrowserEnv() { 1391 | var msie = /(msie|trident)/i.test(navigator.userAgent); 1392 | var urlParsingNode = document.createElement('a'); 1393 | var originURL; 1394 | 1395 | /** 1396 | * Parse a URL to discover it's components 1397 | * 1398 | * @param {String} url The URL to be parsed 1399 | * @returns {Object} 1400 | */ 1401 | function resolveURL(url) { 1402 | var href = url; 1403 | 1404 | if (msie) { 1405 | // IE needs attribute set twice to normalize properties 1406 | urlParsingNode.setAttribute('href', href); 1407 | href = urlParsingNode.href; 1408 | } 1409 | 1410 | urlParsingNode.setAttribute('href', href); 1411 | 1412 | // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 1413 | return { 1414 | href: urlParsingNode.href, 1415 | protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 1416 | host: urlParsingNode.host, 1417 | search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 1418 | hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 1419 | hostname: urlParsingNode.hostname, 1420 | port: urlParsingNode.port, 1421 | pathname: (urlParsingNode.pathname.charAt(0) === '/') ? 1422 | urlParsingNode.pathname : 1423 | '/' + urlParsingNode.pathname 1424 | }; 1425 | } 1426 | 1427 | originURL = resolveURL(window.location.href); 1428 | 1429 | /** 1430 | * Determine if a URL shares the same origin as the current location 1431 | * 1432 | * @param {String} requestURL The URL to test 1433 | * @returns {boolean} True if URL shares the same origin, otherwise false 1434 | */ 1435 | return function isURLSameOrigin(requestURL) { 1436 | var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; 1437 | return (parsed.protocol === originURL.protocol && 1438 | parsed.host === originURL.host); 1439 | }; 1440 | })() : 1441 | 1442 | // Non standard browser envs (web workers, react-native) lack needed support. 1443 | (function nonStandardBrowserEnv() { 1444 | return function isURLSameOrigin() { 1445 | return true; 1446 | }; 1447 | })() 1448 | ); 1449 | 1450 | 1451 | /***/ }), 1452 | /* 21 */ 1453 | /***/ (function(module, exports, __webpack_require__) { 1454 | 1455 | 'use strict'; 1456 | 1457 | var utils = __webpack_require__(2); 1458 | 1459 | module.exports = ( 1460 | utils.isStandardBrowserEnv() ? 1461 | 1462 | // Standard browser envs support document.cookie 1463 | (function standardBrowserEnv() { 1464 | return { 1465 | write: function write(name, value, expires, path, domain, secure) { 1466 | var cookie = []; 1467 | cookie.push(name + '=' + encodeURIComponent(value)); 1468 | 1469 | if (utils.isNumber(expires)) { 1470 | cookie.push('expires=' + new Date(expires).toGMTString()); 1471 | } 1472 | 1473 | if (utils.isString(path)) { 1474 | cookie.push('path=' + path); 1475 | } 1476 | 1477 | if (utils.isString(domain)) { 1478 | cookie.push('domain=' + domain); 1479 | } 1480 | 1481 | if (secure === true) { 1482 | cookie.push('secure'); 1483 | } 1484 | 1485 | document.cookie = cookie.join('; '); 1486 | }, 1487 | 1488 | read: function read(name) { 1489 | var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); 1490 | return (match ? decodeURIComponent(match[3]) : null); 1491 | }, 1492 | 1493 | remove: function remove(name) { 1494 | this.write(name, '', Date.now() - 86400000); 1495 | } 1496 | }; 1497 | })() : 1498 | 1499 | // Non standard browser env (web workers, react-native) lack needed support. 1500 | (function nonStandardBrowserEnv() { 1501 | return { 1502 | write: function write() {}, 1503 | read: function read() { return null; }, 1504 | remove: function remove() {} 1505 | }; 1506 | })() 1507 | ); 1508 | 1509 | 1510 | /***/ }), 1511 | /* 22 */ 1512 | /***/ (function(module, exports, __webpack_require__) { 1513 | 1514 | 'use strict'; 1515 | 1516 | var utils = __webpack_require__(2); 1517 | 1518 | /** 1519 | * Config-specific merge-function which creates a new config-object 1520 | * by merging two configuration objects together. 1521 | * 1522 | * @param {Object} config1 1523 | * @param {Object} config2 1524 | * @returns {Object} New object resulting from merging config2 to config1 1525 | */ 1526 | module.exports = function mergeConfig(config1, config2) { 1527 | // eslint-disable-next-line no-param-reassign 1528 | config2 = config2 || {}; 1529 | var config = {}; 1530 | 1531 | var valueFromConfig2Keys = ['url', 'method', 'params', 'data']; 1532 | var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy']; 1533 | var defaultToConfig2Keys = [ 1534 | 'baseURL', 'url', 'transformRequest', 'transformResponse', 'paramsSerializer', 1535 | 'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName', 1536 | 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 1537 | 'maxContentLength', 'validateStatus', 'maxRedirects', 'httpAgent', 1538 | 'httpsAgent', 'cancelToken', 'socketPath' 1539 | ]; 1540 | 1541 | utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) { 1542 | if (typeof config2[prop] !== 'undefined') { 1543 | config[prop] = config2[prop]; 1544 | } 1545 | }); 1546 | 1547 | utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) { 1548 | if (utils.isObject(config2[prop])) { 1549 | config[prop] = utils.deepMerge(config1[prop], config2[prop]); 1550 | } else if (typeof config2[prop] !== 'undefined') { 1551 | config[prop] = config2[prop]; 1552 | } else if (utils.isObject(config1[prop])) { 1553 | config[prop] = utils.deepMerge(config1[prop]); 1554 | } else if (typeof config1[prop] !== 'undefined') { 1555 | config[prop] = config1[prop]; 1556 | } 1557 | }); 1558 | 1559 | utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) { 1560 | if (typeof config2[prop] !== 'undefined') { 1561 | config[prop] = config2[prop]; 1562 | } else if (typeof config1[prop] !== 'undefined') { 1563 | config[prop] = config1[prop]; 1564 | } 1565 | }); 1566 | 1567 | var axiosKeys = valueFromConfig2Keys 1568 | .concat(mergeDeepPropertiesKeys) 1569 | .concat(defaultToConfig2Keys); 1570 | 1571 | var otherKeys = Object 1572 | .keys(config2) 1573 | .filter(function filterAxiosKeys(key) { 1574 | return axiosKeys.indexOf(key) === -1; 1575 | }); 1576 | 1577 | utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) { 1578 | if (typeof config2[prop] !== 'undefined') { 1579 | config[prop] = config2[prop]; 1580 | } else if (typeof config1[prop] !== 'undefined') { 1581 | config[prop] = config1[prop]; 1582 | } 1583 | }); 1584 | 1585 | return config; 1586 | }; 1587 | 1588 | 1589 | /***/ }), 1590 | /* 23 */ 1591 | /***/ (function(module, exports) { 1592 | 1593 | 'use strict'; 1594 | 1595 | /** 1596 | * A `Cancel` is an object that is thrown when an operation is canceled. 1597 | * 1598 | * @class 1599 | * @param {string=} message The message. 1600 | */ 1601 | function Cancel(message) { 1602 | this.message = message; 1603 | } 1604 | 1605 | Cancel.prototype.toString = function toString() { 1606 | return 'Cancel' + (this.message ? ': ' + this.message : ''); 1607 | }; 1608 | 1609 | Cancel.prototype.__CANCEL__ = true; 1610 | 1611 | module.exports = Cancel; 1612 | 1613 | 1614 | /***/ }), 1615 | /* 24 */ 1616 | /***/ (function(module, exports, __webpack_require__) { 1617 | 1618 | 'use strict'; 1619 | 1620 | var Cancel = __webpack_require__(23); 1621 | 1622 | /** 1623 | * A `CancelToken` is an object that can be used to request cancellation of an operation. 1624 | * 1625 | * @class 1626 | * @param {Function} executor The executor function. 1627 | */ 1628 | function CancelToken(executor) { 1629 | if (typeof executor !== 'function') { 1630 | throw new TypeError('executor must be a function.'); 1631 | } 1632 | 1633 | var resolvePromise; 1634 | this.promise = new Promise(function promiseExecutor(resolve) { 1635 | resolvePromise = resolve; 1636 | }); 1637 | 1638 | var token = this; 1639 | executor(function cancel(message) { 1640 | if (token.reason) { 1641 | // Cancellation has already been requested 1642 | return; 1643 | } 1644 | 1645 | token.reason = new Cancel(message); 1646 | resolvePromise(token.reason); 1647 | }); 1648 | } 1649 | 1650 | /** 1651 | * Throws a `Cancel` if cancellation has been requested. 1652 | */ 1653 | CancelToken.prototype.throwIfRequested = function throwIfRequested() { 1654 | if (this.reason) { 1655 | throw this.reason; 1656 | } 1657 | }; 1658 | 1659 | /** 1660 | * Returns an object that contains a new `CancelToken` and a function that, when called, 1661 | * cancels the `CancelToken`. 1662 | */ 1663 | CancelToken.source = function source() { 1664 | var cancel; 1665 | var token = new CancelToken(function executor(c) { 1666 | cancel = c; 1667 | }); 1668 | return { 1669 | token: token, 1670 | cancel: cancel 1671 | }; 1672 | }; 1673 | 1674 | module.exports = CancelToken; 1675 | 1676 | 1677 | /***/ }), 1678 | /* 25 */ 1679 | /***/ (function(module, exports) { 1680 | 1681 | 'use strict'; 1682 | 1683 | /** 1684 | * Syntactic sugar for invoking a function and expanding an array for arguments. 1685 | * 1686 | * Common use case would be to use `Function.prototype.apply`. 1687 | * 1688 | * ```js 1689 | * function f(x, y, z) {} 1690 | * var args = [1, 2, 3]; 1691 | * f.apply(null, args); 1692 | * ``` 1693 | * 1694 | * With `spread` this example can be re-written. 1695 | * 1696 | * ```js 1697 | * spread(function(x, y, z) {})([1, 2, 3]); 1698 | * ``` 1699 | * 1700 | * @param {Function} callback 1701 | * @returns {Function} 1702 | */ 1703 | module.exports = function spread(callback) { 1704 | return function wrap(arr) { 1705 | return callback.apply(null, arr); 1706 | }; 1707 | }; 1708 | 1709 | 1710 | /***/ }) 1711 | /******/ ]) 1712 | }); 1713 | ; 1714 | //# sourceMappingURL=axios.map -------------------------------------------------------------------------------- /djvue/static/vue/js/script.js: -------------------------------------------------------------------------------- 1 | let nonFieldErrorsKey = "non_field_errors" 2 | let detailErrorKey = "detail" 3 | let errorKeys = [nonFieldErrorsKey, detailErrorKey] 4 | 5 | let djVueMixin = { 6 | delimiters: ["{(", ")}"], 7 | data() { 8 | return { 9 | options: {}, 10 | nonFieldErrors: [], 11 | actionURL: null, 12 | form: {}, 13 | files: {}, 14 | fileUploadURL: uploadURL 15 | } 16 | }, 17 | mounted() { 18 | axios.options(this.actionURL).then((response) => { 19 | this.options = response.data.actions.POST 20 | }) 21 | }, 22 | methods: { 23 | getFieldsetRefs() { 24 | return Object.keys(this.form).filter(key => _.isPlainObject(this.form[key])) 25 | }, 26 | getFormsetRefs() { 27 | return Object.keys(this.form).filter(key => _.isArray(this.form[key])) 28 | }, 29 | getFormData() { 30 | return this.form 31 | }, 32 | submit() { 33 | // reset errors 34 | this.nonFieldErrors = [] 35 | axios.post(this.actionURL, Object.assign({}, this.getFormData(), this.files)) 36 | .then(this.success) 37 | .catch(this.error) 38 | }, 39 | success(response) { 40 | alert("You must implemented success method.") 41 | }, 42 | error(error, ref = "form") { 43 | 44 | if (error.response && error.response.status === 400) { 45 | let errors = error.response.data 46 | this.renderFieldErrors(errors, ref) 47 | } else { 48 | this.nonFieldErrors.push("Error! Contact an administrator.") 49 | } 50 | }, 51 | renderFieldErrors(errors, ref = "form") { 52 | if (_.has(errors, "detail")) { 53 | // rest framework _detail_ error: IE: object not found 54 | this.nonFieldErrors.push(errors["detail"]) 55 | } else if (_.has(errors, nonFieldErrorsKey)) { 56 | // rest framework _non_field_errors_: global error 57 | this.nonFieldErrors = errors[nonFieldErrorsKey] 58 | } else if (!_.has(errors, nonFieldErrorsKey)) { 59 | // fieldset errors 60 | this.renderFieldsetErrors(errors) 61 | // formset errors 62 | this.renderFormsetErrors(errors) 63 | 64 | // field errors 65 | // remove all keys that are having . or values are {} 66 | // to avoid double rendering of nested errors 67 | let fieldErrors = {} 68 | 69 | Object.keys(errors).forEach(key=> { 70 | if (!key.includes('.') && _.isArray(errors[key])) fieldErrors[key] = errors[key] 71 | }) 72 | 73 | this.$refs[ref].setErrors(fieldErrors) 74 | } 75 | }, 76 | 77 | renderFieldsetErrors(errors) { 78 | this.getFieldsetRefs().forEach(fieldsetRef => { 79 | if (errors.hasOwnProperty(fieldsetRef)) { 80 | // append the parent name to the field name 81 | let fieldsetErrors = errors[fieldsetRef] 82 | Object.keys(fieldsetErrors).forEach(key => { 83 | if (!errorKeys.includes(key)) { 84 | fieldsetErrors[`${fieldsetRef}.${key}`] = fieldsetErrors[key] 85 | delete fieldsetErrors[key] 86 | } 87 | }) 88 | 89 | // raise field errors 90 | this.$refs[fieldsetRef].setErrors(fieldsetErrors) 91 | // raise global errors like non_field_errors 92 | this.renderFieldErrors(fieldsetErrors, fieldsetRef) 93 | } 94 | }) 95 | }, 96 | 97 | renderFormsetErrors(errors) { 98 | this.getFormsetRefs().forEach(formsetItem => { 99 | // if errors[formsetItem] is object, means 100 | // the error is fieldset related and not for 101 | // the formsets, so skip it quickly 102 | if (errors.hasOwnProperty(formsetItem)) { 103 | let formsetErrors = errors[formsetItem] 104 | 105 | // append index to each field name 106 | formsetErrors.forEach((item, index) => { 107 | Object.keys(item).map((key) => { 108 | if (!errorKeys.includes(key)) { 109 | item[`${key}-${index}`] = item[key] 110 | delete item[key] 111 | } 112 | }) 113 | this.$refs[formsetItem].setErrors(item) 114 | this.renderFieldErrors(item, formsetItem) 115 | }) 116 | } 117 | }) 118 | }, 119 | uploadFile(event, url, multiple=false) { 120 | let formData = new FormData() 121 | // save vue instance for being able to reference it later. 122 | const vm = this 123 | // clear the errors 124 | this.$refs.form.setErrors({[event.target.name]: []}) 125 | 126 | let uploadURL = Boolean(url) ? url: this.fileUploadURL 127 | uploadURL = `${uploadURL}?field-name=${event.target.name}` 128 | 129 | formData.append("file", event.target.files[0]) 130 | axios 131 | .post(uploadURL, formData, { 132 | headers: { 133 | "Content-Type": "multipart/form-data", 134 | }, 135 | }) 136 | .then(({ data }) => { 137 | // save details on the form data which will be sent to the server 138 | if (multiple) { 139 | let current_files = [] 140 | if (event.target.name in vm.files) { 141 | current_files = vm.files[event.target.name] 142 | } 143 | current_files.push(data); 144 | Vue.set(vm.files, event.target.name, current_files) 145 | 146 | } else { 147 | Vue.set(vm.files, event.target.name, data) 148 | vm.$emit('uploadedFile', event) 149 | } 150 | 151 | }) 152 | .catch((error) => { 153 | // remove the file from the input 154 | event.target.value = null 155 | vm.error(error) 156 | }) 157 | }, 158 | }, 159 | } 160 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/checkbox.html: -------------------------------------------------------------------------------- 1 | 5 |
6 | 16 |
17 | 18 | {( error )} 19 | 20 | {% if field.help_text %} 21 | {{ field.help_text|safe }} 22 | {% endif %} 23 |
-------------------------------------------------------------------------------- /djvue/templates/vue/default/checkbox_multiple.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 7 | {% if field.label %} 8 | 9 | {% endif %} 10 | 11 | {% if style.inline %} 12 |
13 | {% for key, text in field.choices|items %} 14 | 24 | {% endfor %} 25 |
26 | {% else %} 27 | {% for key, text in field.choices|items %} 28 |
29 | 39 |
40 | {% endfor %} 41 | {% endif %} 42 | 43 | {( error )} 44 | 45 | {% if field.help_text %} 46 | {{ field.help_text|safe }} 47 | {% endif %} 48 | 49 |
50 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/dict_field.html: -------------------------------------------------------------------------------- 1 |
2 | {% if field.label %} 3 | 4 | {% endif %} 5 | 6 |

Dictionaries are not currently supported in HTML input.

7 |
8 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/fieldset.html: -------------------------------------------------------------------------------- 1 | {% load vue_tags %} 2 | 3 |
4 | {% if field.label %} 5 | 6 | {{ field.label }} 7 | 8 | {% if field.required %} 9 | {# add validation-provider for showing the serialier errors #} 10 | 11 | {( error )} 12 | 13 | {% endif %} 14 | {% endif %} 15 | 16 | 17 | {% for nested_field in field %} 18 | {% if not nested_field.read_only %} 19 | {% render_vue_field nested_field style=style %} 20 | {% endif %} 21 | {% endfor %} 22 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/file.html: -------------------------------------------------------------------------------- 1 | 5 | {% if field.label %} 6 | 9 | {% endif %} 10 |
11 | 19 | {( error )} 20 | 21 | {% if field.help_text %} 22 | {{ field.help_text|safe }} 23 | {% endif %} 24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/file_multiple.html: -------------------------------------------------------------------------------- 1 | 5 | {% if field.label %} 6 | 9 | {% endif %} 10 |
11 | 19 | {( error )} 20 | 21 |
    22 |
  • {( file.filename )}
  • 23 |
24 | 25 | {% if field.help_text %} 26 | {{ field.help_text|safe }} 27 | {% endif %} 28 |
29 |
30 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/form.html: -------------------------------------------------------------------------------- 1 | {% load vue_tags %} 2 | {% for field in form %} 3 | {% if not field.read_only %} 4 | {% render_vue_field field style=style %} 5 | {% endif %} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/input.html: -------------------------------------------------------------------------------- 1 | 5 | {% if field.label %} 6 | 9 | {% endif %} 10 |
11 | 20 | {( error )} 21 | 22 | {% if field.help_text %} 23 | {{ field.help_text|safe }} 24 | {% endif %} 25 |
26 |
-------------------------------------------------------------------------------- /djvue/templates/vue/default/list_field.html: -------------------------------------------------------------------------------- 1 |
2 | {% if field.label %} 3 | 4 | {% endif %} 5 | 6 |

Lists are not currently supported in HTML input.

7 |
8 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/list_fieldset.html: -------------------------------------------------------------------------------- 1 |
2 | {% if field.label %} 3 | 4 | {{ field.label }} 5 | 6 | {% endif %} 7 | 8 |

Lists are not currently supported in HTML input.

9 |
10 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/radio.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load rest_framework %} 3 | {% trans "None" as none_choice %} 4 | 5 | 9 | {% if field.label %} 10 | 13 | {% endif %} 14 | 15 | {% if style.inline %} 16 |
17 | {% if field.allow_null or field.allow_blank %} 18 | 28 | {% endif %} 29 | 30 | {% for key, text in field.choices|items %} 31 | 41 | {% endfor %} 42 |
43 | {% else %} 44 | {% if field.allow_null or field.allow_blank %} 45 |
46 | 56 |
57 | {% endif %} 58 | 59 | {% for key, text in field.choices|items %} 60 |
61 | 71 |
72 | {% endfor %} 73 | {% endif %} 74 | 75 | {( error )} 76 | 77 | {% if field.help_text %} 78 | {{ field.help_text|safe }} 79 | {% endif %} 80 |
81 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/select.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 7 | {% if field.label %} 8 | 11 | {% endif %} 12 | 13 | 34 | 35 | {( error )} 36 | 37 | {% if field.help_text %} 38 | {{ field.help_text|safe }} 39 | {% endif %} 40 | 41 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/select_multiple.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load rest_framework %} 3 | {% trans "No items to select." as no_items %} 4 | 5 | 9 | {% if field.label %} 10 | 13 | {% endif %} 14 | 15 | 34 | 35 | {( error )} 36 | 37 | {% if field.help_text %} 38 | {{ field.help_text|safe }} 39 | {% endif %} 40 | 41 | -------------------------------------------------------------------------------- /djvue/templates/vue/default/textarea.html: -------------------------------------------------------------------------------- 1 | 5 | {% if field.label %} 6 | 9 | {% endif %} 10 | 11 | 22 | {( error )} 23 | 24 | {% if field.help_text %} 25 | {{ field.help_text|safe }} 26 | {% endif %} 27 | 28 | -------------------------------------------------------------------------------- /djvue/templates/vue/starter.html: -------------------------------------------------------------------------------- 1 | {% load static i18n %} 2 | 3 | 6 | 7 | 8 | 9 | 10 | 29 | 30 | -------------------------------------------------------------------------------- /djvue/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KUWAITNET/djvue/1c5ee08f57446b7a7c3ee96c314061929df56c2d/djvue/templatetags/__init__.py -------------------------------------------------------------------------------- /djvue/templatetags/vue_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.utils.module_loading import import_string 3 | from django.utils.translation import get_language 4 | 5 | from rest_framework.renderers import HTMLFormRenderer 6 | 7 | from djvue.defaults import VV_LOCALE_PATH 8 | 9 | register = template.Library() 10 | 11 | 12 | @register.simple_tag 13 | def render_vue_field(field, style=None, template_pack="vue/default/"): 14 | style = {} if style is None else style 15 | if template_pack: 16 | style["template_pack"] = template_pack 17 | renderer = style.get("renderer", HTMLFormRenderer()) 18 | return renderer.render_field(field, style) 19 | 20 | 21 | @register.simple_tag 22 | def render_vue_form(serializer, template_pack=None): 23 | style = {"template_pack": template_pack or "vue/default"} 24 | renderer = HTMLFormRenderer() 25 | return renderer.render(serializer.data, None, {"style": style}) 26 | 27 | 28 | @register.inclusion_tag("vue/starter.html") 29 | def vue_starter(): 30 | vv_locale = import_string(VV_LOCALE_PATH) 31 | lang = get_language() 32 | ret = { 33 | "vv_language": lang, 34 | } 35 | try: 36 | ret["vv_locale"] = vv_locale[lang] 37 | except KeyError: 38 | pass 39 | return ret 40 | -------------------------------------------------------------------------------- /djvue/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import FileUploadView 4 | 5 | app_name = "djvue" 6 | 7 | urlpatterns = [ 8 | path("hcfljiqzeb/", view=FileUploadView.as_view(), name="file_upload"), 9 | ] 10 | -------------------------------------------------------------------------------- /djvue/validators.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import ugettext as _ 2 | 3 | from rest_framework.exceptions import ValidationError 4 | 5 | 6 | class RequiredFileValidator: 7 | requires_context = True 8 | 9 | def __call__(self, value, serializer_field): 10 | if serializer_field.required and type(value) == dict and not all(list(value.values())): 11 | raise ValidationError(_("This field is required.")) 12 | 13 | if serializer_field.required and type(value) == list and len(value) == 0: 14 | raise ValidationError(_("This field is required.")) 15 | -------------------------------------------------------------------------------- /djvue/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.settings import api_settings 3 | from rest_framework.response import Response 4 | from rest_framework.views import APIView 5 | 6 | from .serializers import FileUploadSerializer 7 | 8 | 9 | class FileUploadView(APIView): 10 | """ 11 | Generic file upload view. 12 | To be subclassed when different behaviour is requested. 13 | IE: 14 | - to add permissions, file upload shouldn't be public, only for specific cases 15 | - handle multiple file uploads at once 16 | - handle directory upload 17 | - etc 18 | """ 19 | 20 | serializer_class = FileUploadSerializer 21 | 22 | def post(self, request, *args, **kwargs): 23 | serializer = self.get_serializer(data=request.data) 24 | serializer.is_valid(raise_exception=True) 25 | data = serializer.save() 26 | headers = self.get_success_headers(data) 27 | return Response(data, status=status.HTTP_201_CREATED, headers=headers) 28 | 29 | def get_success_headers(self, data): 30 | try: 31 | return {"Location": str(data[api_settings.URL_FIELD_NAME])} 32 | except (TypeError, KeyError): 33 | return {} 34 | 35 | def get_serializer(self, *args, **kwargs): 36 | """ 37 | Return the serializer instance that should be used for validating and 38 | deserializing input, and for serializing output. 39 | """ 40 | serializer_class = self.get_serializer_class() 41 | kwargs["context"] = self.get_serializer_context() 42 | return serializer_class(*args, **kwargs) 43 | 44 | def get_serializer_class(self): 45 | """ 46 | Return the class to use for the serializer. 47 | Defaults to using `self.serializer_class`. 48 | 49 | You may want to override this if you need to provide different 50 | serializations depending on the incoming request. 51 | 52 | (Eg. admins get full serialization, others get basic serialization) 53 | """ 54 | assert self.serializer_class is not None, ( 55 | "'%s' should either include a `serializer_class` attribute, " 56 | "or override the `get_serializer_class()` method." % self.__class__.__name__ 57 | ) 58 | 59 | return self.serializer_class 60 | 61 | def get_serializer_context(self): 62 | """ 63 | Extra context provided to the serializer class. 64 | """ 65 | return {"request": self.request, "format": self.format_kwarg, "view": self} 66 | -------------------------------------------------------------------------------- /djvue/vv_locale.py: -------------------------------------------------------------------------------- 1 | # Parent key is matching the django language. 2 | # Child key is matching the VeeValidate lang codes 3 | # https://logaretm.github.io/vee-validate/guide/localization.html#using-the-default-i18n 4 | 5 | vv_locale = { 6 | "ar": { 7 | "ar": { 8 | "messages": { 9 | "alpha": "{_field_} يجب ان يحتوي على حروف فقط", 10 | "alpha_num": "{_field_} قد يحتوي فقط على حروف وارقام", 11 | "alpha_dash": "{_field_} قد يحتوي على حروف او الرموز - و _", 12 | "alpha_spaces": "{_field_} قد يحتوي فقط على حروف ومسافات", 13 | "between": "قيمة {_field_} يجب ان تكون ما بين {min} و {max}", 14 | "confirmed": "{_field_} لا يماثل التأكيد", 15 | "digits": "{_field_} يجب ان تحتوي فقط على ارقام والا يزيد عددها عن {length} رقم", 16 | "dimensions": "{_field_} يجب ان تكون بمقاس {width} بكسل في {height} بكسل", 17 | "email": "{_field_} يجب ان يكون بريدا اليكتروني صحيح", 18 | "excluded": "الحقل {_field_} غير صحيح", 19 | "ext": "نوع ملف {_field_} غير صحيح", 20 | "image": "{_field_} يجب ان تكون صورة", 21 | "integer": "الحقل {_field_} يجب ان يكون عدداً صحيحاً", 22 | "length": "حقل {_field_} يجب الا يزيد عن {length}", 23 | "max_value": "قيمة الحقل {_field_} يجب ان تكون اصغر من {min} او تساويها", 24 | "max": "الحقل {_field_} يجب ان يحتوي على {length} حروف على الأكثر", 25 | "mimes": "نوع ملف {_field_} غير صحيح", 26 | "min_value": "قيمة الحقل {_field_} يجب ان تكون اكبر من {min} او تساويها", 27 | "min": "الحقل {_field_} يجب ان يحتوي على {length} حروف على الأقل", 28 | "numeric": "{_field_} يمكن ان يحتوي فقط على ارقام", 29 | "oneOf": "الحقل {_field_} يجب ان يكون قيمة صحيحة", 30 | "regex": "الحقل {_field_} غير صحيح", 31 | "required": "{_field_} مطلوب", 32 | "required_if": "حقل {_field_} مطلوب", 33 | "size": "{_field_} يجب ان يكون اقل من {size} كيلوبايت", 34 | } 35 | } 36 | }, 37 | "en-us": { 38 | "en": { 39 | "messages": { 40 | "alpha": "The {_field_} field may only contain alphabetic characters", 41 | "alpha_num": "The {_field_} field may only contain alpha-numeric characters", 42 | "alpha_dash": "The {_field_} field may contain alpha-numeric characters as well as dashes and underscores", 43 | "alpha_spaces": "The {_field_} field may only contain alphabetic characters as well as spaces", 44 | "between": "The {_field_} field must be between {min} and {max}", 45 | "confirmed": "The {_field_} field confirmation does not match", 46 | "digits": "The {_field_} field must be numeric and exactly contain {length} digits", 47 | "dimensions": "The {_field_} field must be {width} pixels by {height} pixels", 48 | "email": "The {_field_} field must be a valid email", 49 | "excluded": "The {_field_} field is not a valid value", 50 | "ext": "The {_field_} field is not a valid file", 51 | "image": "The {_field_} field must be an image", 52 | "integer": "The {_field_} field must be an integer", 53 | "length": "The {_field_} field must be {length} long", 54 | "max_value": "The {_field_} field must be {max} or less", 55 | "max": "The {_field_} field may not be greater than {length} characters", 56 | "mimes": "The {_field_} field must have a valid file type", 57 | "min_value": "The {_field_} field must be {min} or more", 58 | "min": "The {_field_} field must be at least {length} characters", 59 | "numeric": "The {_field_} field may only contain numeric characters", 60 | "oneOf": "The {_field_} field is not a valid value", 61 | "regex": "The {_field_} field format is invalid", 62 | "required_if": "The {_field_} field is required", 63 | "required": "The {_field_} field is required", 64 | "size": "The {_field_} field size must be less than {size}KB", 65 | } 66 | } 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ##Example Project for djvue 2 | 3 | This example is provided as a convenience feature to allow potential users to try the app straight from the app repo without having to create a django project. 4 | 5 | It can also be used to develop the app in place. 6 | 7 | To run this example, follow these instructions: 8 | 9 | 1. Navigate to the `tests/example` directory 10 | 2. Install the requirements for the package: 11 | 12 | pip install -r requirements.txt 13 | 14 | 3. Make and apply migrations 15 | 16 | python manage.py makemigrations 17 | 18 | python manage.py migrate 19 | 20 | 4. Run the server 21 | 22 | python manage.py runserver 23 | 24 | 5. Access from the browser at `http://127.0.0.1:8000` 25 | -------------------------------------------------------------------------------- /example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KUWAITNET/djvue/1c5ee08f57446b7a7c3ee96c314061929df56c2d/example/__init__.py -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KUWAITNET/djvue/1c5ee08f57446b7a7c3ee96c314061929df56c2d/example/example/__init__.py -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | """example URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | 19 | 20 | urlpatterns = [ 21 | url(r'^admin/', admin.site.urls), 22 | url(r'', include('djvue.urls')), 23 | url(r'', include('example.urls')) 24 | ] 25 | -------------------------------------------------------------------------------- /example/example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /example/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2020-07-20 14:22 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Profile', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('username', models.CharField(max_length=64)), 20 | ('email', models.EmailField(max_length=254)), 21 | ('password', models.CharField(max_length=64)), 22 | ('file', models.FileField(upload_to='profiles/')), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='Address', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('name', models.CharField(max_length=64)), 30 | ('country', models.CharField(choices=[('ro', 'Romania'), ('kw', 'Kuwait')], max_length=2)), 31 | ('zip_code', models.CharField(max_length=12)), 32 | ('address', models.TextField(blank=True, default='')), 33 | ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='example.Profile')), 34 | ], 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /example/migrations/0002_profileattachment.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-10-07 22:03 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('example', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ProfileAttachment', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('file', models.FileField(upload_to='profiles/')), 19 | ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='example.profile')), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /example/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KUWAITNET/djvue/1c5ee08f57446b7a7c3ee96c314061929df56c2d/example/migrations/__init__.py -------------------------------------------------------------------------------- /example/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Profile(models.Model): 5 | username = models.CharField(max_length=64) 6 | email = models.EmailField() 7 | # do not do this 8 | password = models.CharField(max_length=64) 9 | file = models.FileField(upload_to="profiles/") 10 | 11 | def multiple_file(self): 12 | return [attachment for attachment in self.profileattachment_set.all()] 13 | 14 | 15 | class ProfileAttachment(models.Model): 16 | file = models.FileField(upload_to="profiles/") 17 | profile = models.ForeignKey(Profile, on_delete=models.CASCADE) 18 | 19 | 20 | class Address(models.Model): 21 | profile = models.ForeignKey(Profile, on_delete=models.CASCADE) 22 | name = models.CharField(max_length=64) 23 | country = models.CharField( 24 | max_length=2, choices=(("ro", "Romania"), ("kw", "Kuwait")) 25 | ) 26 | zip_code = models.CharField(max_length=12) 27 | address = models.TextField(blank=True, default="") 28 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | # Your app requirements. 2 | -r ../requirements_dev.txt 3 | 4 | # Your app in editable mode. 5 | -e ../ 6 | -------------------------------------------------------------------------------- /example/serializers.py: -------------------------------------------------------------------------------- 1 | from django.core.files.base import ContentFile 2 | from django.core.validators import FileExtensionValidator 3 | from django.urls import reverse_lazy 4 | 5 | from rest_framework import serializers 6 | 7 | from djvue.fields import FileField, ListFileField 8 | from djvue.serializers import FileUploadSerializer 9 | from example.models import Address, Profile, ProfileAttachment 10 | 11 | 12 | class LoginSerializer(serializers.Serializer): 13 | email = serializers.EmailField(required=True) 14 | password = serializers.CharField(write_only=True, style={"input_type": "password"}) 15 | 16 | # def validate_email(self, value): 17 | # raise serializers.ValidationError("Invalid email!") 18 | 19 | def create(self, validated_data): 20 | return validated_data 21 | 22 | 23 | class AddressSerializer(serializers.ModelSerializer): 24 | class Meta: 25 | model = Address 26 | fields = ("name", "country", "zip_code", "address") 27 | list_serializer_class = serializers.ListSerializer 28 | 29 | 30 | class WorkSerializer(serializers.Serializer): 31 | CHOICES = ( 32 | ("cc", "Chocolate Tested"), 33 | ("dreamer", "Dreamer"), 34 | ("sp", "Smokes packing"), 35 | ) 36 | job = serializers.ChoiceField(choices=CHOICES) 37 | position = serializers.CharField(required=False) 38 | 39 | # def validate_position(self, value): 40 | # raise serializers.ValidationError("Invalid Foo") 41 | # 42 | # def validate(self, attrs): 43 | # raise serializers.ValidationError("Invalid job!") 44 | 45 | 46 | class ProfileSerializer(serializers.ModelSerializer): 47 | username = serializers.CharField(max_length=25, min_length=3, required=True,) 48 | email = serializers.EmailField(required=True) 49 | password1 = serializers.CharField( 50 | write_only=True, 51 | style={"input_type": "password", "rules": "password:@password2"}, 52 | ) 53 | password2 = serializers.CharField(write_only=True, style={"input_type": "password"}) 54 | multiple_file = ListFileField(required=True) 55 | multiple_pdf = ListFileField(style={"upload_url": reverse_lazy("example:pdf_upload")}) 56 | file = FileField(required=True, style={"upload_url": reverse_lazy("example:pdf_upload")}) 57 | # cv = FileField(required=True, style={"upload_url": reverse_lazy("example:pdf_upload")}) 58 | # file = FileField(required=True) 59 | working_place = WorkSerializer(write_only=True) 60 | # addresses = AddressSerializer(many=True) 61 | 62 | class Meta: 63 | model = Profile 64 | fields = ( 65 | "username", 66 | "email", 67 | "password1", 68 | "password2", 69 | "file", 70 | "multiple_file", 71 | "multiple_pdf", 72 | "file", 73 | # "cv", 74 | "working_place", 75 | ) 76 | 77 | def create(self, validated_data): 78 | validated_data["password"] = validated_data.pop("password1") 79 | validated_data.pop("password2") 80 | validated_data.pop( 81 | "working_place" 82 | ) # not required, added only for example purpose 83 | user_file = validated_data.pop("file", None) 84 | user_multiple_file = validated_data.pop("multiple_file", None) 85 | profile = Profile(**validated_data) 86 | profile.save() 87 | # # fetch the file from temporary dir 88 | if user_file is not None and all( 89 | [user_file.get("path", False), user_file.get("filename", False)] 90 | ): 91 | with open(user_file["path"], "rb") as f: 92 | profile.file.save(user_file["filename"], f) 93 | if user_multiple_file is not None and all( 94 | [a_file.get("path", False) and a_file.get("filename", False) for a_file in user_multiple_file] 95 | ): 96 | for a_file in user_multiple_file: 97 | with open(a_file["path"], "rb") as f: 98 | ProfileAttachment.objects.create(profile=profile, file=ContentFile(f.read(), a_file["filename"])) 99 | return profile 100 | 101 | 102 | class PDFUploadSerializer(FileUploadSerializer): 103 | """ 104 | Allows only PDF files to be uploaded 105 | """ 106 | def __init__(self, *args, **kwargs): 107 | super().__init__(*args, **kwargs) 108 | self.fields["file"].validators.append(FileExtensionValidator(allowed_extensions=['pdf'])) 109 | -------------------------------------------------------------------------------- /example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example project. 3 | 4 | Generated by Cookiecutter Django Package 5 | """ 6 | 7 | import os 8 | 9 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 10 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 11 | 12 | # Quick-start development settings - unsuitable for production 13 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 14 | 15 | # SECURITY WARNING: keep the secret key used in production secret! 16 | SECRET_KEY = "hms1@x5^=zpudipur)%r9syfdobv=xybj9g+ub8w=9h!gljlw@" 17 | 18 | # SECURITY WARNING: don't run with debug turned on in production! 19 | DEBUG = True 20 | 21 | ALLOWED_HOSTS = [] 22 | 23 | # Application definition 24 | 25 | INSTALLED_APPS = [ 26 | 'django.contrib.admin', 27 | 'django.contrib.auth', 28 | 'django.contrib.contenttypes', 29 | 'django.contrib.sessions', 30 | 'django.contrib.messages', 31 | 'django.contrib.staticfiles', 32 | 33 | 'rest_framework', 34 | 'djvue', 35 | 'example', 36 | 37 | # if your app has other dependencies that need to be added to the site 38 | # they should be added here 39 | ] 40 | 41 | MIDDLEWARE = [ 42 | 'django.middleware.security.SecurityMiddleware', 43 | 'django.contrib.sessions.middleware.SessionMiddleware', 44 | 'django.middleware.common.CommonMiddleware', 45 | 'django.middleware.csrf.CsrfViewMiddleware', 46 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 47 | 'django.contrib.messages.middleware.MessageMiddleware', 48 | # "django.middleware.locale.LocaleMiddleware", 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 50 | ] 51 | 52 | ROOT_URLCONF = 'example.example.urls' 53 | 54 | TEMPLATES = [ 55 | { 56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 57 | 'DIRS': [os.path.join(BASE_DIR, 'tests', 'templates'), ], 58 | 'APP_DIRS': True, 59 | 'OPTIONS': { 60 | 'context_processors': [ 61 | "django.contrib.auth.context_processors.auth", 62 | "django.contrib.messages.context_processors.messages", 63 | "django.template.context_processors.i18n", 64 | "django.template.context_processors.debug", 65 | "django.template.context_processors.request", 66 | "django.template.context_processors.media", 67 | "django.template.context_processors.csrf", 68 | "django.template.context_processors.tz", 69 | "django.template.context_processors.static", 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = 'example.example.wsgi.application' 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 79 | 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.sqlite3', 83 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 84 | } 85 | } 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 102 | }, 103 | ] 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | 123 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 124 | 125 | 126 | REST_FRAMEWORK = { 127 | 'DEFAULT_PERMISSION_CLASSES': [ 128 | # 'rest_framework.permissions.IsAuthenticated' 129 | ], 130 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 131 | 'rest_framework.authentication.SessionAuthentication', 132 | 'rest_framework.authentication.TokenAuthentication', 133 | ), 134 | 135 | 'DEFAULT_RENDERER_CLASSES': ( 136 | 'rest_framework.renderers.TemplateHTMLRenderer', 137 | 'rest_framework.renderers.JSONRenderer', 138 | ), 139 | 140 | 'DEFAULT_PARSER_CLASSES': ( 141 | 'rest_framework.parsers.JSONParser', 142 | 'rest_framework.parsers.FormParser', 143 | 'rest_framework.parsers.MultiPartParser' 144 | ), 145 | 146 | 'NON_FIELD_ERRORS_KEY': 'non_field_errors', 147 | 148 | } 149 | -------------------------------------------------------------------------------- /example/static/css/main.css: -------------------------------------------------------------------------------- 1 | ul { 2 | list-style-type: none; 3 | } 4 | -------------------------------------------------------------------------------- /example/static/js/script.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KUWAITNET/djvue/1c5ee08f57446b7a7c3ee96c314061929df56c2d/example/static/js/script.js -------------------------------------------------------------------------------- /example/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static vue_tags %} 2 | 3 | 4 | 5 | 6 | 7 | DjVue Example 8 | 9 | 10 | 11 | {# Global stylesheets #} 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | {% block content %}{% endblock %} 22 |
23 | 24 | 27 | 30 | 33 | 34 | {% vue_starter %} 35 | 36 | {% block extra_js %}{% endblock %} 37 | 38 | 39 | -------------------------------------------------------------------------------- /example/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load vue_tags %} 3 | 4 | {% block content %} 5 | 6 | 27 | 28 | {% endblock %} 29 | 30 | {% block extra_js %} 31 | 32 | 51 | 52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /example/templates/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load vue_tags %} 3 | 4 | {% block content %} 5 | 33 | {% endblock %} 34 | 35 | {% block extra_js %} 36 | 37 | 64 | 65 | {% endblock %} 66 | -------------------------------------------------------------------------------- /example/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from django.urls import path, include 5 | from .views import LoginView, ProfileView, PDFUploadView 6 | 7 | app_name = "example" 8 | 9 | urlpatterns = [ 10 | path('login/', LoginView.as_view(), name='login'), 11 | path('profile/', ProfileView.as_view(), name='profile'), 12 | path('pdf-upload/', PDFUploadView.as_view(), name="pdf_upload") 13 | ] 14 | -------------------------------------------------------------------------------- /example/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import CreateAPIView 2 | from rest_framework.renderers import TemplateHTMLRenderer, JSONRenderer 3 | from rest_framework.response import Response 4 | from rest_framework.permissions import IsAuthenticated, AllowAny 5 | from djvue.views import FileUploadView 6 | 7 | from .serializers import LoginSerializer, ProfileSerializer, PDFUploadSerializer 8 | 9 | 10 | class PDFUploadView(FileUploadView): 11 | permission_classes = (AllowAny,) 12 | serializer_class = PDFUploadSerializer 13 | 14 | 15 | class LoginView(CreateAPIView): 16 | renderer_classes = [TemplateHTMLRenderer, JSONRenderer] 17 | serializer_class = LoginSerializer 18 | # permission_classes = (IsAuthenticated,) 19 | permission_classes = (AllowAny,) 20 | template_name = "login.html" 21 | 22 | def get(self, request, *args, **kwargs): 23 | """ 24 | Used only to serve the serializer definition 25 | """ 26 | serializer = self.get_serializer() 27 | data = {"serializer": serializer} 28 | return Response(data) 29 | 30 | 31 | class ProfileView(CreateAPIView): 32 | renderer_classes = [TemplateHTMLRenderer, JSONRenderer] 33 | serializer_class = ProfileSerializer 34 | permission_classes = (AllowAny,) 35 | template_name = "profile.html" 36 | 37 | def get(self, request, *args, **kwargs): 38 | """ 39 | Used only to serve the serializer definition 40 | """ 41 | serializer = self.get_serializer() 42 | data = {"serializer": serializer} 43 | return Response(data) 44 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import unicode_literals, absolute_import 4 | 5 | import os 6 | import sys 7 | 8 | if __name__ == "__main__": 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 10 | from django.core.management import execute_from_command_line 11 | 12 | execute_from_command_line(sys.argv) 13 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | bumpversion==0.5.3 2 | wheel==0.30.0 3 | djangorestframework==3.11.0 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.2.1 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:djvue/__init__.py] 7 | 8 | [wheel] 9 | universal = 1 10 | 11 | [flake8] 12 | ignore = D203 13 | exclude = 14 | djvue/migrations, 15 | .git, 16 | .tox, 17 | docs/conf.py, 18 | build, 19 | dist 20 | max-line-length = 119 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | import sys 6 | 7 | try: 8 | from setuptools import setup 9 | except ImportError: 10 | from distutils.core import setup 11 | 12 | 13 | def get_version(*file_paths): 14 | """Retrieves the version from djvue/__init__.py""" 15 | filename = os.path.join(os.path.dirname(__file__), *file_paths) 16 | version_file = open(filename).read() 17 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 18 | version_file, re.M) 19 | if version_match: 20 | return version_match.group(1) 21 | raise RuntimeError('Unable to find version string.') 22 | 23 | 24 | version = get_version("djvue", "__init__.py") 25 | 26 | 27 | if sys.argv[-1] == 'publish': 28 | try: 29 | import wheel 30 | print("Wheel version: ", wheel.__version__) 31 | except ImportError: 32 | print('Wheel library missing. Please run "pip install wheel"') 33 | sys.exit() 34 | os.system('python setup.py sdist upload') 35 | os.system('python setup.py bdist_wheel upload') 36 | sys.exit() 37 | 38 | if sys.argv[-1] == 'tag': 39 | print("Tagging the version on git:") 40 | os.system("git tag -a %s -m 'version %s'" % (version, version)) 41 | os.system("git push --tags") 42 | sys.exit() 43 | 44 | readme = open('README.rst').read() 45 | history = open('HISTORY.rst').read().replace('.. :changelog:', '') 46 | 47 | setup( 48 | name='djvue', 49 | version=version, 50 | description=""" Mix Vue.js with DRF form renderer in Django templates""", 51 | long_description=readme + '\n\n' + history, 52 | author='Dacian Popute', 53 | author_email='dacian@kuwaitnet.com', 54 | url='https://github.com/KuwaitNET/djvue', 55 | packages=[ 56 | 'djvue', 57 | ], 58 | include_package_data=True, 59 | install_requires=[], 60 | license="MIT", 61 | zip_safe=False, 62 | keywords='djvue', 63 | classifiers=[ 64 | 'Development Status :: 3 - Alpha', 65 | 'Framework :: Django :: 2.2', 66 | 'Intended Audience :: Developers', 67 | 'License :: OSI Approved :: BSD License', 68 | 'Natural Language :: English', 69 | 'Programming Language :: Python :: 2', 70 | 'Programming Language :: Python :: 2.7', 71 | 'Programming Language :: Python :: 3', 72 | 'Programming Language :: Python :: 3.5', 73 | 'Programming Language :: Python :: 3.6', 74 | ], 75 | ) 76 | --------------------------------------------------------------------------------