├── .gitignore ├── LICENSE.txt ├── README.md ├── automatic_crud ├── __init__.py ├── base_report.py ├── data_types.py ├── generics.py ├── models.py ├── register.py ├── response_messages.py ├── urls.py ├── utils.py ├── views_crud.py └── views_crud_ajax.py ├── docs ├── .readthedocs.yaml ├── about.md ├── about.rst ├── ajax-cruds.md ├── ajax-cruds.rst ├── base-model.md ├── base-model.rst ├── data-types.md ├── data-types.rst ├── excel-report.md ├── excel-report.rst ├── extra-functions.md ├── extra-functions.rst ├── index.md ├── index.rst ├── introduction.rst ├── mkdocs.yml ├── normal-cruds.md ├── normal-cruds.rst ├── register-models.md ├── register-models.rst └── requirements.txt ├── requirements.txt ├── setup.py └── test_app ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── models.py ├── templates └── test_app │ ├── category_create.html │ ├── category_detail.html │ ├── category_list.html │ └── category_update.html └── urls.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/django 2 | # Edit at https://www.gitignore.io/?templates=django 3 | 4 | ### Django ### 5 | *.log 6 | *.pot 7 | *.pyc 8 | __pycache__/ 9 | local_settings.py 10 | db.sqlite3 11 | media 12 | migrations/ 13 | 14 | # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ 15 | # in your Git repository. Update and uncomment the following line accordingly. 16 | # /staticfiles/ 17 | 18 | ### Django.Python Stack ### 19 | # Byte-compiled / optimized / DLL files 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | build/ 26 | dist/ 27 | # Distribution / packaging 28 | .Python 29 | develop-eggs/ 30 | downloads/ 31 | eggs/ 32 | .eggs/ 33 | parts/ 34 | wheels/ 35 | share/python-wheels/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | 67 | # Django stuff: 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | .python-version 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | 125 | # End of https://www.gitignore.io/api/django -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Automatic CRUD (CRUD Automáticos con Django) 2 | 3 | Django Automatic CRUD es un proyecto que genera CRUDS automáticos para cada modelo que tenga la herencia indicada mas adelante. Estos CRUDS y URLS pueden ser de 2 tipos: **Normales y AJAX**. 4 | 5 | ## Documentación 6 | 7 | Para una documentación mas detallada visitar: [Documentación Django Automatic CRUD](https://django-automatic-crud.readthedocs.io/es/latest/index.html) 8 | 9 | ## Tutoriales 10 | 11 | Para ver los tutoriales realizados sobre Django Automatic CRUD visitar: [Developer.pe/Django Automatic CRUD](https://www.youtube.com/playlist?list=PLMbRqrU_kvbRUbcpiEB-ixW4e-DmXCUtV) 12 | 13 | ## Nota 14 | 15 | **CRUDS Normales ** - Estos CRUDS son accesibles utilizando el Sistema de Plantillas de Django e incluyen validaciones de errores, existencia de templates, inicio de sesión y permisos. 16 | 17 | **CRUDS AJAX ** - Estos CURDS son accesibles utilizando JavaScript o cualquier herramienta que permita realizar una petición a una URL indicada. 18 | 19 | ## Características 20 | 21 | - CRUDS automáticos con sólo crear los modelos. 22 | - URLS generadas automáticamente para cada tipo de CRUD de modelo. 23 | - Ruta para generación automática de un Reporte en formato Excel. 24 | - Validación de Inicio de Sesión. 25 | - Validación de Permisos. 26 | - CRUDS automáticos independientes, es decir, pueden generarse de los 2 tipos, sólo de uno o independiente. 27 | - Campos a excluir para listado, registro, edición y detalle de modelo de forma dinámica. 28 | - Mensajes de error automáticos y customizables. 29 | - Nombre de templates para CRUDS customizables. 30 | - Form de Django para CRUDS dinámico. 31 | - Server-side. 32 | - Paginación de datos. 33 | 34 | ## Pre-Requisitos 35 | 36 | - Django >= 2.2 37 | - Python >= 3.3 38 | 39 | ## Instalación Rápida 40 | 41 | - Crea un entorno virtual e inicialo. 42 | - Clona el repositorio en tu proyecto: 43 | - Agrega automatic_crud a tu INSTALLED_APPS: 44 | 45 | ``` 46 | INSTALLED_APPS = [ 47 | ... 48 | 'automatic_crud', 49 | ... 50 | ] 51 | ``` 52 | 53 | ## Generación de CRUDS 54 | 55 | - Para cada modelo que desees generar los CRUDS, deben heredar de BaseModel, por ejemplo: 56 | 57 | ```python 58 | 59 | from automatic_crud.models import BaseModel 60 | 61 | class NewModel(BaseModel): 62 | ... 63 | 64 | ``` 65 | 66 | - Agrega la siguiente linea en tu archivo urls.py global: 67 | 68 | ```python 69 | path('automatic-crud/',include('automatic_crud.urls')) 70 | ``` 71 | 72 | - Ahora, ingresa a tu navegador y escribe una ruta que no exista para que Django pueda mostrarte todas las rutas existentes, te mostrará 14 rutas para cada modelo que herede de BaseModel, las cuales estarán dentro de la estructura de ruta: `http://localhost:8000/automatic-crud/` y tendrán el siguiente patrón: 73 | 74 | ```python 75 | 76 | automatic_crud/ app_name/ model_name / list / [name="app_name-model_name-list"] 77 | automatic_crud/ app_name/ model_name / create / [name="app_name-model_name-create"] 78 | automatic_crud/ app_name/ model_name / detail / / [name="app_name-model_name-detail"] 79 | automatic_crud/ app_name/ model_name / update / / [name="app_name-model_name-update"] 80 | automatic_crud/ app_name/ model_name / logic-delete / / [name="app_name-model_name-logic-delete"] 81 | automatic_crud/ app_name/ model_name / direct-delete / / [name="app_name-model_name-direct-delete"] 82 | automatic_crud/ app_name/ model_name / excel-report / [name="app_name-model_name-excel-report"] 83 | 84 | automatic_crud/ ajax-app_name/ model_name / list / [name="app_name-model_name-list-ajax"] 85 | automatic_crud/ ajax-app_name/ model_name / create / [name="app_name-model_name-create-ajax"] 86 | automatic_crud/ ajax-app_name/ model_name / detail / / [name="app_name-model_name-detail-ajax"] 87 | automatic_crud/ ajax-app_name/ model_name / update / / [name="app_name-model_name-update-ajax"] 88 | automatic_crud/ ajax-app_name/ model_name / logic-delete / / [name="app_name-model_name-logic-delete-ajax"] 89 | automatic_crud/ ajax-app_name/ model_name / direct-delete / / [name="app_name-model_name-direct-delete-ajax"] 90 | automatic_crud/ ajax-app_name/ model_name / excel-report / [name="app_name-model_name-excel-report-ajax"] 91 | 92 | ``` 93 | 94 | --- 95 | 96 | Si quieres apoyar realizando una donación, puedes hacerla a este enlace: 97 | 98 | - [Donación al Proyecto](https://www.paypal.com/paypalme/oliversando) 99 | 100 | ## Redes Sociales 101 | 102 | [Web](http://www.developerpe.com) 103 | 104 | [Facebook](https://www.facebook.com/developerper​) 105 | 106 | [Instagram](https://www.instagram.com/developer.pe/​) 107 | 108 | [Twitter](https://twitter.com/Developerpepiur​) 109 | 110 | [Youtube](Developer.pe) 111 | 112 | **Correo: developerpeperu@gmail.com** 113 | -------------------------------------------------------------------------------- /automatic_crud/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developerpe/django-automatic-crud/e826e42ca3ca1675d868b3d5f2fb2bea8afacbeb/automatic_crud/__init__.py -------------------------------------------------------------------------------- /automatic_crud/base_report.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.http import HttpResponse 4 | from django.views.generic import TemplateView 5 | 6 | from openpyxl import Workbook 7 | from openpyxl.styles import ( 8 | Alignment,Border,Font,Side 9 | ) 10 | try: 11 | from openpyxl.cell import get_column_letter 12 | except ImportError: 13 | from openpyxl.utils import get_column_letter 14 | 15 | from .generics import BaseCrudMixin 16 | from .utils import ( 17 | get_model,get_model_fields_names,get_queryset 18 | ) 19 | 20 | def _excel_report_title(__model_name: str): 21 | """ 22 | Build report title with today date 23 | """ 24 | 25 | 26 | date = datetime.now() 27 | title = "REPORTE DE {0} EN FORMATO EXCEL REALIZADO EN LA FECHA: {1}".format( 28 | __model_name.upper(), 29 | "%s/%s/%s" % ( 30 | date.day,date.month, 31 | date.year 32 | ) 33 | ) 34 | return title 35 | 36 | def _validate_id(__field: str): 37 | if str(__field).lower() != 'id': 38 | return True 39 | return False 40 | 41 | class ExcelReportFormat: 42 | """ 43 | This class generates a report in excel for any model you want, 44 | the parameters to be used are defined in the constructor, 45 | and there are also some methods that build the report block by block. 46 | 47 | Parameters: 48 | _app_name name of the application where is the model to be used. 49 | _model_name name of the model to be used. 50 | 51 | Variables: 52 | _app_name name of the application where is the model to be used. 53 | _model_name name of the model to be used. 54 | __model model to be used 55 | __model_fields_names fields list of model. 56 | __queryset queryset of model, contains all registers of model. 57 | __report_title report title. 58 | __workbook Workbook instance, Excel workbook. 59 | __sheetwork Excel Sheetwork, by default first sheet. 60 | 61 | """ 62 | 63 | 64 | def __init__(self, __app_name:str, __model_name:str, *args, **kwargs): 65 | self.__app_name = __app_name 66 | self.__model_name = __model_name 67 | self.__model = get_model(self.__app_name, self.__model_name) 68 | self.__model_fields_names = get_model_fields_names(self.__model) 69 | self.__queryset = get_queryset(self.__model) 70 | self.__report_title = _excel_report_title(self.__model_name) 71 | self.__workbook = Workbook() 72 | self.__sheetwork = self.__workbook.active 73 | 74 | def get_model(self): 75 | return self.__model 76 | 77 | def __excel_report_header(self, row_dimension=15, col_dimension=25): 78 | """ 79 | Build excel report header, print report title and add default styles 80 | 81 | """ 82 | 83 | self.__sheetwork['B1'].alignment = Alignment(horizontal="center", vertical="center") 84 | self.__sheetwork['B1'].border = Border( 85 | left=Side(border_style="thin"), 86 | right=Side(border_style="thin"), 87 | top=Side(border_style="thin"), 88 | bottom=Side(border_style="thin") 89 | ) 90 | self.__sheetwork['B1'].font = Font(name='Calibri', size=12, bold=True) 91 | self.__sheetwork['B1'] = self.__report_title 92 | 93 | if len(self.__model_fields_names) < 12: 94 | __header_letter = 'L' 95 | else: 96 | __header_letter = '{0}'.format(get_column_letter(len(self.__model_fields_names)).upper()) 97 | 98 | self.__sheetwork.merge_cells('B1:{0}1'.format(__header_letter)) 99 | self.__sheetwork.row_dimensions[3].height = row_dimension 100 | 101 | __count = 1 102 | for __field in self.__model_fields_names: 103 | if __field not in self.__model.exclude_fields: 104 | __letter = get_column_letter(__count).upper() 105 | self.__sheetwork['{0}3'.format(__letter)].alignment = Alignment(horizontal="center", vertical="center") 106 | self.__sheetwork['{0}3'.format(__letter)].border = Border( 107 | left=Side(border_style="thin"), 108 | right=Side(border_style="thin"), 109 | top=Side(border_style="thin"), 110 | bottom=Side(border_style="thin") 111 | ) 112 | self.__sheetwork['{0}3'.format(__letter)].font = Font(name='Calibri', size=9, bold=True) 113 | self.__sheetwork['{0}3'.format(__letter)] = '{0}'.format(__field.upper()) 114 | self.__sheetwork.column_dimensions['{0}'.format(__letter)].height = col_dimension 115 | __count += 1 116 | 117 | def __print_values(self): 118 | """ 119 | Print values of queryset, skip id value and exclude fields of model 120 | """ 121 | 122 | row_count = 4 123 | col_count = 1 124 | general_count = len(self.__model_fields_names) 125 | data = [value for value in self.__queryset] 126 | 127 | for value in data: 128 | for key,subvalue in value.items(): 129 | if _validate_id(key): 130 | if key not in self.__model.exclude_fields: 131 | if general_count > 0: 132 | self.__sheetwork.cell(row=row_count, column=col_count).alignment = Alignment(horizontal="center") 133 | self.__sheetwork.cell(row=row_count, column=col_count).border = Border( 134 | left=Side(border_style="thin"), 135 | right=Side(border_style="thin"), 136 | top=Side(border_style="thin"), 137 | bottom=Side(border_style="thin") 138 | ) 139 | if type(subvalue) is bool: 140 | if subvalue is True: 141 | self.__sheetwork.cell(row=row_count, column=col_count).value = 'No eliminado' 142 | else: 143 | self.__sheetwork.cell(row=row_count, column=col_count).value = 'Eliminado' 144 | else: 145 | self.__sheetwork.cell(row=row_count, column=col_count).value = str(subvalue) 146 | if (self.__sheetwork.column_dimensions[get_column_letter(col_count).upper()].width < len(str(subvalue))): 147 | self.__sheetwork.column_dimensions[get_column_letter(col_count).upper()].width = len(str(subvalue)) 148 | 149 | col_count += 1 150 | general_count -= 1 151 | 152 | if general_count == 0: 153 | general_count = len(self.__model_fields_names) 154 | 155 | row_count += 1 156 | col_count = 1 157 | 158 | def get_excel_report(self): 159 | """ 160 | Generate excel response using model name 161 | """ 162 | 163 | report_name = "Reporte {0} en Excel .xlsx".format(self.__model_name) 164 | response = HttpResponse(content_type = "application/ms-excel") 165 | content = "attachment; filename = {0}".format(report_name) 166 | response['Content-Disposition'] = content 167 | self.__workbook.save(response) 168 | return response 169 | 170 | def build_report(self): 171 | """ 172 | Build report call 2 functions: __excel_report_header and __print_values 173 | """ 174 | 175 | self.__excel_report_header() 176 | self.__print_values() 177 | 178 | class GetExcelReport(BaseCrudMixin, TemplateView): 179 | """ 180 | Return Instance Excel Report for a model. 181 | """ 182 | 183 | def get(self, request, _app_name:str, _model_name:str, *args, **kwargs): 184 | __report = ExcelReportFormat(_app_name,_model_name) 185 | 186 | self.model = __report.get_model() 187 | 188 | # login required validation 189 | validation_login_required, response = self.validate_login_required() 190 | if validation_login_required: 191 | return response 192 | 193 | # permission required validation 194 | validation_permissions, response = self.validate_permissions() 195 | if validation_permissions: 196 | return response 197 | 198 | __report.build_report() 199 | return __report.get_excel_report() -------------------------------------------------------------------------------- /automatic_crud/data_types.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | XLS = TypeVar('XLS') 4 | Instance = TypeVar('Instance') 5 | URLList = TypeVar('URLList') 6 | DjangoForm = TypeVar('DjangoForm') 7 | JsonResponse = TypeVar('JsonResponse') -------------------------------------------------------------------------------- /automatic_crud/generics.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse as JSR 2 | from django.contrib.auth.mixins import AccessMixin 3 | from django.views.generic import View 4 | 5 | class BaseCrudMixin(AccessMixin): 6 | model = None 7 | data = None 8 | permission_required = () 9 | 10 | def get_permission_required(self): 11 | """ 12 | Override this method to override the permission_required attribute. 13 | Must return an iterable. 14 | """ 15 | if self.permission_required is None: 16 | raise ImproperlyConfigured( 17 | '{0} is missing the permission_required attribute. Define {0}.permission_required, or override ' 18 | '{0}.get_permission_required().'.format(self.__class__.__name__) 19 | ) 20 | if isinstance(self.permission_required, str): 21 | perms = (self.permission_required, ) 22 | else: 23 | perms = self.permission_required 24 | return perms 25 | 26 | def has_permission(self): 27 | """ 28 | Override this method to customize the way permissions are checked. 29 | """ 30 | perms = self.get_permission_required() 31 | return self.request.user.has_perms(perms) 32 | 33 | def set_permissions(self): 34 | """ 35 | Set permission_required with default permissions if default_permissions = True 36 | """ 37 | 38 | if self.model.default_permissions: 39 | __default_permissions = ('{0}.add_{1}'.format(self.model._meta.app_label,self.model.__name__.lower()), 40 | '{0}.view_{1}'.format(self.model._meta.app_label,self.model.__name__.lower()), 41 | '{0}.delete_{1}'.format(self.model._meta.app_label,self.model.__name__.lower()), 42 | '{0}.change_{1}'.format(self.model._meta.app_label,self.model.__name__.lower())) 43 | self.permission_required = __default_permissions 44 | else: 45 | self.permission_required = self.model.permission_required 46 | 47 | def validate_permissions(self,*args, **kwargs): 48 | """ 49 | Validate permissions required if model_permissions = True 50 | """ 51 | 52 | if self.model.model_permissions: 53 | self.set_permissions() 54 | if not self.has_permission() and not self.request.user.is_superuser: 55 | response = JSR({'error': 'No tiene los permisos para realizar esta acción.'}) 56 | response.status_code = 403 57 | return True, response 58 | return False, None 59 | 60 | def validate_login_required(self, *args, **kwargs): 61 | """ 62 | Validate login required if login_required = True 63 | """ 64 | 65 | if self.model.login_required: 66 | if not self.request.user.is_authenticated: 67 | response = JSR({'error': 'No ha iniciado sesión.'}) 68 | response.status_code = 403 69 | return True, response 70 | return False, None 71 | 72 | 73 | class BaseCrud(BaseCrudMixin, View): 74 | 75 | def get_fields_for_model(self): 76 | """ 77 | Return fields for model excluding exclude_fields of model 78 | """ 79 | fields = [field.name for field in self.model._meta.get_fields()] 80 | for field in self.model.exclude_fields: 81 | if field in fields: 82 | fields.remove(field) 83 | return fields -------------------------------------------------------------------------------- /automatic_crud/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.urls import path,reverse_lazy 3 | 4 | from .utils import get_model 5 | from .data_types import * 6 | from .base_report import GetExcelReport 7 | from .views_crud import * 8 | from .views_crud_ajax import * 9 | 10 | class BaseModel(models.Model): 11 | """Model definition for BaseModel.""" 12 | 13 | # TODO: Define fields here 14 | id = models.AutoField(primary_key=True) 15 | model_state = models.BooleanField(default=True) 16 | date_created = models.DateTimeField('Fecha de Creación', auto_now=False, auto_now_add=True) 17 | date_modified = models.DateTimeField('Fecha de Modificación', auto_now=True, auto_now_add=False) 18 | date_deleted = models.DateTimeField('Fecha de Eliminación', auto_now=True, auto_now_add=False) 19 | 20 | create_form = None 21 | update_form = None 22 | all_cruds_types = True 23 | ajax_crud = False 24 | normal_cruds = False 25 | 26 | server_side = False 27 | exclude_model = False 28 | normal_pagination = False 29 | values_for_page = 10 30 | 31 | login_required = False 32 | permission_required = () 33 | model_permissions = False 34 | default_permissions = False 35 | 36 | exclude_fields = ['date_created','date_modified','date_deleted','model_state'] 37 | 38 | success_create_message = "registrado correctamente!" 39 | success_update_message = "actualizado correctamente!" 40 | success_delete_message = "eliminado correctamente!" 41 | 42 | error_create_message = "no se ha podido registrar!" 43 | error_update_message = "no se ha podido actualizar!" 44 | non_found_message = "No se ha encontrado un registro con estos datos!" 45 | 46 | create_template = None 47 | update_template = None 48 | list_template = None 49 | delete_template = None 50 | detail_template = None 51 | 52 | class Meta: 53 | """Meta definition for BaseModel.""" 54 | abstract = True 55 | 56 | def get_create_form(self, form=None): 57 | if form != None: 58 | self.create_form = form 59 | return self.create_form 60 | 61 | def get_update_form(self, form=None): 62 | if form != None: 63 | self.update_form = form 64 | return self.update_form 65 | 66 | def build_message(self, message:str, custom_message=False): 67 | if custom_message: 68 | return "{0}".format(message) 69 | return "{0} {1}".format(self._meta.verbose_name, message) 70 | 71 | def get_create_url(self): 72 | return "{0}/create/".format(self._meta.object_name.lower()) 73 | 74 | def get_list_url(self): 75 | return "{0}/list/".format(self._meta.object_name.lower()) 76 | 77 | def get_direct_delete_url(self): 78 | return "{0}/direct-delete//".format(self._meta.object_name.lower()) 79 | 80 | def get_logic_delete_url(self): 81 | return "{0}/logic-delete//".format(self._meta.object_name.lower()) 82 | 83 | def get_update_url(self): 84 | return "{0}/update//".format(self._meta.object_name.lower()) 85 | 86 | def get_detail_url(self): 87 | return "{0}/detail//".format(self._meta.object_name.lower()) 88 | 89 | def get_excel_report_url(self): 90 | return "{0}/excel-report/".format(self._meta.object_name.lower()) 91 | 92 | def get_alias_create_url(self): 93 | return "{0}-{1}-create".format(self._meta.app_label, self._meta.object_name.lower()) 94 | 95 | def get_alias_list_url(self): 96 | return "{0}-{1}-list".format(self._meta.app_label, self._meta.object_name.lower()) 97 | 98 | def get_alias_logic_delete_url(self): 99 | return "{0}-{1}-logic-delete".format(self._meta.app_label, self._meta.object_name.lower()) 100 | 101 | def get_alias_direct_delete_url(self): 102 | return "{0}-{1}-direct-delete".format(self._meta.app_label, self._meta.object_name.lower()) 103 | 104 | def get_alias_update_url(self): 105 | return "{0}-{1}-update".format(self._meta.app_label, self._meta.object_name.lower()) 106 | 107 | def get_alias_detail_url(self): 108 | return "{0}-{1}-detail".format(self._meta.app_label, self._meta.object_name.lower()) 109 | 110 | def get_alias_excel_report_url(self): 111 | return "{0}-{1}-excel-report".format(self._meta.app_label, self._meta.object_name.lower()) 112 | 113 | def build_generics_urls_crud(self) -> URLList: 114 | 115 | __app_name = self._meta.app_label 116 | __model_name = self._meta.object_name 117 | __model = get_model(__app_name, __model_name) 118 | __create_form = self.get_create_form() 119 | __update_form = self.get_update_form() 120 | 121 | urlpatterns = [ 122 | path( 123 | "{0}/{1}".format(__app_name, self.get_list_url()), 124 | BaseList.as_view( 125 | template_name=__model.list_template, 126 | model=__model 127 | ), 128 | name=self.get_alias_list_url() 129 | ), 130 | path( 131 | "{0}/{1}".format(__app_name, self.get_create_url()), 132 | BaseCreate.as_view( 133 | template_name=__model.create_template, 134 | model=__model, 135 | form_class=__create_form, 136 | success_url=reverse_lazy("{0}".format(self.get_alias_list_url())) 137 | ), 138 | name=self.get_alias_create_url() 139 | ), 140 | path( 141 | "{0}/{1}".format(__app_name, self.get_detail_url()), 142 | BaseDetail.as_view(model=__model), 143 | name=self.get_alias_detail_url() 144 | ), 145 | path( 146 | "{0}/{1}".format(__app_name, self.get_update_url()), 147 | BaseUpdate.as_view( 148 | template_name=__model.update_template, 149 | model=__model, 150 | form_class=__update_form, 151 | success_url=reverse_lazy("{0}".format(self.get_alias_list_url())) 152 | ), 153 | name=self.get_alias_update_url() 154 | ), 155 | path( 156 | "{0}/{1}".format(__app_name, self.get_logic_delete_url()), 157 | BaseLogicDelete.as_view( 158 | model=__model, 159 | success_url=reverse_lazy("{0}".format(self.get_alias_list_url())) 160 | ), 161 | name=self.get_alias_logic_delete_url() 162 | ), 163 | path( 164 | "{0}/{1}".format(__app_name, self.get_direct_delete_url()), 165 | BaseDirectDelete.as_view( 166 | model=__model, 167 | template_name=__model.delete_template, 168 | success_url=reverse_lazy("{0}".format(self.get_alias_list_url())) 169 | ), 170 | name=self.get_alias_direct_delete_url() 171 | ), 172 | path( 173 | "{0}/{1}".format(__app_name, self.get_excel_report_url()), 174 | GetExcelReport.as_view(), 175 | {'_app_name': __app_name, '_model_name': __model_name}, 176 | name=self.get_alias_excel_report_url() 177 | ), 178 | ] 179 | 180 | return urlpatterns 181 | 182 | def build_generics_urls_ajax_crud(self) -> URLList: 183 | __app_name = self._meta.app_label 184 | __model_name = self._meta.object_name 185 | __model = get_model(__app_name, __model_name) 186 | __model_context = { 187 | 'model':__model 188 | } 189 | __model_create_form_context = { 190 | 'model':__model, 191 | 'form':self.get_create_form() 192 | } 193 | __model_update_form_context = { 194 | 'model':__model, 195 | 'form':self.get_update_form() 196 | } 197 | 198 | urlpatterns = [ 199 | path( 200 | "ajax-{0}/{1}".format(__app_name, self.get_list_url()), 201 | BaseListAJAX.as_view(), 202 | __model_context, 203 | name="{0}-ajax".format(self.get_alias_list_url()) 204 | ), 205 | path( 206 | "ajax-{0}/{1}".format(__app_name, self.get_create_url()), 207 | BaseCreateAJAX.as_view(), 208 | __model_create_form_context, 209 | name="{0}-ajax".format(self.get_alias_create_url()) 210 | ), 211 | path( 212 | "ajax-{0}/{1}".format(__app_name, self.get_detail_url()), 213 | BaseDetailAJAX.as_view(), 214 | __model_context, 215 | name="{0}-ajax".format(self.get_alias_detail_url()) 216 | ), 217 | path( 218 | "ajax-{0}/{1}".format(__app_name, self.get_update_url()), 219 | BaseUpdateAJAX.as_view(), 220 | __model_update_form_context, 221 | name="{0}-ajax".format(self.get_alias_update_url()) 222 | ), 223 | path( 224 | "ajax-{0}/{1}".format(__app_name, self.get_logic_delete_url()), 225 | BaseLogicDeleteAJAX.as_view(), 226 | __model_context, 227 | name="{0}-ajax".format(self.get_alias_logic_delete_url()) 228 | ), 229 | path( 230 | "ajax-{0}/{1}".format(__app_name, self.get_direct_delete_url()), 231 | BaseDirectDeleteAJAX.as_view(), 232 | __model_context, 233 | name="{0}-ajax".format(self.get_alias_direct_delete_url()) 234 | ), 235 | path( 236 | "ajax-{0}/{1}".format(__app_name, self.get_excel_report_url()), 237 | GetExcelReport.as_view(), 238 | {'_app_name': __app_name, '_model_name': __model_name}, 239 | name="{0}-ajax".format(self.get_alias_excel_report_url()) 240 | ), 241 | ] 242 | 243 | return urlpatterns 244 | -------------------------------------------------------------------------------- /automatic_crud/register.py: -------------------------------------------------------------------------------- 1 | from django.apps import apps 2 | 3 | from .models import BaseModel 4 | 5 | def register_models(): 6 | """ 7 | Register models with automatic cruds excluding models with exclude_model = True 8 | Return urlspatterns with automatic cruds 9 | """ 10 | 11 | urlpatterns = [] 12 | exclude_models = ['ContentType', 'LogEntry', 'Session', 'Permission', 'Group'] 13 | models = apps.get_models() 14 | 15 | for model in models: 16 | 17 | if issubclass(model, BaseModel): 18 | try: 19 | if model.__name__ not in exclude_models: 20 | 21 | if not model.exclude_model: 22 | 23 | if model.all_cruds_types: 24 | urlpatterns += model().build_generics_urls_crud() 25 | urlpatterns += model().build_generics_urls_ajax_crud() 26 | 27 | else: 28 | 29 | if model.ajax_crud: 30 | urlpatterns += model().build_generics_urls_ajax_crud() 31 | if model.normal_cruds: 32 | urlpatterns += model().build_generics_urls_crud() 33 | 34 | except: 35 | pass 36 | 37 | return urlpatterns 38 | -------------------------------------------------------------------------------- /automatic_crud/response_messages.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse as JR 2 | 3 | from .data_types import Instance, JsonResponse, DjangoForm 4 | 5 | def jr_response(message:str, error: str, statud_code: int) -> JsonResponse: 6 | response = JR({'message': message, 'error': error}) 7 | response.status_code = statud_code 8 | return response 9 | 10 | def success_create_message(model: Instance) -> JsonResponse: 11 | message = model().build_message(model.success_create_message) 12 | error = 'Ninguno' 13 | return jr_response(message, error, 201) 14 | 15 | def error_create_message(model: Instance, form:DjangoForm) -> JsonResponse: 16 | message = model().build_message(model.error_create_message) 17 | error = form.errors 18 | return jr_response(message, error, 400) 19 | 20 | def success_update_message(model: Instance) -> JsonResponse: 21 | message = model().build_message(model.success_update_message) 22 | error = 'Ninguno' 23 | return jr_response(message, error, 200) 24 | 25 | def error_update_message(model: Instance, form:DjangoForm) -> JsonResponse: 26 | message = model().build_message(model.error_update_message) 27 | error = form.errors 28 | return jr_response(message, error, 400) 29 | 30 | def success_delete_message(model: Instance) -> JsonResponse: 31 | message = model().build_message(model.success_delete_message) 32 | error = 'Ninguno' 33 | return jr_response(message, error, 200) 34 | 35 | def not_found_message(model: Instance) -> JsonResponse: 36 | response = JR({'error': model.non_found_message}) 37 | response.status_code = 400 38 | return response -------------------------------------------------------------------------------- /automatic_crud/urls.py: -------------------------------------------------------------------------------- 1 | from .register import register_models 2 | 3 | urlpatterns = [] 4 | 5 | urlpatterns += register_models() 6 | -------------------------------------------------------------------------------- /automatic_crud/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | from django.apps import apps 4 | from django.forms import models 5 | 6 | from .data_types import Instance, DjangoForm 7 | 8 | def get_model(__app_name:str, __model_name:str) -> Instance: 9 | # return the model corresponding to the application name and model name sent 10 | return apps.get_model(app_label=__app_name, model_name=__model_name) 11 | 12 | def get_object(model: Instance, pk: int): 13 | # return the record for a pk sended 14 | instance = model.objects.filter(id=pk, model_state=True).first() 15 | if instance: 16 | return instance 17 | return None 18 | 19 | def get_model_fields_names(__model: Instance) -> List: 20 | # return a list of field names from a model 21 | return [name for name, _ in models.fields_for_model(__model).items()] 22 | 23 | def get_queryset(__model: Instance) -> Dict: 24 | # returns all records in a dictionary for a model 25 | return __model.objects.all().values() 26 | 27 | def get_form(form: DjangoForm, model: Instance) -> DjangoForm: 28 | """ 29 | Return a Django Form for a model, also a Django Form can be indicated 30 | by default the Django Form will exclude the 'state' field from the model 31 | 32 | """ 33 | 34 | 35 | if form is not None: 36 | return models.modelform_factory(model=model, form=form) 37 | else: 38 | return models.modelform_factory(model=model, exclude=('model_state', )) 39 | 40 | def build_template_name(template_name: str, model: Instance, action: str) -> str: 41 | """ 42 | Build template name with app label from model, model name and action(list,create,update,detail) 43 | 44 | """ 45 | 46 | 47 | if template_name == None: 48 | template_name = '{0}/{1}_{2}.html'.format( 49 | model._meta.app_label, 50 | model._meta.object_name.lower(), 51 | action 52 | ) 53 | return template_name -------------------------------------------------------------------------------- /automatic_crud/views_crud.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render,redirect 2 | from django.views.generic import ( 3 | CreateView,DeleteView,UpdateView,DetailView, 4 | ListView 5 | ) 6 | from django.core.paginator import Paginator 7 | 8 | from .generics import BaseCrudMixin 9 | from .utils import get_object,get_form,build_template_name 10 | 11 | class BaseList(BaseCrudMixin, ListView): 12 | 13 | def dispatch(self, request, *args, **kwargs): 14 | # login required validation 15 | validation_login_required, response = self.validate_login_required() 16 | if validation_login_required: 17 | return response 18 | 19 | # permission required validation 20 | validation_permissions, response = self.validate_permissions() 21 | if validation_permissions: 22 | return response 23 | 24 | return super().dispatch(request, *args, **kwargs) 25 | 26 | def get_queryset(self): 27 | return self.model.objects.filter(model_state=True) 28 | 29 | def get_context_data(self, **kwargs): 30 | context = {} 31 | data = self.get_queryset() 32 | 33 | if self.model.normal_pagination: 34 | paginator = Paginator(data, self.model.values_for_page) 35 | page_number = self.request.GET.get('page', '1') 36 | data = paginator.get_page(page_number) 37 | 38 | context['app'] = self.model._meta.app_label 39 | context['model'] = self.model.__name__.lower() 40 | context['title'] = self.model._meta.verbose_name 41 | context['object_list'] = data 42 | return context 43 | 44 | def get(self, request, *args, **kwargs): 45 | self.template_name = build_template_name(self.template_name, self.model, 'list') 46 | return render(request, self.template_name, self.get_context_data()) 47 | 48 | class BaseCreate(BaseCrudMixin, CreateView): 49 | 50 | def dispatch(self, request, *args, **kwargs): 51 | # login required validation 52 | validation_login_required, response = self.validate_login_required() 53 | if validation_login_required: 54 | return response 55 | 56 | # permission required validation 57 | validation_permissions, response = self.validate_permissions() 58 | if validation_permissions: 59 | return response 60 | 61 | return super().dispatch(request, *args, **kwargs) 62 | 63 | def get(self, request, form=None, *args, **kwargs): 64 | self.template_name = build_template_name(self.template_name, self.model, 'create') 65 | form = get_form(form, self.model) 66 | context = { 67 | 'form': form, 68 | 'title': self.model._meta.verbose_name, 69 | 'model': self.model.__name__.lower(), 70 | 'app': self.model._meta.app_label 71 | } 72 | return render(request, self.template_name, context) 73 | 74 | def post(self, request, form=None, *args, **kwargs): 75 | self.template_name = build_template_name(self.template_name, self.model, 'list') 76 | form = get_form(form, self.model) 77 | 78 | if self.form_class == None: 79 | form = form(request.POST, request.FILES) 80 | else: 81 | form = self.form_class(request.POST, request.FILES) 82 | 83 | if form.is_valid(): 84 | form.save() 85 | return redirect(self.success_url) 86 | else: 87 | form = self.form_class() 88 | context = { 89 | 'form':form, 90 | 'title': self.model._meta.verbose_name, 91 | 'model': self.model.__name__.lower(), 92 | 'app': self.model._meta.app_label 93 | } 94 | return render(request, self.template_name, context) 95 | 96 | class BaseDetail(BaseCrudMixin, DetailView): 97 | def dispatch(self, request, *args, **kwargs): 98 | # login required validation 99 | validation_login_required, response = self.validate_login_required() 100 | if validation_login_required: 101 | return response 102 | 103 | # permission required validation 104 | validation_permissions, response = self.validate_permissions() 105 | if validation_permissions: 106 | return response 107 | 108 | return super().dispatch(request, *args, **kwargs) 109 | 110 | def get_context_data(self, **kwargs): 111 | context = {} 112 | context['object'] = get_object(self.model,self.kwargs['pk']) 113 | context['app'] = self.model._meta.app_label 114 | context['model'] = self.model.__name__.lower() 115 | context['title'] = self.model._meta.verbose_name 116 | return context 117 | 118 | def get(self, request, form=None, *args, **kwargs): 119 | self.template_name = build_template_name(self.template_name, self.model, 'detail') 120 | return render(request, self.template_name, self.get_context_data()) 121 | 122 | class BaseUpdate(BaseCrudMixin, UpdateView): 123 | 124 | def dispatch(self, request, *args, **kwargs): 125 | # login required validation 126 | validation_login_required, response = self.validate_login_required() 127 | if validation_login_required: 128 | return response 129 | 130 | # permission required validation 131 | validation_permissions, response = self.validate_permissions() 132 | if validation_permissions: 133 | return response 134 | 135 | return super().dispatch(request, *args, **kwargs) 136 | 137 | def get_context_data(self, **kwargs): 138 | context = {} 139 | context['object'] = get_object(self.model, self.kwargs['pk']) 140 | context['app'] = self.model._meta.app_label 141 | context['model'] = self.model.__name__.lower() 142 | context['title'] = self.model._meta.verbose_name 143 | return context 144 | 145 | def get(self, request, form=None, *args, **kwargs): 146 | self.template_name = build_template_name(self.template_name, self.model, 'update') 147 | 148 | form = get_form(form, self.model) 149 | form = form(instance=get_object(self.model, self.kwargs['pk'])) 150 | 151 | context = self.get_context_data() 152 | if context['object'] == None: 153 | return redirect(self.success_url) 154 | 155 | context['form'] = form 156 | return render(request, self.template_name, context) 157 | 158 | def post(self, request, form=None, *args, **kwargs): 159 | self.template_name = build_template_name(self.template_name, self.model, 'list') 160 | form = get_form(form, self.model) 161 | 162 | instance = get_object(self.model, self.kwargs['pk']) 163 | if instance is not None: 164 | if self.form_class == None: 165 | form = form(request.POST, request.FILES, instance=instance) 166 | else: 167 | form = self.form_class(request.POST, request.FILES, instance=instance) 168 | if form.is_valid(): 169 | form.save() 170 | return redirect(self.success_url) 171 | else: 172 | form = self.form_class() 173 | context = { 174 | 'form':form, 175 | 'title': self.model._meta.verbose_name, 176 | 'model': self.model.__name__.lower(), 177 | 'app': self.model._meta.app_label 178 | } 179 | return render(request, self.template_name, context) 180 | else: 181 | return redirect(self.success_url) 182 | 183 | class BaseDirectDelete(BaseCrudMixin, DeleteView): 184 | def dispatch(self, request, *args, **kwargs): 185 | # login required validation 186 | validation_login_required, response = self.validate_login_required() 187 | if validation_login_required: 188 | return response 189 | 190 | # permission required validation 191 | validation_permissions, response = self.validate_permissions() 192 | if validation_permissions: 193 | return response 194 | 195 | return super().dispatch(request, *args, **kwargs) 196 | 197 | def get_deleted_objects(self, objs, request): 198 | from django.db import router 199 | from django.utils.text import capfirst 200 | from django.contrib.admin.utils import NestedObjects 201 | 202 | """ 203 | Find all objects related to ``objs`` that should also be deleted. ``objs`` 204 | must be a homogeneous iterable of objects (e.g. a QuerySet). 205 | Return a nested list of strings suitable for display in the 206 | template with the ``unordered_list`` filter. 207 | """ 208 | try: 209 | obj = objs[0] 210 | except IndexError: 211 | return [], {}, set(), [] 212 | else: 213 | using = router.db_for_write(obj._meta.model) 214 | collector = NestedObjects(using=using) 215 | collector.collect(objs) 216 | perms_needed = set() 217 | 218 | def format_callback(obj): 219 | model = obj.__class__ 220 | opts = obj._meta 221 | 222 | no_edit_link = "%s: %s" % (capfirst(opts.verbose_name), obj) 223 | # Don't display link to edit, because it either has no 224 | # admin or is edited inline. 225 | return no_edit_link 226 | 227 | to_delete = collector.nested(format_callback) 228 | 229 | protected = [format_callback(obj) for obj in collector.protected] 230 | model_count = { 231 | model._meta.verbose_name_plural: len(objs) 232 | for model, objs in collector.model_objs.items() 233 | } 234 | 235 | return to_delete, model_count, perms_needed, protected 236 | 237 | def get_context_data(self, **kwargs): 238 | delete_objects, model_count, _, _ = self.get_deleted_objects([self.get_object()], self.request) 239 | context = super(BaseDirectDelete, self).get_context_data(**kwargs) 240 | context['app'] = self.model._meta.app_label 241 | context['model'] = self.model.__name__.lower() 242 | context['title'] = self.model._meta.verbose_name 243 | context['child_object'] = delete_objects[1:] 244 | context['child_object_count'] = model_count 245 | return context 246 | 247 | class BaseLogicDelete(BaseCrudMixin, DeleteView): 248 | 249 | def dispatch(self, request, *args, **kwargs): 250 | # login required validation 251 | validation_login_required, response = self.validate_login_required() 252 | if validation_login_required: 253 | return response 254 | 255 | # permission required validation 256 | validation_permissions, response = self.validate_permissions() 257 | if validation_permissions: 258 | return response 259 | 260 | return super().dispatch(request, *args, **kwargs) 261 | 262 | def delete(self, request, *args, **kwargs): 263 | instance = get_object(self.model, self.kwargs['pk']) 264 | 265 | if instance is not None: 266 | self.model.objects.filter(id=self.kwargs['pk']).update(model_state=False) 267 | return redirect(self.success_url) 268 | else: 269 | return redirect(self.success_url) -------------------------------------------------------------------------------- /automatic_crud/views_crud_ajax.py: -------------------------------------------------------------------------------- 1 | import json 2 | import ast 3 | 4 | from django.http import HttpResponse,JsonResponse as JSR 5 | from django.core.serializers import serialize 6 | 7 | from .generics import BaseCrud 8 | from .utils import get_object,get_form 9 | from .response_messages import * 10 | 11 | class BaseListAJAX(BaseCrud): 12 | 13 | def get_queryset(self): 14 | return self.model.objects.filter(model_state=True).select_related().prefetch_related() 15 | 16 | def get_server_side_queryset(self): 17 | """ 18 | Returns the values as a dictionary ordered by the order_by attribute, 19 | by default order_by = id 20 | 21 | """ 22 | 23 | return self.model.objects.filter( 24 | model_state=True 25 | ).select_related().prefetch_related().order_by(f"{self.request.GET.get('order_by', 'id')}") 26 | 27 | def server_side(self): 28 | """ 29 | Returns the paged query from the server excluding the fields that have been defined in 30 | self.model.exclude_fields. 31 | 32 | The follow attributes must be sent in request.GET: 33 | start: element number where the page starts 34 | end: element number where the page ends 35 | 36 | The response structure is: 37 | 38 | { 39 | 'length': # amount of records, 40 | 'objects': # list of records 41 | } 42 | 43 | For more information see: https://www.youtube.com/watch?v=89Ur7GCyLxI 44 | 45 | """ 46 | 47 | 48 | start = int(self.request.GET.get('start', '0')) 49 | end = int(self.request.GET.get('end', '10')) 50 | 51 | object_list = [] 52 | 53 | temp_response = JSR({'data': self.data}) 54 | temp_data = temp_response.content.decode("UTF-8") 55 | temp_data = ast.literal_eval(temp_data) 56 | temp_data = json.loads(temp_data['data']) 57 | 58 | for index, instance in enumerate(temp_data[start:start+end], start): 59 | del instance['model'] 60 | for field in instance['fields']: 61 | if field in self.model.exclude_fields: del instance['fields'][f'{exclude_field}'] 62 | 63 | instance['index'] = index + 1 64 | object_list.append(instance) 65 | 66 | self.data = { 67 | 'length': self.get_server_side_queryset().count(), 68 | 'objects':object_list 69 | } 70 | self.data = json.dumps(self.data) 71 | 72 | def normalize_data(self): 73 | """ 74 | Generate an HttpResponse instance to get the serialized query and 75 | delete the ['model'] key from the dictionary and convert the dictionary 76 | to json and save on self.data 77 | 78 | """ 79 | 80 | temp_response = JSR({'data': self.data}) 81 | temp_data = temp_response.content.decode("UTF-8") 82 | temp_data = ast.literal_eval(temp_data) 83 | temp_data = json.loads(temp_data['data']) 84 | for item in temp_data: del item['model'] 85 | self.data = json.dumps(temp_data) 86 | 87 | def get(self, request,model,*args,**kwargs): 88 | """ 89 | Return data of model 90 | 91 | If self.model.server_side == True return Paginated Data 92 | else return No Paginated Data 93 | 94 | """ 95 | 96 | 97 | self.model = model 98 | 99 | # login required validation 100 | validation_login_required, response = self.validate_login_required() 101 | if validation_login_required: 102 | return response 103 | 104 | # permission required validation 105 | validation_permissions, response = self.validate_permissions() 106 | if validation_permissions: 107 | return response 108 | 109 | if self.model.server_side: 110 | self.data = serialize( 111 | 'json', 112 | self.get_server_side_queryset(), 113 | fields=self.get_fields_for_model(), 114 | use_natural_foreign_keys=True 115 | ) 116 | self.server_side() 117 | else: 118 | self.data = serialize( 119 | 'json', 120 | self.get_queryset(), 121 | fields=self.get_fields_for_model(), 122 | use_natural_foreign_keys=True 123 | ) 124 | self.normalize_data() 125 | return HttpResponse(self.data, content_type="application/json") 126 | 127 | class BaseCreateAJAX(BaseCrud): 128 | model = None 129 | form_class = None 130 | 131 | def post(self, request, model, form=None, *args, **kwargs): 132 | self.model = model 133 | 134 | # login required validation 135 | validation_login_required, response = self.validate_login_required() 136 | if validation_login_required: 137 | return response 138 | 139 | # permission required validation 140 | validation_permissions, response = self.validate_permissions() 141 | if validation_permissions: 142 | return response 143 | 144 | self.form_class = get_form(form, self.model) 145 | form = self.form_class(request.POST, request.FILES) 146 | if form.is_valid(): 147 | form.save() 148 | return success_create_message(self.model) 149 | return error_create_message(self.model, form) 150 | 151 | class BaseDetailAJAX(BaseCrud): 152 | 153 | data = None 154 | 155 | def normalize_data(self): 156 | """ 157 | Generate an HttpResponse instance to get the serialized query and 158 | delete the ['model'] key from the dictionary and convert the dictionary 159 | to json and save on self.data 160 | 161 | """ 162 | temp_response = JSR({'data': self.data}) 163 | temp_data = temp_response.content.decode("UTF-8") 164 | temp_data = ast.literal_eval(temp_data) 165 | temp_data = json.loads(temp_data['data']) 166 | for item in temp_data: 167 | del item['model'] 168 | self.data = json.dumps(item) 169 | 170 | def get(self, request, model, *args, **kwargs): 171 | self.model = model 172 | 173 | # login required validation 174 | validation_login_required, response = self.validate_login_required() 175 | if validation_login_required: 176 | return response 177 | 178 | # permission required validation 179 | validation_permissions, response = self.validate_permissions() 180 | if validation_permissions: 181 | return response 182 | 183 | self.data = get_object(self.model,self.kwargs['pk']) 184 | if self.data is not None: 185 | self.data = serialize( 186 | 'json', 187 | [self.data,], 188 | fields=self.get_fields_for_model(), 189 | use_natural_foreign_keys=True, 190 | use_natural_primary_keys=True 191 | ) 192 | self.normalize_data() 193 | return HttpResponse(self.data, content_type="application/json") 194 | return not_found_message(self.model) 195 | 196 | class BaseUpdateAJAX(BaseCrud): 197 | data = None 198 | model = None 199 | form_class = None 200 | 201 | def normalize_data(self): 202 | """ 203 | Generate an HttpResponse instance to get the serialized query and 204 | delete the ['model'] key from the dictionary and convert the dictionary 205 | to json and save on self.data 206 | 207 | """ 208 | temp_response = JSR({'data': self.data}) 209 | temp_data = temp_response.content.decode("UTF-8") 210 | temp_data = ast.literal_eval(temp_data) 211 | temp_data = json.loads(temp_data['data']) 212 | for item in temp_data: 213 | del item['model'] 214 | self.data = json.dumps(item) 215 | 216 | def get(self, request, model, *args, **kwargs): 217 | self.model = model 218 | 219 | # login required validation 220 | validation_login_required, response = self.validate_login_required() 221 | if validation_login_required: 222 | return response 223 | 224 | # permission required validation 225 | validation_permissions, response = self.validate_permissions() 226 | if validation_permissions: 227 | return response 228 | 229 | self.data = get_object(self.model,self.kwargs['pk']) 230 | if self.data is not None: 231 | self.data = serialize( 232 | 'json', 233 | [self.data,], 234 | fields=self.get_fields_for_model(), 235 | use_natural_foreign_keys=True, 236 | use_natural_primary_keys=True 237 | ) 238 | self.normalize_data() 239 | return HttpResponse(self.data, content_type="application/json") 240 | return not_found_message(self.model) 241 | 242 | def post(self, request, model, form=None, *args, **kwargs): 243 | self.model = model 244 | 245 | # login required validation 246 | validation_login_required, response = self.validate_login_required() 247 | if validation_login_required: 248 | return response 249 | 250 | # permission required validation 251 | validation_permissions, response = self.validate_permissions() 252 | if validation_permissions: 253 | return response 254 | 255 | self.form_class = get_form(form, self.model) 256 | instance = get_object(self.model, self.kwargs['pk']) 257 | if instance is not None: 258 | form = self.form_class(request.POST, request.FILES, instance=instance) 259 | if form.is_valid(): 260 | form.save() 261 | return success_update_message(self.model) 262 | else: 263 | return error_update_message(self.model, form) 264 | return not_found_message(self.model) 265 | 266 | class BaseDirectDeleteAJAX(BaseCrud): 267 | model = None 268 | 269 | def delete(self, request, model, *args, **kwargs): 270 | self.model = model 271 | 272 | # login required validation 273 | validation_login_required, response = self.validate_login_required() 274 | if validation_login_required: 275 | return response 276 | 277 | # permission required validation 278 | validation_permissions, response = self.validate_permissions() 279 | if validation_permissions: 280 | return response 281 | 282 | instance = get_object(self.model, self.kwargs['pk']) 283 | if instance is not None: 284 | instance.delete() 285 | return success_delete_message(self.model) 286 | return not_found_message(self.model) 287 | 288 | class BaseLogicDeleteAJAX(BaseCrud): 289 | model = None 290 | 291 | def delete(self,request,model,*args,**kwargs): 292 | self.model = model 293 | 294 | # login required validation 295 | validation_login_required, response = self.validate_login_required() 296 | if validation_login_required: 297 | return response 298 | 299 | # permission required validation 300 | validation_permissions, response = self.validate_permissions() 301 | if validation_permissions: 302 | return response 303 | 304 | instance = get_object(self.model, self.kwargs['pk']) 305 | if instance is not None: 306 | self.model.objects.filter(id=self.kwargs['pk']).update(model_state=False) 307 | return success_delete_message(self.model) 308 | return not_found_message(self.model) -------------------------------------------------------------------------------- /docs/.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | mkdocs: 9 | configuration: mkdocs.yml 10 | 11 | -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | # Acerca de Django Automatic CRUD 2 | 3 | Este proyecto nace inspirado en el Sitio de Administración de Django, dicho Admin permite realizar CRUDS automáticos con sólo crear nuestros modelos y registrarlos en él. 4 | Además permite agregarle diversas funcionalidades a dicho Admin, pero todo ligado a la interfaz que ya tiene pre-establecida. 5 | 6 | Debido a esto es que se crea este proyecto, tomando como principal característica la creación de CRUDS automáticos para cualquier modelo pero sin la necesidad de una interfaz definida, ya que esta puede existir o no, haciendo que pueda utilizarse la interfaz que se desee y tener homogeneidad en el proyecto que estemos realizando y seguir contando con la ventaja de no realizar vistas ni rutas para cada modelo secundario que deseemos utilizar, sino sólo para los principales, centrandonos sólo en la interfaz. -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | Acerca de Django Automatic CRUD 2 | =============================== 3 | 4 | Este proyecto nace inspirado en el Sitio de Administración de Django, 5 | dicho Admin permite realizar CRUDS automáticos con sólo crear nuestros 6 | modelos y registrarlos en él. Además permite agregarle diversas 7 | funcionalidades a dicho Admin, pero todo ligado a la interfaz que ya 8 | tiene pre-establecida. 9 | 10 | Debido a esto es que se crea este proyecto, tomando como principal 11 | característica la creación de CRUDS automáticos para cualquier modelo 12 | pero sin la necesidad de una interfaz definida, ya que esta puede 13 | existir o no, haciendo que pueda utilizarse la interfaz que se desee y 14 | tener homogeneidad en el proyecto que estemos realizando y seguir 15 | contando con la ventaja de no realizar vistas ni rutas para cada modelo 16 | secundario que deseemos utilizar, sino sólo para los principales, 17 | centrandonos sólo en la interfaz. 18 | -------------------------------------------------------------------------------- /docs/ajax-cruds.md: -------------------------------------------------------------------------------- 1 | # CRUDS AJAX 2 | 3 | ## BaseListAJAX 4 | 5 | ```python 6 | class BaseListAJAX(BaseCrud): 7 | pass 8 | ``` 9 | 10 | Vista Basada en Clase encargada de generar y retornar el listado de registros para el modelo que se le haya indicado de forma automática. 11 | 12 | Recibe herencia de `BaseCrud`, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required. 13 | 14 | El listado de registros obtenidos para el modelo será retornado en formato JSON y puede ser de 2 tipos: 15 | 16 | **SERVER SIDE** 17 | 18 | Retornará los datos paginados y sólo aquellos cuyo campo `model_state` sea `True` con la siguiente estructura por página: 19 | 20 | { 21 | 'length': # número de registros, 22 | 'objects': # listado de registros 23 | } 24 | 25 | Ejemplo: 26 | 27 | { 28 | "length": 6, 29 | "objects": [ 30 | { 31 | "pk": 1, 32 | "fields":{ 33 | "name": "abarrote", 34 | "modal_state": true, 35 | }, 36 | "index": 1 37 | }, 38 | { 39 | "pk": 1, 40 | "fields":{ 41 | "name": "carro", 42 | "modal_state": true, 43 | }, 44 | "index": 1 45 | } 46 | ] 47 | } 48 | 49 | El campo `index` es la numeración para la tabla que se utilizará en caso desee enumerar cada item. 50 | 51 | Y deben ser enviados en el request.GET los parámetros: 52 | 53 | - **start** : número de elemento donde la página iniciará. 54 | 55 | - **end** : número de elemento donde la página terminará. 56 | 57 | - **order_by** : campo por el cuál los datos se ordenarán. 58 | 59 | Por defectos estos valores serán 0, 10, id respectivamente. 60 | 61 | Los campos que se hayan colocado como excluidos en el modelo, es decir en el campo `exclude_fields` del modelo no serán tomados en cuenta para el listado de datos 62 | 63 | Para activar Server Side, revisar el apartado [BaseModel](base-model.md#atributos-de-modelos-que-hereden-de-basemodel) 64 | 65 | Para mas información o información aún mas detallada, revisar el siguiente vídeo [Server Side con Django](https://www.youtube.com/watch?v=89Ur7GCyLxI) 66 | 67 | **NO SERVER SIDE** 68 | 69 | Retornará todos los registros del modelo que se encuentren en la Base de Datos cuyo campo `model_state` sea `True`. 70 | 71 | Ejemplo: 72 | 73 | [ 74 | { 75 | "pk": 1, 76 | "fields": { 77 | "model_state": true, 78 | "name": "abarrote" 79 | } 80 | }, 81 | { 82 | "pk": 2, 83 | "fields": { 84 | "model_state": true, 85 | "name": "carro" 86 | } 87 | } 88 | ] 89 | 90 | Para desactivar Server Side, revisar el apartado [BaseModel](base-model.md#atributos-de-modelos-que-hereden-de-basemodel) 91 | 92 | ## BaseCreateAJAX 93 | 94 | ```python 95 | class BaseCreateAJAX(BaseCrud): 96 | pass 97 | ``` 98 | 99 | Vista Basada en Clase encargada de realizar un nuevo registro para el modelo indicado automáticamente. 100 | 101 | Recibe herencia de `BaseCrud`, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required. 102 | 103 | Los nombres de los campos que deben ser enviados en la petición, request.POST, deben tener el mismo nombre que tienen estos en el modelo. 104 | 105 | Al registrar correctamente la instancia o haber problemas al registrarla, retornará una respuesta de tipo JSON de la siguiente manera: 106 | 107 | Registro Correcto 108 | 109 | { 110 | "message": "Categoria registrado correctamente!", 111 | "error": "Ninguno" 112 | } 113 | 114 | Registro Incorrecto 115 | 116 | { 117 | "message": "Categoria no se ha podido registrar!", 118 | "error": { 119 | "name": [ 120 | "This field is required." 121 | ] 122 | } 123 | } 124 | 125 | Los errores retornados son por campo y por defecto retornará los que Django haya reconocido automáticamente de los modelos, si desea utilizar errores personalizados deberá utilizar un Form de Django personalizado, el cual debe indicarlo en el modelo. 126 | 127 | **FORM PERSONALIZADO** 128 | 129 | Si se desea utilizar un Form de Django personalizado para el registro o edición de un modelo deberá sobreescribir los siguientes métodos en su modelo: 130 | 131 | ```python 132 | EJEMPLO 133 | 134 | # form para crear 135 | def get_create_form(self,form = None): 136 | from test_app.forms import CategoryForm 137 | self.create_form = CategoryForm 138 | return self.create_form 139 | 140 | # form para actualizar 141 | def get_update_form(self,form = None): 142 | from test_app.forms import CategoryForm 143 | self.update_form = CategoryForm 144 | return self.update_form 145 | ``` 146 | 147 | Siempre deberá importar el form personalizado **dentro de la función**, nunca fuera de ella, esto para evitar un error conocido como `Importación Circular`. 148 | 149 | ## BaseDetailAJAX 150 | 151 | ```python 152 | class BaseDetailAJAX(BaseCrud): 153 | pass 154 | ``` 155 | 156 | Vista Basada en Clase encargada de retornar la instancia del modelo que se le haya indicado de forma automática. 157 | 158 | Recibe herencia de `BaseCrud`, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required. 159 | 160 | Retorna la información del objeto en formato JSON. 161 | 162 | Ejemplo 163 | 164 | { 165 | "pk": 1, 166 | "fields": { 167 | "model_state": true, 168 | "name": "abarrote" 169 | } 170 | } 171 | 172 | Los campos retornados son aquellos que no estén incluidos en el atributo del modelo `exclude_fields` 173 | 174 | ## BaseUpdateAJAX 175 | 176 | ```python 177 | class BaseUpdateAJAX(BaseCrud): 178 | pass 179 | ``` 180 | 181 | Vista Basada en Clase encargada de realizar la actualización de un registro para el modelo indicado automáticamente. 182 | 183 | Recibe herencia de `BaseCrud`, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required. 184 | 185 | Los nombres de los campos que deben ser enviados en la petición, request.POST, deben tener el mismo nombre que tienen estos en el modelo. 186 | 187 | Al actualizar correctamente la instancia o haber problemas al actualizar, retornará una respuesta de tipo JSON de la siguiente manera: 188 | 189 | Actualización Correcto 190 | 191 | { 192 | "message": "Categoria actualizada correctamente!", 193 | "error": "Ninguno" 194 | } 195 | 196 | Actualización Incorrecto 197 | 198 | { 199 | "message": "Categoria no se ha podido actualizar!", 200 | "error": { 201 | "name": [ 202 | "This field is required." 203 | ] 204 | } 205 | } 206 | 207 | Los errores retornados son por campo y por defecto retornará los que Django haya reconocido automáticamente de los modelos, si desea utilizar errores personalizados deberá utilizar un Form de Django personalizado, el cual debe indicarlo en el modelo. 208 | 209 | **FORM PERSONALIZADO** 210 | 211 | Si se desea utilizar un Form de Django personalizado para el registro o edición de un modelo deberá sobreescribir los siguientes métodos en su modelo: 212 | 213 | ```python 214 | EJEMPLO 215 | 216 | # form para crear 217 | def get_create_form(self,form = None): 218 | from test_app.forms import CategoryForm 219 | self.create_form = CategoryForm 220 | return self.create_form 221 | 222 | # form para actualizar 223 | def get_update_form(self,form = None): 224 | from test_app.forms import CategoryForm 225 | self.update_form = CategoryForm 226 | return self.update_form 227 | ``` 228 | 229 | Siempre deberá importar el form personalizado **dentro de la función**, nunca fuera de ella, esto para evitar un error conocido como `Importación Circular`. 230 | 231 | ## BaseDirectDeleteAJAX 232 | 233 | ```python 234 | class BaseDirectDeleteAJAX(BaseCrud): 235 | pass 236 | ``` 237 | 238 | Vista Basada en Clase encargada de realizar la eliminación directa en la Base de Datos de un registro para el modelo indicado automáticamente. 239 | 240 | Recibe herencia de `BaseCrud`, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required. 241 | 242 | La respuesta dependerá de si se encontró o no el objeto que se desea eliminar. 243 | 244 | Objeto encontrado 245 | 246 | { 247 | "message": "Categoria eliminado correctamente!", 248 | "error": "Ninguno" 249 | } 250 | 251 | Objeto no encontrado 252 | 253 | { 254 | "error": "No se ha encontrado un registro con estos datos." 255 | } 256 | 257 | ## BaseLogicDeleteAJAX 258 | 259 | ```python 260 | class BaseLogicDeleteAJAX(BaseCrud): 261 | pass 262 | ``` 263 | 264 | Vista Basada en Clase encargada de realizar la eliminación lógica de un registro para el modelo indicado automáticamente, es decir, colocará el campo `model_state` en `False`. 265 | 266 | Recibe herencia de `BaseCrud`, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required. 267 | 268 | La respuesta dependerá de si se encontró o no el objeto que se desea eliminar. 269 | 270 | Objeto encontrado 271 | 272 | { 273 | "message": "Categoria eliminado correctamente!", 274 | "error": "Ninguno" 275 | } 276 | 277 | Objeto no encontrado 278 | 279 | { 280 | "error": "No se ha encontrado un registro con estos datos." 281 | } 282 | 283 | -------------------------------------------------------------------------------- /docs/ajax-cruds.rst: -------------------------------------------------------------------------------- 1 | CRUDS AJAX 2 | ========== 3 | 4 | BaseListAJAX 5 | ------------ 6 | 7 | .. code:: python 8 | 9 | class BaseListAJAX(BaseCrud): 10 | pass 11 | 12 | Vista Basada en Clase encargada de generar y retornar el listado de 13 | registros para el modelo que se le haya indicado de forma automática. 14 | 15 | Recibe herencia de ``BaseCrud``, la cuál se encarga de realizar las 16 | validaciones correspondientes a permisos y login\_required. 17 | 18 | El listado de registros obtenidos para el modelo será retornado en 19 | formato JSON y puede ser de 2 tipos: 20 | 21 | **SERVER SIDE** 22 | 23 | Retornará los datos paginados y sólo aquellos cuyo campo ``model_state`` 24 | sea ``True`` con la siguiente estructura por página: 25 | 26 | :: 27 | 28 | { 29 | 'length': # número de registros, 30 | 'objects': # listado de registros 31 | } 32 | 33 | Ejemplo: 34 | 35 | { 36 | "length": 6, 37 | "objects": [ 38 | { 39 | "pk": 1, 40 | "fields":{ 41 | "name": "abarrote", 42 | "modal_state": true, 43 | }, 44 | "index": 1 45 | }, 46 | { 47 | "pk": 1, 48 | "fields":{ 49 | "name": "carro", 50 | "modal_state": true, 51 | }, 52 | "index": 1 53 | } 54 | ] 55 | } 56 | 57 | El campo ``index`` es la numeración para la tabla que se utilizará en 58 | caso desee enumerar cada item. 59 | 60 | Y deben ser enviados en el request.GET los parámetros: 61 | 62 | - **start** : número de elemento donde la página iniciará. 63 | 64 | - **end** : número de elemento donde la página terminará. 65 | 66 | - **order\_by** : campo por el cuál los datos se ordenarán. 67 | 68 | Por defectos estos valores serán 0, 10, id respectivamente. 69 | 70 | Los campos que se hayan colocado como excluidos en el modelo, es decir 71 | en el campo ``exclude_fields`` del modelo no serán tomados en cuenta 72 | para el listado de datos 73 | 74 | Para activar Server Side, revisar el apartado 75 | `BaseModel `__ 76 | 77 | Para mas información o información aún mas detallada, revisar el 78 | siguiente vídeo `Server Side con 79 | Django `__ 80 | 81 | **NO SERVER SIDE** 82 | 83 | Retornará todos los registros del modelo que se encuentren en la Base de 84 | Datos cuyo campo ``model_state`` sea ``True``. 85 | 86 | :: 87 | 88 | Ejemplo: 89 | 90 | [ 91 | { 92 | "pk": 1, 93 | "fields": { 94 | "model_state": true, 95 | "name": "abarrote" 96 | } 97 | }, 98 | { 99 | "pk": 2, 100 | "fields": { 101 | "model_state": true, 102 | "name": "carro" 103 | } 104 | } 105 | ] 106 | 107 | Para desactivar Server Side, revisar el apartado 108 | `BaseModel `__ 109 | 110 | BaseCreateAJAX 111 | -------------- 112 | 113 | .. code:: python 114 | 115 | class BaseCreateAJAX(BaseCrud): 116 | pass 117 | 118 | Vista Basada en Clase encargada de realizar un nuevo registro para el 119 | modelo indicado automáticamente. 120 | 121 | Recibe herencia de ``BaseCrud``, la cuál se encarga de realizar las 122 | validaciones correspondientes a permisos y login\_required. 123 | 124 | Los nombres de los campos que deben ser enviados en la petición, 125 | request.POST, deben tener el mismo nombre que tienen estos en el modelo. 126 | 127 | Al registrar correctamente la instancia o haber problemas al 128 | registrarla, retornará una respuesta de tipo JSON de la siguiente 129 | manera: 130 | 131 | :: 132 | 133 | Registro Correcto 134 | 135 | { 136 | "message": "Categoria registrado correctamente!", 137 | "error": "Ninguno" 138 | } 139 | 140 | Registro Incorrecto 141 | 142 | { 143 | "message": "Categoria no se ha podido registrar!", 144 | "error": { 145 | "name": [ 146 | "This field is required." 147 | ] 148 | } 149 | } 150 | 151 | Los errores retornados son por campo y por defecto retornará los que 152 | Django haya reconocido automáticamente de los modelos, si desea utilizar 153 | errores personalizados deberá utilizar un Form de Django personalizado, 154 | el cual debe indicarlo en el modelo. 155 | 156 | **FORM PERSONALIZADO** 157 | 158 | Si se desea utilizar un Form de Django personalizado para el registro o 159 | edición de un modelo deberá sobreescribir los siguientes métodos en su 160 | modelo: 161 | 162 | .. code:: python 163 | 164 | EJEMPLO 165 | 166 | # form para crear 167 | def get_create_form(self,form = None): 168 | from test_app.forms import CategoryForm 169 | self.create_form = CategoryForm 170 | return self.create_form 171 | 172 | # form para actualizar 173 | def get_update_form(self,form = None): 174 | from test_app.forms import CategoryForm 175 | self.update_form = CategoryForm 176 | return self.update_form 177 | 178 | Siempre deberá importar el form personalizado **dentro de la función**, 179 | nunca fuera de ella, esto para evitar un error conocido como 180 | ``Importación Circular``. 181 | 182 | BaseDetailAJAX 183 | -------------- 184 | 185 | .. code:: python 186 | 187 | class BaseDetailAJAX(BaseCrud): 188 | pass 189 | 190 | Vista Basada en Clase encargada de retornar la instancia del modelo que 191 | se le haya indicado de forma automática. 192 | 193 | Recibe herencia de ``BaseCrud``, la cuál se encarga de realizar las 194 | validaciones correspondientes a permisos y login\_required. 195 | 196 | Retorna la información del objeto en formato JSON. 197 | 198 | :: 199 | 200 | Ejemplo 201 | 202 | { 203 | "pk": 1, 204 | "fields": { 205 | "model_state": true, 206 | "name": "abarrote" 207 | } 208 | } 209 | 210 | Los campos retornados son aquellos que no estén incluidos en el atributo 211 | del modelo ``exclude_fields`` 212 | 213 | BaseUpdateAJAX 214 | -------------- 215 | 216 | .. code:: python 217 | 218 | class BaseUpdateAJAX(BaseCrud): 219 | pass 220 | 221 | Vista Basada en Clase encargada de realizar la actualización de un 222 | registro para el modelo indicado automáticamente. 223 | 224 | Recibe herencia de ``BaseCrud``, la cuál se encarga de realizar las 225 | validaciones correspondientes a permisos y login\_required. 226 | 227 | Los nombres de los campos que deben ser enviados en la petición, 228 | request.POST, deben tener el mismo nombre que tienen estos en el modelo. 229 | 230 | Al actualizar correctamente la instancia o haber problemas al 231 | actualizar, retornará una respuesta de tipo JSON de la siguiente manera: 232 | 233 | :: 234 | 235 | Actualización Correcto 236 | 237 | { 238 | "message": "Categoria actualizada correctamente!", 239 | "error": "Ninguno" 240 | } 241 | 242 | Actualización Incorrecto 243 | 244 | { 245 | "message": "Categoria no se ha podido actualizar!", 246 | "error": { 247 | "name": [ 248 | "This field is required." 249 | ] 250 | } 251 | } 252 | 253 | Los errores retornados son por campo y por defecto retornará los que 254 | Django haya reconocido automáticamente de los modelos, si desea utilizar 255 | errores personalizados deberá utilizar un Form de Django personalizado, 256 | el cual debe indicarlo en el modelo. 257 | 258 | **FORM PERSONALIZADO** 259 | 260 | Si se desea utilizar un Form de Django personalizado para el registro o 261 | edición de un modelo deberá sobreescribir los siguientes métodos en su 262 | modelo: 263 | 264 | .. code:: python 265 | 266 | EJEMPLO 267 | 268 | # form para crear 269 | def get_create_form(self,form = None): 270 | from test_app.forms import CategoryForm 271 | self.create_form = CategoryForm 272 | return self.create_form 273 | 274 | # form para actualizar 275 | def get_update_form(self,form = None): 276 | from test_app.forms import CategoryForm 277 | self.update_form = CategoryForm 278 | return self.update_form 279 | 280 | Siempre deberá importar el form personalizado **dentro de la función**, 281 | nunca fuera de ella, esto para evitar un error conocido como 282 | ``Importación Circular``. 283 | 284 | BaseDirectDeleteAJAX 285 | -------------------- 286 | 287 | .. code:: python 288 | 289 | class BaseDirectDeleteAJAX(BaseCrud): 290 | pass 291 | 292 | Vista Basada en Clase encargada de realizar la eliminación directa en la 293 | Base de Datos de un registro para el modelo indicado automáticamente. 294 | 295 | Recibe herencia de ``BaseCrud``, la cuál se encarga de realizar las 296 | validaciones correspondientes a permisos y login\_required. 297 | 298 | La respuesta dependerá de si se encontró o no el objeto que se desea 299 | eliminar. 300 | 301 | :: 302 | 303 | Objeto encontrado 304 | 305 | { 306 | "message": "Categoria eliminado correctamente!", 307 | "error": "Ninguno" 308 | } 309 | 310 | Objeto no encontrado 311 | 312 | { 313 | "error": "No se ha encontrado un registro con estos datos." 314 | } 315 | 316 | BaseLogicDeleteAJAX 317 | ------------------- 318 | 319 | .. code:: python 320 | 321 | class BaseLogicDeleteAJAX(BaseCrud): 322 | pass 323 | 324 | Vista Basada en Clase encargada de realizar la eliminación lógica de un 325 | registro para el modelo indicado automáticamente, es decir, colocará el 326 | campo ``model_state`` en ``False``. 327 | 328 | Recibe herencia de ``BaseCrud``, la cuál se encarga de realizar las 329 | validaciones correspondientes a permisos y login\_required. 330 | 331 | La respuesta dependerá de si se encontró o no el objeto que se desea 332 | eliminar. 333 | 334 | :: 335 | 336 | Objeto encontrado 337 | 338 | { 339 | "message": "Categoria eliminado correctamente!", 340 | "error": "Ninguno" 341 | } 342 | 343 | Objeto no encontrado 344 | 345 | { 346 | "error": "No se ha encontrado un registro con estos datos." 347 | } 348 | 349 | -------------------------------------------------------------------------------- /docs/base-model.md: -------------------------------------------------------------------------------- 1 | # BaseModel 2 | 3 | BaseModel es el modelo padre de tipo **Abstracto**, tiene una herencia de `models.Model`, es decir: 4 | 5 | ```python 6 | 7 | from django.db import models 8 | 9 | class BaseModel(models.Model): 10 | 11 | class Meta: 12 | abstract = True 13 | 14 | ``` 15 | 16 | Aquí es donde se han definido las características principales de Django Automatic CRUD, es el enlace con los modelos ya que, a través de la herencia, todo modelo obtendrá los siguientes campos: 17 | 18 | ```python 19 | id = models.AutoField(primary_key = True) 20 | model_state = models.BooleanField(default = True) 21 | date_created = models.DateTimeField('Fecha de Creación', auto_now=False, auto_now_add=True) 22 | date_modified = models.DateTimeField('Fecha de Modificación', auto_now=True, auto_now_add=False) 23 | date_deleted = models.DateTimeField('Fecha de Eliminación', auto_now=True, auto_now_add=False) 24 | ``` 25 | 26 | > model*state* es usado dentro de Django Automatic CRUD para la eliminación lógica. 27 | 28 | Y los siguientes atributos: 29 | 30 | all_cruds_types = True 31 | normal_cruds = False 32 | ajax_crud = False 33 | server_side = False 34 | exclude_model = False 35 | login_required = False 36 | permission_required = () 37 | model_permissions = False 38 | default_permissions = False 39 | exclude_fields = ['date_created','date_modified','date_deleted','model_state'] 40 | 41 | success_create_message = "registrado correctamente!" 42 | success_update_message = "actualizado correctamente!" 43 | success_delete_message = "eliminado correctamente!" 44 | 45 | error_create_message = "no se ha podido registrar!" 46 | error_update_message = "no se ha podido actualizar!" 47 | non_found_message = "No se ha encontrado un registro con estos datos!" 48 | 49 | create_template = None 50 | update_template = None 51 | list_template = None 52 | detail_template = None 53 | 54 | ## Atributos de modelos que hereden de BaseModel 55 | 56 | - **all_cruds_types** - si su valor es `True` generará CRUDS de tipo: Normales y AJAX, en `False` no generará ningún CRUD. 57 | - **normal_cruds** - si _all_cruds_types_ es `True`, el valor de este campo no será tomado en cuenta, si _all_cruds_types_ es `False` y este campo es `True`, generará CRUDS de tipo `Normal`. 58 | - **ajax_crud** - si _all_cruds_types_ es `True`, el valor de este campo no será tomado en cuenta, si _all_cruds_types_ es `False` y este campo es `True`, generará CRUDS de tipo `AJAX`. 59 | - **server_side** - si su valor es `True` se realizará la paginación del lado del servidor, esto es válido sólo para _CRUDS de tipo AJAX_, retornará la siguiente estructura: 60 | 61 | { 62 | 'length': # número de registros, 63 | 'objects': # lista de datos por página 64 | } 65 | 66 | - **exclude_model** - si su valor es `True`, no se generarán CRUDS para el modelo, aún cuando _all_cruds_types_ sea `True`. 67 | - **login_required** - si su valor es `True`, solicitará que un quien realice la petición haya iniciado sesión. Se recomiendo realizar un `login(user)` de Django en la implementación de su sistema de Login. 68 | - **permission_required** - tupla de permisos a solicitarse para un usuario que realice la petición a cualquier ruta de Django Automatic CRUD sólo si _model_permission_ es `True`. 69 | - **model_permissions** - si su valor es `True`, solicitará permisos para el usuario que realice la petición. 70 | - **default_permissions** - si su valor es `True`, los permisos a solicitar serán los básicos de Django, es decir, add,change,view,delete. 71 | - **exclude_fields** - lista de campos excluidos, estos campos no serán tomados en cuenta para listar, editar, crear o cuando se obtenga el detalle de un registro. Por defecto los campos excluidos son los campos: `date_created,date_modified,date_deleted,model_state`. 72 | 73 | - **success_create_message** - mensaje por defecto mostrado cuando se realiza un nuevo registro del modelo. Este campo es concatenado con el nombre del modelo, es decir: `{model.__name__} success_create_message`, por ejemplo: `Persona registrada correctamente`. **Válido sólo para CRUDS AJAX**. 74 | - **success_update_message** - mensaje por defecto mostrado cuando se realiza una edición de un registro del modelo. Este campo es concatenado con el nombre del modelo, al igual que _success_create_message_. **Válido sólo para CRUDS AJAX**. 75 | - **success_delete_message** - mensaje por defecto mostrado cuando se realiza una eliminación de un registro del modelo, ya sea eliminación lógica o directa. Este campo es concatenado con el nombre del modelo, al igual que _success_create_message_. **Válido sólo para CRUDS AJAX**. 76 | 77 | - **error_create_message** - mensaje por defecto mostrado cuando ocurre un error al realizarse un nuevo registro del modelo. Este campo es concatenado con el nombre del modelo, al igual que _success_create_message_. **Válido sólo para CRUDS AJAX**. 78 | - **error_update_message** - mensaje por defecto mostrado cuando ocurre un error al realizarse una edición de un registro del modelo. Este campo es concatenado con el nombre del modelo, al igual que _success_create_message_. **Válido sólo para CRUDS AJAX**. 79 | - **non_found_message** - mensaje por defecto mostrado cuando no se encuentra un obtjeto solicitado. **Válido sólo para CRUDS AJAX**. 80 | 81 | - **create_template** - nombre de template de creación para los CRUDS Normales del modelo. Por defecto el sistema solicita un template llamado `{model.__name__}_create.html`. 82 | - **update_template** - nombre de template de edición para los CRUDS Normales del modelo. Por defecto el sistema solicita un template llamado `{model.__name__}_update.html`. 83 | - **list_template** - nombre de template de listado para los CRUDS Normales del modelo. Por defecto el sistema solicita un template llamado `{model.__name__}_list.html`. 84 | - **detail_template** - nombre de template de detalle para los CRUDS Normales del modelo. Por defecto el sistema solicita un template llamado `{model.__name__}_detail.html`. 85 | 86 | 87 | **NOTA** 88 | 89 | El nombre solicitado de forma automática por los templates para CRUDS Normales son generados por una función llamada build_template_name, puedes encontrar información en [build_template_name](extra-functions.md#build_template_name) -------------------------------------------------------------------------------- /docs/base-model.rst: -------------------------------------------------------------------------------- 1 | BaseModel 2 | ========= 3 | 4 | BaseModel es el modelo padre de tipo **Abstracto**, tiene una herencia 5 | de ``models.Model``, es decir: 6 | 7 | .. code:: python 8 | 9 | 10 | from django.db import models 11 | 12 | class BaseModel(models.Model): 13 | 14 | class Meta: 15 | abstract = True 16 | 17 | Aquí es donde se han definido las características principales de Django 18 | Automatic CRUD, es el enlace con los modelos ya que, a través de la 19 | herencia, todo modelo obtendrá los siguientes campos: 20 | 21 | .. code:: python 22 | 23 | id = models.AutoField(primary_key = True) 24 | model_state = models.BooleanField(default = True) 25 | date_created = models.DateTimeField('Fecha de Creación', auto_now=False, auto_now_add=True) 26 | date_modified = models.DateTimeField('Fecha de Modificación', auto_now=True, auto_now_add=False) 27 | date_deleted = models.DateTimeField('Fecha de Eliminación', auto_now=True, auto_now_add=False) 28 | 29 | model_state es usado dentro de Django Automatic CRUD para la 30 | eliminación lógica. 31 | 32 | Y los siguientes atributos: 33 | 34 | :: 35 | 36 | all_cruds_types = True 37 | normal_cruds = False 38 | ajax_crud = False 39 | server_side = False 40 | exclude_model = False 41 | login_required = False 42 | permission_required = () 43 | model_permissions = False 44 | default_permissions = False 45 | exclude_fields = ['date_created','date_modified','date_deleted','model_state'] 46 | 47 | success_create_message = "registrado correctamente!" 48 | success_update_message = "actualizado correctamente!" 49 | success_delete_message = "eliminado correctamente!" 50 | 51 | error_create_message = "no se ha podido registrar!" 52 | error_update_message = "no se ha podido actualizar!" 53 | non_found_message = "No se ha encontrado un registro con estos datos!" 54 | 55 | create_template = None 56 | update_template = None 57 | list_template = None 58 | detail_template = None 59 | 60 | Atributos de modelos que hereden de BaseModel 61 | --------------------------------------------- 62 | 63 | - **all\_cruds\_types** - si su valor es ``True`` generará CRUDS de 64 | tipo: Normales y AJAX, en ``False`` no generará ningún CRUD. 65 | - **normal\_cruds** - si *all*\ cruds\_types\_ es ``True``, el valor de 66 | este campo no será tomado en cuenta, si *all*\ cruds\_types\_ es 67 | ``False`` y este campo es ``True``, generará CRUDS de tipo 68 | ``Normal``. 69 | - **ajax\_crud** - si *all*\ cruds\_types\_ es ``True``, el valor de 70 | este campo no será tomado en cuenta, si *all*\ cruds\_types\_ es 71 | ``False`` y este campo es ``True``, generará CRUDS de tipo ``AJAX``. 72 | - **server\_side** - si su valor es ``True`` se realizará la paginación 73 | del lado del servidor, esto es válido sólo para *CRUDS de tipo AJAX*, 74 | retornará la siguiente estructura: 75 | 76 | :: 77 | 78 | { 79 | 'length': # número de registros, 80 | 'objects': # lista de datos por página 81 | } 82 | 83 | - **exclude\_model** - si su valor es ``True``, no se generarán CRUDS 84 | para el modelo, aún cuando *all*\ cruds\_types\_ sea ``True``. 85 | - **login\_required** - si su valor es ``True``, solicitará que un 86 | quien realice la petición haya iniciado sesión. Se recomiendo 87 | realizar un ``login(user)`` de Django en la implementación de su 88 | sistema de Login. 89 | - **permission\_required** - tupla de permisos a solicitarse para un 90 | usuario que realice la petición a cualquier ruta de Django Automatic 91 | CRUD sólo si *model*\ permission\_ es ``True``. 92 | - **model\_permissions** - si su valor es ``True``, solicitará permisos 93 | para el usuario que realice la petición. 94 | - **default\_permissions** - si su valor es ``True``, los permisos a 95 | solicitar serán los básicos de Django, es decir, 96 | add,change,view,delete. 97 | - **exclude\_fields** - lista de campos excluidos, estos campos no 98 | serán tomados en cuenta para listar, editar, crear o cuando se 99 | obtenga el detalle de un registro. Por defecto los campos excluidos 100 | son los campos: 101 | ``date_created,date_modified,date_deleted,model_state``. 102 | 103 | - **success\_create\_message** - mensaje por defecto mostrado cuando se 104 | realiza un nuevo registro del modelo. Este campo es concatenado con 105 | el nombre del modelo, es decir: 106 | ``{model.__name__} success_create_message``, por ejemplo: 107 | ``Persona registrada correctamente``. **Válido sólo para CRUDS 108 | AJAX**. 109 | - **success\_update\_message** - mensaje por defecto mostrado cuando se 110 | realiza una edición de un registro del modelo. Este campo es 111 | concatenado con el nombre del modelo, al igual que 112 | *success*\ create\_message\_. **Válido sólo para CRUDS AJAX**. 113 | - **success\_delete\_message** - mensaje por defecto mostrado cuando se 114 | realiza una eliminación de un registro del modelo, ya sea eliminación 115 | lógica o directa. Este campo es concatenado con el nombre del modelo, 116 | al igual que *success*\ create\_message\_. **Válido sólo para CRUDS 117 | AJAX**. 118 | 119 | - **error\_create\_message** - mensaje por defecto mostrado cuando 120 | ocurre un error al realizarse un nuevo registro del modelo. Este 121 | campo es concatenado con el nombre del modelo, al igual que 122 | *success*\ create\_message\_. **Válido sólo para CRUDS AJAX**. 123 | - **error\_update\_message** - mensaje por defecto mostrado cuando 124 | ocurre un error al realizarse una edición de un registro del modelo. 125 | Este campo es concatenado con el nombre del modelo, al igual que 126 | *success*\ create\_message\_. **Válido sólo para CRUDS AJAX**. 127 | - **non\_found\_message** - mensaje por defecto mostrado cuando no se 128 | encuentra un obtjeto solicitado. **Válido sólo para CRUDS AJAX**. 129 | 130 | - **create\_template** - nombre de template de creación para los CRUDS 131 | Normales del modelo. Por defecto el sistema solicita un template 132 | llamado ``{model.__name__}_create.html``. 133 | - **update\_template** - nombre de template de edición para los CRUDS 134 | Normales del modelo. Por defecto el sistema solicita un template 135 | llamado ``{model.__name__}_update.html``. 136 | - **list\_template** - nombre de template de listado para los CRUDS 137 | Normales del modelo. Por defecto el sistema solicita un template 138 | llamado ``{model.__name__}_list.html``. 139 | - **detail\_template** - nombre de template de detalle para los CRUDS 140 | Normales del modelo. Por defecto el sistema solicita un template 141 | llamado ``{model.__name__}_detail.html``. 142 | 143 | **NOTA** 144 | 145 | El nombre solicitado de forma automática por los templates para CRUDS 146 | Normales son generados por una función llamada build\_template\_name, 147 | puedes encontrar información en el apartado de **Funciones Extra - build_template_name** 148 | -------------------------------------------------------------------------------- /docs/data-types.md: -------------------------------------------------------------------------------- 1 | # Tipos de Datos 2 | 3 | Los tipos de datos a los que hago referencia son a las Anotaciones de Python, una forma de realizar código Python estático, es sabido que Python es de tipado Dinámico, sin embargo, desde hace unas versiones podemos indicar tipado estático, claro está que no es tomado en cuenta por el intérprete sino sirve como ayuda para los programadores. 4 | 5 | Los tipos de datos que encontrarás en Django Automatic CRUD son: 6 | 7 | * **XLS** - Tipo de Dato de Excel. 8 | * **Instance** - Tipo de Dato de Instancia. 9 | * **URLList** - Tipo de Dato Listado de URLS. 10 | * **DjangoForm** - Tipo de Dato Form de Django. 11 | * **JsonResponse** - Tipo de Dato Respuesta en JSON. -------------------------------------------------------------------------------- /docs/data-types.rst: -------------------------------------------------------------------------------- 1 | Tipos de Datos 2 | ============== 3 | 4 | Los tipos de datos a los que hago referencia son a las Anotaciones de 5 | Python, una forma de realizar código Python estático, es sabido que 6 | Python es de tipado Dinámico, sin embargo, desde hace unas versiones 7 | podemos indicar tipado estático, claro está que no es tomado en cuenta 8 | por el intérprete sino sirve como ayuda para los programadores. 9 | 10 | Los tipos de datos que encontrarás en Django Automatic CRUD son: 11 | 12 | - **XLS** - Tipo de Dato de Excel. 13 | - **Instance** - Tipo de Dato de Instancia. 14 | - **URLList** - Tipo de Dato Listado de URLS. 15 | - **DjangoForm** - Tipo de Dato Form de Django. 16 | - **JsonResponse** - Tipo de Dato Respuesta en JSON. 17 | 18 | -------------------------------------------------------------------------------- /docs/excel-report.md: -------------------------------------------------------------------------------- 1 | # Reporte en Excel 2 | 3 | Django Automatic CRUD incluye una ruta extra que genera un Reporte en formato de Excel, este reporte es automatizado, toma el modelo en cuestión y genera toda la estructura definida, es decir, `título, cabecera y cuerpo`. 4 | 5 | El constructor de esta clase llamada `ExcelReportFormat`, define las siguientes variables y parámetros: 6 | 7 | Parámetros: 8 | _app_name nombre de la aplicación donde está el modelo en cuestión. 9 | _model_name nombre del modelo a utilizar. 10 | 11 | Variables: 12 | _app_name nombre de la aplicación donde está el modelo en cuestión. 13 | _model_name nombre del modelo a utilizar. 14 | __model modelo a usarse. 15 | __model_fields_names lista de campos del modelo. 16 | __queryset queryset del modelo, contiene todos los registros de este. 17 | __report_title título del reporte. 18 | __workbook instancia de Workbook. 19 | __sheetwork hoja de Excel a utilizarse, por defecto es la primera. 20 | 21 | La construcción del Reporte se realiza por etapas: 22 | 23 | * **Etapa 1** - lo primero que se realiza es la asignación de las variables iniciales. 24 | 25 | ```python 26 | def __init__(self,__app_name:str,__model_name:str, *args, **kwargs): 27 | self.__app_name = __app_name 28 | self.__model_name = __model_name 29 | self.__model = get_model(self.__app_name,self.__model_name) 30 | self.__model_fields_names = get_model_fields_names(self.__model) 31 | self.__queryset = get_queryset(self.__model) 32 | self.__report_title = _excel_report_title(self.__model_name) 33 | self.__workbook = Workbook() 34 | self.__sheetwork = self.__workbook.active 35 | ``` 36 | 37 | * **Etapa 2** - se genera la cabecera del reporte, es decir, los nombres de cada campo como cabecera de las celdas donde se pintarán cada valor de cada campo, estos campos se obtienen de la variable `__model_fields_names`, en estos no se incluyen los campos que se encuentran en el atributo del modelo `exclude_fields`. 38 | 39 | ```python 40 | def __excel_report_header(self,row_dimension = 15, col_dimension = 25): 41 | pass 42 | ``` 43 | 44 | * **Etapa 3** - se pintan los valores para cada campos que se colocó en la cabecera de la tabla, dichos valores se obtienen de la variable `__queryset`. 45 | 46 | ```python 47 | def __print_values(self): 48 | ``` 49 | 50 | * **Etapa 4** - se construye la respuesta de tipo `ms-excel`, se toma el título que se genera con la función: 51 | 52 | ```python 53 | def _excel_report_title(__model_name: str): 54 | """ 55 | Build report title with today date 56 | """ 57 | 58 | 59 | date = datetime.now() 60 | title = "REPORTE DE {0} EN FORMATO EXCEL REALIZADO EN LA FECHA: {1}".format( 61 | __model_name.upper(), 62 | "%s/%s/%s" % ( 63 | date.day,date.month, 64 | date.year 65 | ) 66 | ) 67 | return title 68 | ``` 69 | 70 | Y se procede a construir una respuesta de tipo HttpResponse a la cual se le agrega el reporte. 71 | 72 | ```python 73 | def get_excel_report(self): 74 | pass 75 | ``` 76 | 77 | * Etapa 5 - finalmente hay una función que agrupa todos estos pasos, la cual construye como tal el reporte. 78 | 79 | ```python 80 | def build_report(self): 81 | """ 82 | Build report call 2 functions: __excel_report_header and __print_values 83 | """ 84 | 85 | self.__excel_report_header() 86 | self.__print_values() 87 | ``` 88 | --- 89 | 90 | Sin embargo, la clase `ExcelReportFormat` NO genera el retorno del Reporte en Excel que la URL dedicada llama, sino lo retorna la vista `GetExcelReport`, la cual recibe herencia de `BaseCrudMixin` y de `TemplateView`. 91 | 92 | ```python 93 | class GetExcelReport(BaseCrudMixin,TemplateView): 94 | pass 95 | ``` -------------------------------------------------------------------------------- /docs/excel-report.rst: -------------------------------------------------------------------------------- 1 | Reporte en Excel 2 | ================ 3 | 4 | Django Automatic CRUD incluye una ruta extra que genera un Reporte en 5 | formato de Excel, este reporte es automatizado, toma el modelo en 6 | cuestión y genera toda la estructura definida, es decir, 7 | ``título, cabecera y cuerpo``. 8 | 9 | El constructor de esta clase llamada ``ExcelReportFormat``, define las 10 | siguientes variables y parámetros: 11 | 12 | :: 13 | 14 | Parámetros: 15 | _app_name nombre de la aplicación donde está el modelo en cuestión. 16 | _model_name nombre del modelo a utilizar. 17 | 18 | Variables: 19 | _app_name nombre de la aplicación donde está el modelo en cuestión. 20 | _model_name nombre del modelo a utilizar. 21 | __model modelo a usarse. 22 | __model_fields_names lista de campos del modelo. 23 | __queryset queryset del modelo, contiene todos los registros de este. 24 | __report_title título del reporte. 25 | __workbook instancia de Workbook. 26 | __sheetwork hoja de Excel a utilizarse, por defecto es la primera. 27 | 28 | La construcción del Reporte se realiza por etapas: 29 | 30 | - **Etapa 1** - lo primero que se realiza es la asignación de las 31 | variables iniciales. 32 | 33 | .. code:: python 34 | 35 | def __init__(self,__app_name:str,__model_name:str, *args, **kwargs): 36 | self.__app_name = __app_name 37 | self.__model_name = __model_name 38 | self.__model = get_model(self.__app_name,self.__model_name) 39 | self.__model_fields_names = get_model_fields_names(self.__model) 40 | self.__queryset = get_queryset(self.__model) 41 | self.__report_title = _excel_report_title(self.__model_name) 42 | self.__workbook = Workbook() 43 | self.__sheetwork = self.__workbook.active 44 | 45 | - **Etapa 2** - se genera la cabecera del reporte, es decir, los 46 | nombres de cada campo como cabecera de las celdas donde se pintarán 47 | cada valor de cada campo, estos campos se obtienen de la variable 48 | ``__model_fields_names``, en estos no se incluyen los campos que se 49 | encuentran en el atributo del modelo ``exclude_fields``. 50 | 51 | .. code:: python 52 | 53 | def __excel_report_header(self,row_dimension = 15, col_dimension = 25): 54 | pass 55 | 56 | - **Etapa 3** - se pintan los valores para cada campos que se colocó en 57 | la cabecera de la tabla, dichos valores se obtienen de la variable 58 | ``__queryset``. 59 | 60 | .. code:: python 61 | 62 | def __print_values(self): 63 | 64 | - **Etapa 4** - se construye la respuesta de tipo ``ms-excel``, se toma 65 | el título que se genera con la función: 66 | 67 | .. code:: python 68 | 69 | def _excel_report_title(__model_name: str): 70 | """ 71 | Build report title with today date 72 | """ 73 | 74 | 75 | date = datetime.now() 76 | title = "REPORTE DE {0} EN FORMATO EXCEL REALIZADO EN LA FECHA: {1}".format( 77 | __model_name.upper(), 78 | "%s/%s/%s" % ( 79 | date.day,date.month, 80 | date.year 81 | ) 82 | ) 83 | return title 84 | 85 | Y se procede a construir una respuesta de tipo HttpResponse a la cual se 86 | le agrega el reporte. 87 | 88 | .. code:: python 89 | 90 | def get_excel_report(self): 91 | pass 92 | 93 | - Etapa 5 - finalmente hay una función que agrupa todos estos pasos, la 94 | cual construye como tal el reporte. 95 | 96 | .. code:: python 97 | 98 | def build_report(self): 99 | """ 100 | Build report call 2 functions: __excel_report_header and __print_values 101 | """ 102 | 103 | self.__excel_report_header() 104 | self.__print_values() 105 | 106 | -------------- 107 | 108 | Sin embargo, la clase ``ExcelReportFormat`` NO genera el retorno del 109 | Reporte en Excel que la URL dedicada llama, sino lo retorna la vista 110 | ``GetExcelReport``, la cual recibe herencia de ``BaseCrudMixin`` y de 111 | ``TemplateView``. 112 | 113 | .. code:: python 114 | 115 | class GetExcelReport(BaseCrudMixin,TemplateView): 116 | pass 117 | 118 | -------------------------------------------------------------------------------- /docs/extra-functions.md: -------------------------------------------------------------------------------- 1 | # Funciones Extra 2 | 3 | ## get_model 4 | 5 | ```python 6 | def get_model(__app_name:str,__model_name:str) -> Instance: 7 | # return the model corresponding to the application name and model name sent 8 | return apps.get_model(app_label = __app_name,model_name = __model_name) 9 | ``` 10 | 11 | - **\_\_app_name** - Nombre de la aplicación donde está el modelo en cadena de texto 12 | - **\_\_model_name** - Nombre del modelo en cadena de texto 13 | 14 | Retorna el modelo en cuestión para el nombre de la aplicación y modelo indicados. 15 | 16 | ## get_object 17 | 18 | ```python 19 | def get_object(model: Instance,pk: int): 20 | # return the record for a pk sended 21 | instance = model.objects.filter(id = pk,model_state = True).first() 22 | if instance: 23 | return instance 24 | return None 25 | ``` 26 | 27 | - **model** - Modelo a realizar la consulta. 28 | - **pk** - ID de registro a buscar. 29 | 30 | Retorna la instancia del modelo perteneciente al pk enviado. 31 | 32 | ## get_model_fields_names 33 | 34 | ```python 35 | def get_model_fields_names(__model: Instance) -> List: 36 | # return a list of field names from a model 37 | return [name for name,_ in models.fields_for_model(__model).items()] 38 | ``` 39 | 40 | * **__model** - Modelo del cual se desea obtener los nombres de sus campos. 41 | 42 | Retorna una lista con los nombres de los campos del modelo enviado. 43 | 44 | ## get_queryset 45 | 46 | ```python 47 | def get_queryset(__model:Instance) -> Dict: 48 | # returns all records in a dictionary for a model 49 | return __model.objects.all().values() 50 | ``` 51 | 52 | * **__model** - Modelo del cual se desea obtener los nombres de sus campos. 53 | 54 | Retorna todos los datos registrados en la base de datos para el modelo indicado. 55 | Esta función se utiliza en el Reporte en Excel generado automáticamente. 56 | 57 | ## get_form 58 | 59 | ```python 60 | def get_form(form: DjangoForm,model: Instance) -> DjangoForm: 61 | """ 62 | Return a Django Form for a model, also a Django Form can be indicated 63 | by default the Django Form will exclude the 'state' field from the model 64 | 65 | """ 66 | 67 | 68 | if form is not None: 69 | return models.modelform_factory(model = model,form = form) 70 | else: 71 | return models.modelform_factory(model = model,exclude = ('model_state',)) 72 | ``` 73 | 74 | * **model** - Modelo en el cual se desea basar el Form de Django a crearse. 75 | * **form** - Form de Django opcional a utilizarse en la creación de un Form de Django basado en modelo. 76 | 77 | Retorna un Form de Django basado en el modelo indicado. 78 | Opcionalmente recibe el parámetro form, el cuál se utilizará para generarlo el nuevo Form en caso se desee utilizar uno personalizado. 79 | Para que el Form se genere automáticamente sin necesidad de enviarle el parámetro `form`, este debe ser enviado como None. 80 | 81 | ## build_template_name 82 | 83 | ```python 84 | def build_template_name(template_name: str,model: Instance,action:str) -> str: 85 | """ 86 | Build template name with app label from model, model name and action(list,create,update,detail) 87 | 88 | """ 89 | 90 | 91 | if template_name == None: 92 | template_name = '{0}/{1}_{2}.html'.format( 93 | model._meta.app_label, 94 | model._meta.object_name.lower(), 95 | action 96 | ) 97 | return template_name 98 | ``` 99 | 100 | * **model** - Modelo del cuál se desea generar los nombres de templates solicitados en CRUDS Normales. 101 | * **template_name** - Nombre del template a utilizarse en la vista de CRUDS Normales. -------------------------------------------------------------------------------- /docs/extra-functions.rst: -------------------------------------------------------------------------------- 1 | Funciones Extra 2 | =============== 3 | 4 | get\_model 5 | ---------- 6 | 7 | .. code:: python 8 | 9 | def get_model(__app_name:str,__model_name:str) -> Instance: 10 | # return the model corresponding to the application name and model name sent 11 | return apps.get_model(app_label = __app_name,model_name = __model_name) 12 | 13 | - **\_\_app\_name** - Nombre de la aplicación donde está el modelo en 14 | cadena de texto 15 | - **\_\_model\_name** - Nombre del modelo en cadena de texto 16 | 17 | Retorna el modelo en cuestión para el nombre de la aplicación y modelo 18 | indicados. 19 | 20 | get\_object 21 | ----------- 22 | 23 | .. code:: python 24 | 25 | def get_object(model: Instance,pk: int): 26 | # return the record for a pk sended 27 | instance = model.objects.filter(id = pk,model_state = True).first() 28 | if instance: 29 | return instance 30 | return None 31 | 32 | - **model** - Modelo a realizar la consulta. 33 | - **pk** - ID de registro a buscar. 34 | 35 | Retorna la instancia del modelo perteneciente al pk enviado. 36 | 37 | get\_model\_fields\_names 38 | ------------------------- 39 | 40 | .. code:: python 41 | 42 | def get_model_fields_names(__model: Instance) -> List: 43 | # return a list of field names from a model 44 | return [name for name,_ in models.fields_for_model(__model).items()] 45 | 46 | - \*\*\_\_model\*\* - Modelo del cual se desea obtener los nombres de 47 | sus campos. 48 | 49 | Retorna una lista con los nombres de los campos del modelo enviado. 50 | 51 | get\_queryset 52 | ------------- 53 | 54 | .. code:: python 55 | 56 | def get_queryset(__model:Instance) -> Dict: 57 | # returns all records in a dictionary for a model 58 | return __model.objects.all().values() 59 | 60 | - \*\*\_\_model\*\* - Modelo del cual se desea obtener los nombres de 61 | sus campos. 62 | 63 | Retorna todos los datos registrados en la base de datos para el modelo 64 | indicado. Esta función se utiliza en el Reporte en Excel generado 65 | automáticamente. 66 | 67 | get\_form 68 | --------- 69 | 70 | .. code:: python 71 | 72 | def get_form(form: DjangoForm,model: Instance) -> DjangoForm: 73 | """ 74 | Return a Django Form for a model, also a Django Form can be indicated 75 | by default the Django Form will exclude the 'state' field from the model 76 | 77 | """ 78 | 79 | 80 | if form is not None: 81 | return models.modelform_factory(model = model,form = form) 82 | else: 83 | return models.modelform_factory(model = model,exclude = ('model_state',)) 84 | 85 | - **model** - Modelo en el cual se desea basar el Form de Django a 86 | crearse. 87 | - **form** - Form de Django opcional a utilizarse en la creación de un 88 | Form de Django basado en modelo. 89 | 90 | Retorna un Form de Django basado en el modelo indicado. Opcionalmente 91 | recibe el parámetro form, el cuál se utilizará para generarlo el nuevo 92 | Form en caso se desee utilizar uno personalizado. Para que el Form se 93 | genere automáticamente sin necesidad de enviarle el parámetro ``form``, 94 | este debe ser enviado como None. 95 | 96 | build\_template\_name 97 | --------------------- 98 | 99 | .. code:: python 100 | 101 | def build_template_name(template_name: str,model: Instance,action:str) -> str: 102 | """ 103 | Build template name with app label from model, model name and action(list,create,update,detail) 104 | 105 | """ 106 | 107 | 108 | if template_name == None: 109 | template_name = '{0}/{1}_{2}.html'.format( 110 | model._meta.app_label, 111 | model._meta.object_name.lower(), 112 | action 113 | ) 114 | return template_name 115 | 116 | - **model** - Modelo del cuál se desea generar los nombres de templates 117 | solicitados en CRUDS Normales. 118 | - **template\_name** - Nombre del template a utilizarse en la vista de 119 | CRUDS Normales. 120 | 121 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Django Automatic CRUD (CRUD Automáticos con Django) 2 | 3 | Django Automatic CRUD es un proyecto que genera CRUDS automáticos para cada modelo que tenga la herencia indicada mas adelante. Estos CRUDS y URLS pueden ser de 2 tipos: **Normales y AJAX**. 4 | 5 | ## Nota 6 | 7 | **CRUDS Normales: ** Estos CRUDS son accesibles utilizando el Sistema de Plantillas de Django e incluyen validaciones de errores, existencia de templates, inicio de sesión y permisos. 8 | 9 | **CRUDS AJAX: ** Estos CURDS son accesibles utilizando JavaScript o cualquier herramienta que permita realizar una petición a una URL indicada. 10 | 11 | ## Características 12 | 13 | * CRUDS automáticos con sólo crear los modelos. 14 | * URLS generadas automáticamente para cada tipo de CRUD de modelo. 15 | * Ruta para generación automática de un Reporte en formato Excel. 16 | * Validación de Inicio de Sesión. 17 | * Validación de Permisos. 18 | * CRUDS automáticos independientes, es decir, pueden generarse de los 2 tipos, sólo de uno o independiente. 19 | * Campos a excluir para listado, registro, edición y detalle de modelo de forma dinámica. 20 | * Mensajes de error automáticos y customizables. 21 | * Nombre de templates para CRUDS customizables. 22 | * Form de Django para CRUDS dinámico. 23 | * Server-side. 24 | * Paginación de datos. 25 | 26 | ## Pre-Requisitos 27 | 28 | - Django >= 2.2 29 | - Python >= 3.3 30 | 31 | ## Instalación Rápida 32 | 33 | - Crea un entorno virtual e inicialo. 34 | - Ejecuta el siguiente comando desde tu consola: 35 | 36 | ``` 37 | pip install django-automatic-crud 38 | ``` 39 | 40 | - Agrega automatic_crud a tu INSTALLED_APPS: 41 | ``` 42 | INSTALLED_APPS = [ 43 | ... 44 | 'automatic_crud', 45 | ... 46 | ] 47 | ``` 48 | 49 | ## Generación de CRUDS 50 | 51 | - Para cada modelo que desees generar los CRUDS, deben heredar de BaseModel, por ejemplo: 52 | 53 | ```python 54 | 55 | from automatic_crud.models import BaseModel 56 | 57 | class NewModel(BaseModel): 58 | ... 59 | 60 | ``` 61 | 62 | - Agrega la siguiente linea en tu archivo urls.py global: 63 | 64 | ```python 65 | path('automatic-crud/',include('automatic_crud.urls')) 66 | ``` 67 | 68 | - Ahora, ingresa a tu navegador y escribe una ruta que no exista para que Django pueda mostrarte todas las rutas existentes, te mostrará 14 rutas para cada modelo que herede de BaseModel, las cuales estarán dentro de la estructura de ruta: `http://localhost:8000/automatic-crud/` y tendrán el siguiente patrón: 69 | 70 | ```python 71 | 72 | automatic_crud/ app_name/ model_name / list / [name="app_name-model_name-list"] 73 | automatic_crud/ app_name/ model_name / create / [name="app_name-model_name-create"] 74 | automatic_crud/ app_name/ model_name / detail / / [name="app_name-model_name-detail"] 75 | automatic_crud/ app_name/ model_name / update / / [name="app_name-model_name-update"] 76 | automatic_crud/ app_name/ model_name / logic-delete / / [name="app_name-model_name-logic-delete"] 77 | automatic_crud/ app_name/ model_name / direct-delete / / [name="app_name-model_name-direct-delete"] 78 | automatic_crud/ app_name/ model_name / excel-report / [name="app_name-model_name-excel-report"] 79 | 80 | automatic_crud/ ajax-app_name/ model_name / list / [name="app_name-model_name-list-ajax"] 81 | automatic_crud/ ajax-app_name/ model_name / create / [name="app_name-model_name-create-ajax"] 82 | automatic_crud/ ajax-app_name/ model_name / detail / / [name="app_name-model_name-detail-ajax"] 83 | automatic_crud/ ajax-app_name/ model_name / update / / [name="app_name-model_name-update-ajax"] 84 | automatic_crud/ ajax-app_name/ model_name / logic-delete / / [name="app_name-model_name-logic-delete-ajax"] 85 | automatic_crud/ ajax-app_name/ model_name / direct-delete / / [name="app_name-model_name-direct-delete-ajax"] 86 | automatic_crud/ ajax-app_name/ model_name / excel-report / [name="app_name-model_name-excel-report-ajax"] 87 | 88 | ``` 89 | 90 | --- 91 | 92 | Si quieres apoyar realizando una donación, puedes hacerla a este enlace: 93 | 94 | - [Donación al Proyecto](https://www.paypal.com/paypalme/oliversando) 95 | 96 | ## Redes Sociales 97 | 98 | [Web](http://www.developerpe.com) 99 | 100 | [Facebook](https://www.facebook.com/developerper​) 101 | 102 | [Instagram](https://www.instagram.com/developer.pe/​) 103 | 104 | [Twitter](https://twitter.com/Developerpepiur​) 105 | 106 | [Youtube](Developer.pe) 107 | 108 | **Correo: developerpeperu@gmail.com** 109 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Django Automatic CRUD (CRUD Automáticos con Django) 2 | =================================================== 3 | 4 | .. toctree:: 5 | :caption: Introduccion 6 | 7 | introduction 8 | acerca-de 9 | 10 | .. toctree:: 11 | :caption: Guía de Usuario 12 | 13 | base-model 14 | normal-cruds 15 | ajax-cruds 16 | excel-report 17 | data-types 18 | register-models 19 | extra-functions 20 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | Django Automatic CRUD (CRUD Automáticos con Django) 2 | =================================================== 3 | 4 | Django Automatic CRUD es un proyecto que genera CRUDS automáticos para 5 | cada modelo que tenga la herencia indicada mas adelante. Estos CRUDS y 6 | URLS pueden ser de 2 tipos: **Normales y AJAX**. 7 | 8 | Nota 9 | ---- 10 | 11 | **CRUDS Normales: ** Estos CRUDS son accesibles utilizando el Sistema de 12 | Plantillas de Django e incluyen validaciones de errores, existencia de 13 | templates, inicio de sesión y permisos. 14 | 15 | **CRUDS AJAX: ** Estos CURDS son accesibles utilizando JavaScript o 16 | cualquier herramienta que permita realizar una petición a una URL 17 | indicada. 18 | 19 | Características 20 | --------------- 21 | 22 | - CRUDS automáticos con sólo crear los modelos. 23 | - URLS generadas automáticamente para cada tipo de CRUD de modelo. 24 | - Ruta para generación automática de un Reporte en formato Excel. 25 | - Validación de Inicio de Sesión. 26 | - Validación de Permisos. 27 | - CRUDS automáticos independientes, es decir, pueden generarse de los 2 28 | tipos, sólo de uno o independiente. 29 | - Campos a excluir para listado, registro, edición y detalle de modelo 30 | de forma dinámica. 31 | - Mensajes de error automáticos y customizables. 32 | - Nombre de templates para CRUDS customizables. 33 | - Form de Django para CRUDS dinámico. 34 | - Server-side. 35 | - Paginación de datos. 36 | 37 | Pre-Requisitos 38 | -------------- 39 | 40 | - Django >= 2.2 41 | - Python >= 3.3 42 | 43 | Instalación Rápida 44 | ------------------ 45 | 46 | - Crea un entorno virtual e inicialo. 47 | - Ejecuta el siguiente comando desde tu consola: 48 | 49 | :: 50 | 51 | pip install django-automatic-crud 52 | 53 | - Agrega automatic\_crud a tu INSTALLED\_APPS: 54 | 55 | :: 56 | 57 | INSTALLED_APPS = [ 58 | ... 59 | 'automatic_crud', 60 | ... 61 | ] 62 | 63 | Generación de CRUDS 64 | ------------------- 65 | 66 | - Para cada modelo que desees generar los CRUDS, deben heredar de 67 | BaseModel, por ejemplo: 68 | 69 | .. code:: python 70 | 71 | 72 | from automatic_crud.models import BaseModel 73 | 74 | class NewModel(BaseModel): 75 | ... 76 | 77 | - Agrega la siguiente linea en tu archivo urls.py global: 78 | 79 | .. code:: python 80 | 81 | path('automatic-crud/',include('automatic_crud.urls')) 82 | 83 | - Ahora, ingresa a tu navegador y escribe una ruta que no exista para 84 | que Django pueda mostrarte todas las rutas existentes, te mostrará 14 85 | rutas para cada modelo que herede de BaseModel, las cuales estarán 86 | dentro de la estructura de ruta: 87 | ``http://localhost:8000/automatic-crud/`` y tendrán el siguiente 88 | patrón: 89 | 90 | .. code:: python 91 | 92 | 93 | automatic_crud/ app_name/ model_name / list / [name="app_name-model_name-list"] 94 | automatic_crud/ app_name/ model_name / create / [name="app_name-model_name-create"] 95 | automatic_crud/ app_name/ model_name / detail / / [name="app_name-model_name-detail"] 96 | automatic_crud/ app_name/ model_name / update / / [name="app_name-model_name-update"] 97 | automatic_crud/ app_name/ model_name / logic-delete / / [name="app_name-model_name-logic-delete"] 98 | automatic_crud/ app_name/ model_name / direct-delete / / [name="app_name-model_name-direct-delete"] 99 | automatic_crud/ app_name/ model_name / excel-report / [name="app_name-model_name-excel-report"] 100 | 101 | automatic_crud/ ajax-app_name/ model_name / list / [name="app_name-model_name-list-ajax"] 102 | automatic_crud/ ajax-app_name/ model_name / create / [name="app_name-model_name-create-ajax"] 103 | automatic_crud/ ajax-app_name/ model_name / detail / / [name="app_name-model_name-detail-ajax"] 104 | automatic_crud/ ajax-app_name/ model_name / update / / [name="app_name-model_name-update-ajax"] 105 | automatic_crud/ ajax-app_name/ model_name / logic-delete / / [name="app_name-model_name-logic-delete-ajax"] 106 | automatic_crud/ ajax-app_name/ model_name / direct-delete / / [name="app_name-model_name-direct-delete-ajax"] 107 | automatic_crud/ ajax-app_name/ model_name / excel-report / [name="app_name-model_name-excel-report-ajax"] 108 | 109 | -------------- 110 | 111 | Si quieres apoyar realizando una donación, puedes hacerla a este enlace: 112 | 113 | - `Donación al 114 | Proyecto `__ 115 | 116 | Redes Sociales 117 | -------------- 118 | 119 | `Web `__ 120 | 121 | `Facebook `__ 122 | 123 | `Instagram `__ 124 | 125 | `Twitter `__ 126 | 127 | `Youtube `__ 128 | 129 | **Correo: developerpeperu@gmail.com** 130 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Documentación de Django Automatic CRUD 2 | 3 | nav: 4 | - Inicio: 'index.md' 5 | - Acerca de: 'about.md' 6 | - 'Guía de Usuario': 7 | - 'BaseModel': 'base-model.md' 8 | - 'CRUDS Normales': 'normal-cruds.md' 9 | - 'CRUDS AJAX': 'ajax-cruds.md' 10 | - 'Reporte en Excel': 'excel-report.md' 11 | - 'Tipos de Datos': 'data-types.md' 12 | - 'Registro de Modelos': 'register-models.md' 13 | - 'Funciones Extras': 'extra-functions.md' 14 | 15 | theme: readthedocs -------------------------------------------------------------------------------- /docs/normal-cruds.md: -------------------------------------------------------------------------------- 1 | # CRUDS Normales 2 | 3 | ## BaseList 4 | 5 | ```python 6 | class BaseList(BaseCrudMixin,ListView): 7 | pass 8 | ``` 9 | 10 | Vista Basada en Clase encargada de generar y retornar el listado de registros para el modelo que se le haya indicado de forma automática. 11 | 12 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required, y de la clase Genérica de Django, ListView. 13 | 14 | El listado de registros obtenidos para el modelo indicado serán retornados al template bajo el nombre de `object_list` 15 | 16 | ## BaseCreate 17 | 18 | ```python 19 | class BaseCreate(BaseCrudMixin,CreateView): 20 | pass 21 | ``` 22 | 23 | Vista Basada en Clase encargada de generar y retornar el Form de Django para el agregar registros del modelo que se le haya indicado de forma automática. 24 | 25 | Al registrar correctamente la instancia, redireccionará automáticamente a la ruta de Listado de CRUDS Normales. 26 | 27 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required, y de la clase Genérica de Django, CreateView. 28 | 29 | Retorna el form de Django para el modelo al template bajo el nombre de `form`. 30 | 31 | **FORM PERSONALIZADO** 32 | 33 | Si se desea utilizar un Form de Django personalizado para el registro o edición de un modelo deberá sobreescribir los siguientes métodos en su modelo: 34 | 35 | ```python 36 | EJEMPLO 37 | 38 | # form para crear 39 | def get_create_form(self,form = None): 40 | from test_app.forms import CategoryForm 41 | self.create_form = CategoryForm 42 | return self.create_form 43 | 44 | # form para actualizar 45 | def get_update_form(self,form = None): 46 | from test_app.forms import CategoryForm 47 | self.update_form = CategoryForm 48 | return self.update_form 49 | ``` 50 | 51 | Siempre deberá importar el form personalizado **dentro de la función**, nunca fuera de ella, esto para evitar un error conocido como `Importación Circular`. 52 | 53 | ## BaseDetail 54 | 55 | ```python 56 | class BaseDetail(BaseCrudMixin,DetailView): 57 | pass 58 | ``` 59 | 60 | Vista Basada en Clase encargada de retornar la instancias del modelo que se le haya indicado de forma automática. 61 | 62 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required, y de la clase Genérica de Django, DetailView. 63 | 64 | Retorna la instancia del modelo al template bajo el nombre de `object`. 65 | 66 | ## BaseUpdate 67 | 68 | ```python 69 | class BaseUpdate(BaseCrudMixin,UpdateView): 70 | pass 71 | ``` 72 | 73 | Vista Basada en Clase encargada de generar y retornar el Form de Django para la edición de una instancia del modelo que se le haya indicado de forma automática. 74 | 75 | Al editar correctamente la instancia, redireccionará automáticamente a la ruta de Listado de CRUDS Normales. 76 | 77 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required, y de la clase Genérica de Django, UpdateView. 78 | 79 | Retorna el form de Django para el modelo al template bajo el nombre de `form`. 80 | 81 | Retorna la instancia del modelo al template bajo el nombre de `object`. 82 | 83 | **FORM PERSONALIZADO** 84 | 85 | Si se desea utilizar un Form de Django personalizado para el registro o edición de un modelo deberá sobreescribir los siguientes métodos en su modelo: 86 | 87 | ```python 88 | EJEMPLO 89 | 90 | # form para crear 91 | def get_create_form(self,form = None): 92 | from test_app.forms import CategoryForm 93 | self.create_form = CategoryForm 94 | return self.create_form 95 | 96 | # form para actualizar 97 | def get_update_form(self,form = None): 98 | from test_app.forms import CategoryForm 99 | self.update_form = CategoryForm 100 | return self.update_form 101 | ``` 102 | 103 | Siempre deberá importar el form personalizado **dentro de la función**, nunca fuera de ella, esto para evitar un error conocido como `Importación Circular`. 104 | 105 | ## BaseDirectDelete 106 | 107 | ```python 108 | class BaseDirectDelete(BaseCrudMixin,DeleteView): 109 | pass 110 | ``` 111 | 112 | Vista Basada en Clase encargada de eliminar directamente de la Base de Datos la instancia del modelo que se le haya indicado de forma automática. 113 | 114 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required, y de la clase Genérica de Django, DeleteView. 115 | 116 | Al eliminar correctamente la instancia, redireccionará automáticamente a la ruta de Listado de CRUDS Normales. 117 | 118 | ## BaseLogicDelete 119 | 120 | ```python 121 | class BaseLogicDelete(BaseCrudMixin,DeleteView): 122 | pass 123 | ``` 124 | 125 | Vista Basada en Clase encargada de eliminar de forma lógica, es decir cambiando el campo `model_state` a `False` de la instancia del modelo que se le haya indicado de forma automática. 126 | 127 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las validaciones correspondientes a permisos y login_required, y de la clase Genérica de Django, DeleteView. 128 | 129 | Al eliminar correctamente la instancia, redireccionará automáticamente a la ruta de Listado de CRUDS Normales. -------------------------------------------------------------------------------- /docs/normal-cruds.rst: -------------------------------------------------------------------------------- 1 | CRUDS Normales 2 | ============== 3 | 4 | BaseList 5 | -------- 6 | 7 | .. code:: python 8 | 9 | class BaseList(BaseCrudMixin,ListView): 10 | pass 11 | 12 | Vista Basada en Clase encargada de generar y retornar el listado de 13 | registros para el modelo que se le haya indicado de forma automática. 14 | 15 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las 16 | validaciones correspondientes a permisos y login\_required, y de la 17 | clase Genérica de Django, ListView. 18 | 19 | El listado de registros obtenidos para el modelo indicado serán 20 | retornados al template bajo el nombre de ``object_list`` 21 | 22 | Si se coloca el parámetro ``normal_pagination`` en ``True`` se aplicará la paginación normal de Django, 23 | por defecto mostrará 10 elementos por página, sin embargo, esto se puede modificar con el 24 | atributo ``values_for_page`` 25 | 26 | BaseCreate 27 | ---------- 28 | 29 | .. code:: python 30 | 31 | class BaseCreate(BaseCrudMixin,CreateView): 32 | pass 33 | 34 | Vista Basada en Clase encargada de generar y retornar el Form de Django 35 | para el agregar registros del modelo que se le haya indicado de forma 36 | automática. 37 | 38 | Al registrar correctamente la instancia, redireccionará automáticamente 39 | a la ruta de Listado de CRUDS Normales. 40 | 41 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las 42 | validaciones correspondientes a permisos y login\_required, y de la 43 | clase Genérica de Django, CreateView. 44 | 45 | Retorna el form de Django para el modelo al template bajo el nombre de 46 | ``form``. 47 | 48 | **FORM PERSONALIZADO** 49 | 50 | Si se desea utilizar un Form de Django personalizado para el registro o 51 | edición de un modelo deberá sobreescribir los siguientes métodos en su 52 | modelo: 53 | 54 | .. code:: python 55 | 56 | EJEMPLO 57 | 58 | # form para crear 59 | def get_create_form(self,form = None): 60 | from test_app.forms import CategoryForm 61 | self.create_form = CategoryForm 62 | return self.create_form 63 | 64 | # form para actualizar 65 | def get_update_form(self,form = None): 66 | from test_app.forms import CategoryForm 67 | self.update_form = CategoryForm 68 | return self.update_form 69 | 70 | Siempre deberá importar el form personalizado **dentro de la función**, 71 | nunca fuera de ella, esto para evitar un error conocido como 72 | ``Importación Circular``. 73 | 74 | BaseDetail 75 | ---------- 76 | 77 | .. code:: python 78 | 79 | class BaseDetail(BaseCrudMixin,DetailView): 80 | pass 81 | 82 | Vista Basada en Clase encargada de retornar la instancias del modelo que 83 | se le haya indicado de forma automática. 84 | 85 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las 86 | validaciones correspondientes a permisos y login\_required, y de la 87 | clase Genérica de Django, DetailView. 88 | 89 | Retorna la instancia del modelo al template bajo el nombre de 90 | ``object``. 91 | 92 | BaseUpdate 93 | ---------- 94 | 95 | .. code:: python 96 | 97 | class BaseUpdate(BaseCrudMixin,UpdateView): 98 | pass 99 | 100 | Vista Basada en Clase encargada de generar y retornar el Form de Django 101 | para la edición de una instancia del modelo que se le haya indicado de 102 | forma automática. 103 | 104 | Al editar correctamente la instancia, redireccionará automáticamente a 105 | la ruta de Listado de CRUDS Normales. 106 | 107 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las 108 | validaciones correspondientes a permisos y login\_required, y de la 109 | clase Genérica de Django, UpdateView. 110 | 111 | Retorna el form de Django para el modelo al template bajo el nombre de 112 | ``form``. 113 | 114 | Retorna la instancia del modelo al template bajo el nombre de 115 | ``object``. 116 | 117 | **FORM PERSONALIZADO** 118 | 119 | Si se desea utilizar un Form de Django personalizado para el registro o 120 | edición de un modelo deberá sobreescribir los siguientes métodos en su 121 | modelo: 122 | 123 | .. code:: python 124 | 125 | EJEMPLO 126 | 127 | # form para crear 128 | def get_create_form(self,form = None): 129 | from test_app.forms import CategoryForm 130 | self.create_form = CategoryForm 131 | return self.create_form 132 | 133 | # form para actualizar 134 | def get_update_form(self,form = None): 135 | from test_app.forms import CategoryForm 136 | self.update_form = CategoryForm 137 | return self.update_form 138 | 139 | Siempre deberá importar el form personalizado **dentro de la función**, 140 | nunca fuera de ella, esto para evitar un error conocido como 141 | ``Importación Circular``. 142 | 143 | BaseDirectDelete 144 | ---------------- 145 | 146 | .. code:: python 147 | 148 | class BaseDirectDelete(BaseCrudMixin,DeleteView): 149 | pass 150 | 151 | Vista Basada en Clase encargada de eliminar directamente de la Base de 152 | Datos la instancia del modelo que se le haya indicado de forma 153 | automática. 154 | 155 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las 156 | validaciones correspondientes a permisos y login\_required, y de la 157 | clase Genérica de Django, DeleteView. 158 | 159 | Al eliminar correctamente la instancia, redireccionará automáticamente a 160 | la ruta de Listado de CRUDS Normales. 161 | 162 | BaseLogicDelete 163 | --------------- 164 | 165 | .. code:: python 166 | 167 | class BaseLogicDelete(BaseCrudMixin,DeleteView): 168 | pass 169 | 170 | Vista Basada en Clase encargada de eliminar de forma lógica, es decir 171 | cambiando el campo ``model_state`` a ``False`` de la instancia del 172 | modelo que se le haya indicado de forma automática. 173 | 174 | Recibe herencia de BaseCrudMixin, la cuál se encarga de realizar las 175 | validaciones correspondientes a permisos y login\_required, y de la 176 | clase Genérica de Django, DeleteView. 177 | 178 | Al eliminar correctamente la instancia, redireccionará automáticamente a 179 | la ruta de Listado de CRUDS Normales. 180 | -------------------------------------------------------------------------------- /docs/register-models.md: -------------------------------------------------------------------------------- 1 | # Resgistro de Modelos 2 | 3 | La magia de la automatización de Django Automatic CRUD recae en esta funcionalidad, dentro de la instalación del paquete, es necesario incluir las rutas del paquete como tal en el archivo urls.py del proyecto donde se vaya a utilizar, esto se realiza por un motivo en específico que en si, es el motivo principal. 4 | 5 | Cuando nosotros vinculamos estas rutas, lo que hacemos en si es llamar a la función `register_models` ya que el archivo urls de Django Automatic CRUD lo que contiene es: 6 | 7 | ```python 8 | from automatic_crud.register import register_models 9 | 10 | urlpatterns = [] 11 | 12 | urlpatterns += register_models() 13 | ``` 14 | 15 | Esta función lo que realiza es una iteración de todos los modelos que existen dentro de las aplicaciones registradas en el proyecto donde se esté utilizando, excluyendo los modelos: `ContentType,LogEntry,Session,Permission,Group`. 16 | 17 | Las validaciones que se hacen es que si o si el modelo debe ser de tipo `BaseModel` o que tenga los atributos de este tipo de modelos, se valida que el modelo tenga el atributo `exclude_model` en `True` y para agregar las URLS de cada tipo de CRUD que Django Automatic CRUD permite, es decir, tomando en cuenta los atributos del modelo `all_cruds_types, ajax_crud y normal_cruds`. 18 | 19 | Finalmente se retornan las rutas generadas para cada modelo ya que en cada iteración por cada modelo se agregan las rutas a un listado de rutas que estarán en la variable `urlpatterns`. -------------------------------------------------------------------------------- /docs/register-models.rst: -------------------------------------------------------------------------------- 1 | Resgistro de Modelos 2 | ==================== 3 | 4 | La magia de la automatización de Django Automatic CRUD recae en esta 5 | funcionalidad, dentro de la instalación del paquete, es necesario 6 | incluir las rutas del paquete como tal en el archivo urls.py del 7 | proyecto donde se vaya a utilizar, esto se realiza por un motivo en 8 | específico que en si, es el motivo principal. 9 | 10 | Cuando nosotros vinculamos estas rutas, lo que hacemos en si es llamar a 11 | la función ``register_models`` ya que el archivo urls de Django 12 | Automatic CRUD lo que contiene es: 13 | 14 | .. code:: python 15 | 16 | from automatic_crud.register import register_models 17 | 18 | urlpatterns = [] 19 | 20 | urlpatterns += register_models() 21 | 22 | Esta función lo que realiza es una iteración de todos los modelos que 23 | existen dentro de las aplicaciones registradas en el proyecto donde se 24 | esté utilizando, excluyendo los modelos: 25 | ``ContentType,LogEntry,Session,Permission,Group``. 26 | 27 | Las validaciones que se hacen es que si o si el modelo debe ser de tipo 28 | ``BaseModel`` o que tenga los atributos de este tipo de modelos, se 29 | valida que el modelo tenga el atributo ``exclude_model`` en ``True`` y 30 | para agregar las URLS de cada tipo de CRUD que Django Automatic CRUD 31 | permite, es decir, tomando en cuenta los atributos del modelo 32 | ``all_cruds_types, ajax_crud y normal_cruds``. 33 | 34 | Finalmente se retornan las rutas generadas para cada modelo ya que en 35 | cada iteración por cada modelo se agregan las rutas a un listado de 36 | rutas que estarán en la variable ``urlpatterns``. 37 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.3.1 2 | bleach==3.3.0 3 | certifi==2020.12.5 4 | chardet==4.0.0 5 | click==7.1.2 6 | colorama==0.4.4 7 | Django==3.1.14 8 | docutils==0.17.1 9 | et-xmlfile==1.0.1 10 | future==0.18.2 11 | idna==2.10 12 | importlib-metadata==4.0.1 13 | Jinja2==2.11.3 14 | joblib==1.0.1 15 | keyring==23.0.1 16 | livereload==2.6.3 17 | lunr==0.5.8 18 | Markdown==3.3.4 19 | MarkupSafe==1.1.1 20 | mkdocs==1.1.2 21 | nltk==3.6.1 22 | openpyxl==3.0.7 23 | packaging==20.9 24 | pkginfo==1.7.0 25 | Pygments==2.8.1 26 | pyparsing==2.4.7 27 | pytz==2021.1 28 | pywin32-ctypes==0.2.0 29 | PyYAML==5.4.1 30 | readme-renderer==29.0 31 | regex==2021.4.4 32 | requests==2.25.1 33 | requests-toolbelt==0.9.1 34 | rfc3986==1.4.0 35 | six==1.15.0 36 | sqlparse==0.4.1 37 | tornado==6.1 38 | tqdm==4.60.0 39 | twine==3.4.1 40 | urllib3==1.26.4 41 | webencodings==0.5.1 42 | zipp==3.4.1 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.3.1 2 | Django==3.1.7 3 | et-xmlfile==1.0.1 4 | openpyxl==3.0.7 5 | pytz==2021.1 6 | sqlparse==0.4.1 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | with open("README.md", "r", encoding="utf-8") as fh: 5 | long_description = fh.read() 6 | 7 | 8 | setup( 9 | name='django-automatic-crud', 10 | version='1.2.0', 11 | packages=['automatic_crud'], 12 | include_package_data=True, 13 | license='BSD License', 14 | description='CRUDS Automáticos con Django', 15 | long_description=long_description, 16 | long_description_content_type="text/markdown", 17 | url='https://github.com/developerpe/django-automatic-crud', 18 | author='Oliver Sandoval', 19 | author_email='developerpeperu@gmail.com', 20 | install_requires=[ 21 | 'Django>=2.2', 22 | 'openpyxl==3.0.7', 23 | ], 24 | classifiers=[ 25 | 'Environment :: Web Environment', 26 | 'Framework :: Django', 27 | 'Intended Audience :: Developers', 28 | 'License :: OSI Approved :: BSD License', 29 | 'Operating System :: OS Independent', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 3.3' 32 | ] 33 | ) -------------------------------------------------------------------------------- /test_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developerpe/django-automatic-crud/e826e42ca3ca1675d868b3d5f2fb2bea8afacbeb/test_app/__init__.py -------------------------------------------------------------------------------- /test_app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.models import Permission 3 | 4 | admin.site.register(Permission) 5 | -------------------------------------------------------------------------------- /test_app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TestAppConfig(AppConfig): 5 | name = 'test_app' 6 | -------------------------------------------------------------------------------- /test_app/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from test_app.models import Category 3 | 4 | class CategoryForm(forms.ModelForm): 5 | class Meta: 6 | model = Category 7 | exclude = ('model_state',) 8 | 9 | def clean_name(self): 10 | data = self.cleaned_data['name'] 11 | if data != '' or data is not None: 12 | print("Hola desde form personalizado") 13 | return data 14 | 15 | -------------------------------------------------------------------------------- /test_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from automatic_crud.models import BaseModel 4 | 5 | 6 | 7 | class Category(BaseModel): 8 | """Model definition for Category.""" 9 | 10 | # TODO: Define fields here 11 | name = models.CharField('Nombre de Categoría', max_length=150) 12 | 13 | exclude_fields = ['date_created','date_modified','date_deleted'] 14 | exclude_model = False 15 | server_side = True 16 | login_required = False 17 | model_permissions = False 18 | default_permissions = True 19 | all_cruds_types = True 20 | normal_cruds = False 21 | ajax_crud = False 22 | 23 | list_template = None 24 | 25 | def get_create_form(self,form = None): 26 | from test_app.forms import CategoryForm 27 | self.create_form = CategoryForm 28 | return self.create_form 29 | 30 | def get_update_form(self,form = None): 31 | from test_app.forms import CategoryForm 32 | self.update_form = CategoryForm 33 | return self.update_form 34 | 35 | class Meta: 36 | """Meta definition for Category.""" 37 | 38 | verbose_name = 'Categoria' 39 | verbose_name_plural = 'Categories' 40 | 41 | def __str__(self): 42 | """Unicode representation of Category.""" 43 | return self.name 44 | 45 | def natural_key(self): 46 | return self.name 47 | 48 | class Product(BaseModel): 49 | """Model definition for Product.""" 50 | 51 | # TODO: Define fields here 52 | name = models.CharField('Nombre de Producto', max_length=150) 53 | category = models.ForeignKey(Category, on_delete=models.CASCADE) 54 | 55 | class Meta: 56 | """Meta definition for Product.""" 57 | 58 | verbose_name = 'Product' 59 | verbose_name_plural = 'Products' 60 | 61 | def __str__(self): 62 | """Unicode representation of Product.""" 63 | return self.name 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /test_app/templates/test_app/category_create.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | {{ form.as_p }} 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /test_app/templates/test_app/category_detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ object.name }} 8 | 9 | 10 |

{{ object.name }}

11 | 12 | -------------------------------------------------------------------------------- /test_app/templates/test_app/category_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | {% if object_list %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for category in object_list %} 19 | 20 | 21 | 22 | 23 | 24 | {% endfor %} 25 | 26 |
NombreEstadoOpciones
{{ category.name }}{{ category.model_state }}
27 | {% else %} 28 |

No existen categorías registradas.

29 | {% endif %} 30 | 31 | -------------------------------------------------------------------------------- /test_app/templates/test_app/category_update.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Edicion de {{ object }} 8 | 9 | 10 |
11 | {{ form.as_p }} 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /test_app/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from test_app.models import * 4 | 5 | urlpatterns = [ 6 | 7 | ] --------------------------------------------------------------------------------