|
| 1 | +:description: Faster prototyping with basic CRUD (Create, Read, Update, Delete) python views and HTML templates for your django models. |
| 2 | + |
| 3 | +crud |
| 4 | +==== |
| 5 | + |
| 6 | +Accelerate prototyping with basic CRUD (Create, Read, Update, Delete) python views and HTML templates, enhanced with htmx and Tailwind CSS. |
| 7 | + |
| 8 | +.. exec_code:: |
| 9 | + :language_output: shell |
| 10 | + |
| 11 | + # --- hide: start --- |
| 12 | + from falco.management.commands.crud import Command |
| 13 | + |
| 14 | + Command().print_help("manage.py", "crud") |
| 15 | + #hide:toggle |
| 16 | + |
| 17 | +This command generates htmx-powered create, read, update, and delete views for your model. It follows a similar idea |
| 18 | +as `neapolitan <https://github.com/carltongibson/neapolitan>`_, but with a completely different approach. Instead of inheriting |
| 19 | +from a class as you would with ``neapolitan``, this command generates basic ``views``, ``urls``, ``forms``, ``admin`` (thanks to `django-extensions <https://django-extensions.readthedocs.io/en/latest/admin_generator.html>`_) |
| 20 | +and HTML ``templates``, and updates or overrides the corresponding files in your project. I prefer this approach because, at the end, you'll have all the new code directly in front of you. It's easily |
| 21 | +accessible and you can update it as you see fit. The idea is to accelerate project prototyping. Write a model and you instantly have views ready for it. |
| 22 | + |
| 23 | + |
| 24 | +.. admonition:: Why function based views? |
| 25 | + :class: hint dropdown |
| 26 | + |
| 27 | + I think class-based views get complex faster than function-based views. Both have their use cases, but function-based views |
| 28 | + stay simpler to manage longer in my experience. There is an excellent document on the topic, read `django views the right way <https://spookylukey.github.io/django-views-the-right-way/>`_. |
| 29 | + |
| 30 | +.. If you want to see an example of the generated code, check out the `source code of the demo project <https://github.com/Tobi-De/falco/tree/main/demo/myjourney/entries>`_. |
| 31 | +
|
| 32 | +
|
| 33 | +Python code |
| 34 | +^^^^^^^^^^^ |
| 35 | + |
| 36 | +All Python code added by this command will be in **append** mode, meaning it won't override the content of your existing files. |
| 37 | +Instead, it will add code at the end or create the files if they are missing. The files that will be modified |
| 38 | +are ``forms.py``, ``urls.py``, ``admin.py`` (if you have `django-extension <https://django-extensions.readthedocs.io/en/latest/index.html>`_ installed), |
| 39 | +``views.py`` and your project root ``urls.py``. |
| 40 | + |
| 41 | +For the sake brevity, I'll only show an example of what the ``urls.py`` file might look like for a model named ``Entry`` in a django app named ``entries``. |
| 42 | + |
| 43 | +.. code-block:: bash |
| 44 | +
|
| 45 | + python -m myproject entry.entries |
| 46 | +
|
| 47 | +.. literalinclude:: /_static/snippets/urls.py |
| 48 | + |
| 49 | +As you can see, the convention is quite simple: ``<model_name_lower>_<operation>``. Note that if you don't specify the model name and run |
| 50 | +``python -m myproject entries``, the same code with the described conventions will be generated for all the models in the ``entries`` app. |
| 51 | + |
| 52 | +Now, if you're anything like me, the code above might have made you cringe due to the excessive repetitions of the word ``entry``. |
| 53 | +This wouldn't have been the case if the model was called ``Category``, for example. For these specific cases, there is an ``--entry-point`` option. |
| 54 | + |
| 55 | +Let's try it. |
| 56 | + |
| 57 | +.. code-block:: bash |
| 58 | +
|
| 59 | + python -m myproject entries.entry --entry-point |
| 60 | +
|
| 61 | +.. admonition:: Oops, I made a mistake |
| 62 | + :class: tip dropdown |
| 63 | + |
| 64 | + If you made a mistake when running CRUD commands and want to discard the changes and restart, you can use the following commands instead of manually deleting all changes. |
| 65 | + Note that the commands below will discard all current changes. You can also specify a specific path to remove only a subset of the changes. |
| 66 | + |
| 67 | + .. code-block:: shell |
| 68 | +
|
| 69 | + git checkout -- . # Remove changes in the working tree |
| 70 | + git clean -fd # Remove untracked files and directories from the working tree |
| 71 | +
|
| 72 | +.. code-block:: python |
| 73 | + :caption: entries/urls.py |
| 74 | +
|
| 75 | + from django.urls import path |
| 76 | +
|
| 77 | + from . import views |
| 78 | +
|
| 79 | + app_name = "entries" |
| 80 | +
|
| 81 | + urlpatterns = [ |
| 82 | + path("", views.index, name="index"), |
| 83 | + path("create/", views.create, name="create"), |
| 84 | + path("<int:pk>/", views.detail, name="detail"), |
| 85 | + path("<int:pk>/update/", views.update, name="update"), |
| 86 | + path("<int:pk>/delete/", views.delete, name="delete"), |
| 87 | + ] |
| 88 | +
|
| 89 | +Much cleaner, specifying that option means you consider the ``Entry`` model as the entry point of your ``entries`` app. |
| 90 | +So, instead of the base URL of the app looking like ``entries/entries/``, it will just be ``entries/``. |
| 91 | + |
| 92 | +As previously mentioned, the command will also register your app in your project root URLs configuration. This occurs when |
| 93 | +you generate ``crud`` views for a model and there is no existing ``urls.py`` file for the app. In such cases, it is assumed |
| 94 | +that you haven't already registered the URLs for your app since the command just created the file. |
| 95 | + |
| 96 | +Here is an example of how the ``entries`` app will be registered. |
| 97 | + |
| 98 | +.. code-block:: python |
| 99 | + :caption: config/urls.py |
| 100 | + :linenos: |
| 101 | + :emphasize-lines: 4 |
| 102 | +
|
| 103 | + urlpatterns = [ |
| 104 | + path("admin/", admin.site.urls), |
| 105 | + ... |
| 106 | + path("entries/", include("entries.urls", namespace="entries")) |
| 107 | + ] |
| 108 | +
|
| 109 | +
|
| 110 | +
|
| 111 | +HTML templates |
| 112 | +^^^^^^^^^^^^^^ |
| 113 | + |
| 114 | +Unlike the Python code, the generated HTML templates will overwrite any existing ones. If you want to avoid this, you should commit |
| 115 | +your changes before running this command or use the ``--only-python`` option to generate only Python code. The files are generated |
| 116 | +with minimal styling (using Tailwind CSS) and are reasonably presentable. |
| 117 | +Four files are generated: |
| 118 | + |
| 119 | +* ``<model_name_lower>_list.html`` |
| 120 | +* ``<model_name_lower>_create.html`` |
| 121 | +* ``<model_name_lower>_detail.html`` |
| 122 | +* ``<model_name_lower>_update.html`` |
| 123 | + |
| 124 | +There is no ``<model_name_lower>_delete.html`` file because deletion is handled in the ``<model_name_lower>_list.html``. |
| 125 | +Each generated HTML file extends a ``base.html`` template. Therefore, make sure you have a top-level ``base.html`` file in |
| 126 | +your templates directory. |
| 127 | + |
| 128 | + |
| 129 | +.. note:: |
| 130 | + |
| 131 | + If you use the ``--entry-point`` option, the files will be named ``index.html``, ``create.html``, ``detail.html``, and ``update.html``. |
| 132 | + |
| 133 | +To determine where to place the generated files, we check the ``DIRS`` key in the ``TEMPLATES`` settings of your Django project. |
| 134 | +If it is populated, we take the first value in the list and generate the template files in ``<templates_dir>/<app_label>``. |
| 135 | +If it is not populated, we use the classic Django layout, which is ``<app_label>/templates/<app_label>``. |
| 136 | + |
| 137 | +.. If you want an overview of what the templates look like, check out the `demo project <https://github.com/Tobi-De/falco/tree/main/demo/templates/entries>`_. |
| 138 | +
|
| 139 | +Custom Templates |
| 140 | +**************** |
| 141 | + |
| 142 | +The ``crud`` command supports the ability to specify your own HTML templates using the ``--blueprints`` option. |
| 143 | +This option only takes into account HTML files and will completely override the default templates. The HTML templates |
| 144 | +use the `jinja2 <https://jinja.palletsprojects.com/en/3.1.x/>`_ syntax. To see examples of what the templates look like, |
| 145 | +check out the base templates `here <https://github.com/falcopackages/falco-cli/tree/main/src/falco_cli/templates/crud>`_. |
| 146 | + |
| 147 | +.. Below is an example of the context each template will receive. |
| 148 | +
|
| 149 | +
|
| 150 | +.. .. jupyter-execute:: |
| 151 | +.. :hide-code: |
| 152 | +
|
| 153 | +.. from falco_cli.management.commands.crud import DjangoModel |
| 154 | +.. from pprint import pprint |
| 155 | +
|
| 156 | +.. dj_model = DjangoModel( |
| 157 | +.. name = "Entry", |
| 158 | +.. name_plural = "Entries", |
| 159 | +.. verbose_name = "Entry", |
| 160 | +.. verbose_name_plural = "Entries", |
| 161 | +.. has_file_field = False, |
| 162 | +.. has_editable_date_field = False, |
| 163 | +.. fields = { |
| 164 | +.. "name": {"verbose_name": "Name", "editable": True, "class_name": "CharField", "accessor": "{{entry.name}}"}, |
| 165 | +.. "price": {"verbose_name": "Price", "editable": True, "class_name": "DecimalField", "accessor": "{{entry.price}}"}, |
| 166 | +.. } |
| 167 | +.. ) |
| 168 | +
|
| 169 | +.. pprint(get_html_blueprint_context(app_label="entries", django_model=dj_model), sort_dicts=False, compact=True, width=120) |
| 170 | +
|
| 171 | +
|
| 172 | +Examples |
| 173 | +^^^^^^^^ |
| 174 | + |
| 175 | +Some usage examples. |
| 176 | + |
| 177 | +.. code:: bash |
| 178 | +
|
| 179 | + $ python -m myproject entries.entry |
| 180 | + $ python -m myproject entries |
| 181 | + $ python -m myproject entries.entry -e="secret_field1" -e="secret_field2" |
| 182 | + $ python -m myproject entries.entry --only-html |
| 183 | + $ python -m myproject entries.entry --only-python |
| 184 | + $ python -m myproject entries.entry --entry-point |
| 185 | + $ python -m myproject entries.entry --entry-point --login |
| 186 | + $ python -m myproject entries.entry --blueprints /path/to/blueprints |
0 commit comments