├── .gitignore ├── README.md ├── cloud-services-django-s3 ├── Documents S3.postman_collection.json ├── README.md └── cloudbox │ ├── cloudbox │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py │ ├── documents │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_document_file.py │ │ ├── 0003_alter_document_file.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py │ ├── manage.py │ └── requirements.txt ├── conceptos-python-backend ├── README.md └── clase1 - conceptos_python_backend.ipynb ├── intro-flask ├── README.md ├── app.py ├── requirements.txt └── templates │ ├── index.html │ ├── login.html │ └── welcome.html ├── orm-flask ├── .env.example ├── README.md ├── app.py ├── database.py ├── requirements.txt └── templates │ ├── index.html │ ├── login.html │ ├── products │ ├── create.html │ ├── index.html │ └── update.html │ └── register.html ├── rest-api-with-DRF ├── README.md ├── requirements.txt └── shopping_cart │ ├── __init__.py │ ├── api │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py │ ├── manage.py │ └── shopping_cart │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── rest-api-with-django ├── README.md ├── REST API con Django - Shopping Cart.postman_collection.json └── shopping_cart ├── api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── manage.py └── shopping_cart ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .ipynb_checkpoints/ 3 | .DS_Store 4 | __pycache__ 5 | .pyc 6 | */env 7 | .env 8 | db.sqlite3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bootcamp Backend con Python 2 | Material de las clases del Bootcamp de Backend con Python de Código Facilito. 3 | -------------------------------------------------------------------------------- /cloud-services-django-s3/Documents S3.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "96f6c0b9-b389-41d9-b19d-05a3185284e0", 4 | "name": "Documents S3", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "4903572" 7 | }, 8 | "item": [ 9 | { 10 | "name": "/api/list", 11 | "request": { 12 | "method": "GET", 13 | "header": [], 14 | "url": { 15 | "raw": "http://127.0.0.1:8000/documents/list/", 16 | "protocol": "http", 17 | "host": [ 18 | "127", 19 | "0", 20 | "0", 21 | "1" 22 | ], 23 | "port": "8000", 24 | "path": [ 25 | "documents", 26 | "list", 27 | "" 28 | ] 29 | } 30 | }, 31 | "response": [] 32 | }, 33 | { 34 | "name": "/api/upload/", 35 | "request": { 36 | "method": "POST", 37 | "header": [], 38 | "body": { 39 | "mode": "formdata", 40 | "formdata": [ 41 | { 42 | "key": "file", 43 | "type": "file", 44 | "src": "/Users/carolinagomez/Documents/codigo_facilito/books.txt" 45 | }, 46 | { 47 | "key": "title", 48 | "value": "Another test", 49 | "type": "text" 50 | } 51 | ] 52 | }, 53 | "url": { 54 | "raw": "http://127.0.0.1:8000/documents/upload/", 55 | "protocol": "http", 56 | "host": [ 57 | "127", 58 | "0", 59 | "0", 60 | "1" 61 | ], 62 | "port": "8000", 63 | "path": [ 64 | "documents", 65 | "upload", 66 | "" 67 | ] 68 | } 69 | }, 70 | "response": [] 71 | }, 72 | { 73 | "name": "/documents/download//", 74 | "protocolProfileBehavior": { 75 | "disableBodyPruning": true 76 | }, 77 | "request": { 78 | "method": "GET", 79 | "header": [], 80 | "body": { 81 | "mode": "formdata", 82 | "formdata": [ 83 | { 84 | "key": "file", 85 | "type": "file", 86 | "src": "/Users/carolinagomez/Documents/codigo_facilito/books.txt" 87 | }, 88 | { 89 | "key": "title", 90 | "value": "Another test", 91 | "type": "text" 92 | } 93 | ] 94 | }, 95 | "url": { 96 | "raw": "http://127.0.0.1:8000/documents/download/4/", 97 | "protocol": "http", 98 | "host": [ 99 | "127", 100 | "0", 101 | "0", 102 | "1" 103 | ], 104 | "port": "8000", 105 | "path": [ 106 | "documents", 107 | "download", 108 | "4", 109 | "" 110 | ] 111 | } 112 | }, 113 | "response": [] 114 | }, 115 | { 116 | "name": "/documents/delete//", 117 | "request": { 118 | "method": "DELETE", 119 | "header": [], 120 | "body": { 121 | "mode": "formdata", 122 | "formdata": [ 123 | { 124 | "key": "file", 125 | "type": "file", 126 | "src": "/Users/carolinagomez/Documents/codigo_facilito/books.txt" 127 | }, 128 | { 129 | "key": "title", 130 | "value": "Another test", 131 | "type": "text" 132 | } 133 | ] 134 | }, 135 | "url": { 136 | "raw": "http://127.0.0.1:8000/documents/delete/5/", 137 | "protocol": "http", 138 | "host": [ 139 | "127", 140 | "0", 141 | "0", 142 | "1" 143 | ], 144 | "port": "8000", 145 | "path": [ 146 | "documents", 147 | "delete", 148 | "5", 149 | "" 150 | ] 151 | } 152 | }, 153 | "response": [] 154 | } 155 | ] 156 | } -------------------------------------------------------------------------------- /cloud-services-django-s3/README.md: -------------------------------------------------------------------------------- 1 | # Integración de Servicios Cloud con Django (S3) 2 | 3 | ## Proceso de Instalación 4 | 1. Crea un ambiente virtual: 5 | ``` 6 | python3 -m venv env 7 | ``` 8 | 2. Activa el ambiente virtual: 9 | ``` 10 | # Activación en Unix 11 | source env/bin/activate 12 | 13 | # Activación en Windows 14 | env\Scripts\activate 15 | ``` 16 | 3. Instala Django: 17 | ``` 18 | pip install django boto3 django-storages python-dotenv 19 | ``` 20 | 4. Crea un nuevo proyecto en Django: 21 | ``` 22 | django-admin startproject cloudbox 23 | ``` 24 | 5. Crea una nueva aplicación en Django: 25 | ``` 26 | cd cloudbox 27 | python manage.py startapp documents 28 | ``` 29 | 6. Crea un archivo `.env` con el siguiente contenido en el root del proyecto: 30 | ``` 31 | AWS_ACCESS_KEY_ID=your-access-key 32 | AWS_SECRET_ACCESS_KEY=your-secret-key 33 | AWS_STORAGE_BUCKET_NAME=your-bucket-name 34 | AWS_S3_REGION_NAME=your-region # e.g., us-west-1 35 | ``` 36 | 6. Agrega las siguientes lineas al final de INSTALLED_APPS en el archivo de `settings.py`: 37 | ``` 38 | INSTALLED_APPS = [ 39 | ... 40 | 'documents', 41 | 'storages' 42 | ] 43 | ``` 44 | 45 | 7. Genera las migraciones y ejeculatas: 46 | ``` 47 | python manage.py makemigrations 48 | python manage.py migrate 49 | ``` 50 | 8. Crea un super usuario: 51 | ``` 52 | python manage.py createsuperuser 53 | ``` 54 | 9. Corre la aplicación: 55 | ``` 56 | python manage.py runserver 57 | ``` 58 | 59 | ### Servicio Cloud 60 | 1. Crea una cuenta en [AWS](https://aws.amazon.com/es/). 61 | 2. Crea un bucket en [S3](https://s3.console.aws.amazon.com/s3). 62 | - Bucket Name: documents-django-bucket 63 | - Region: us-east-1 64 | 3. Crea un [IAM User](https://console.aws.amazon.com/iam/). 65 | - Click `Users` → `Create user` 66 | - User Name: `django-s3-user` 67 | - `Attach policies directly` → `AmazonS3FullAccess` 68 | - Abre el usuario y Crea llaves de acceso dando click en `Create access key` 69 | - Descarga el archivo con las llaves de acceso. 70 | 71 | ### Probando nuestra API 72 | 73 | Para probar nuestra REST API vamos a usar Postman para hacer peticiones a nuestra API, descargala [aquí.](https://www.postman.com/downloads/) 74 | 75 | Abre postman e importa la colección que esta en el repositorio. -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/cloudbox/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/cloud-services-django-s3/cloudbox/cloudbox/__init__.py -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/cloudbox/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for cloudbox project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cloudbox.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/cloudbox/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for cloudbox project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | from pathlib import Path 15 | from dotenv import load_dotenv 16 | 17 | # Load environment variables 18 | load_dotenv() 19 | 20 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 21 | BASE_DIR = Path(__file__).resolve().parent.parent 22 | 23 | 24 | # Quick-start development settings - unsuitable for production 25 | # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ 26 | 27 | # SECURITY WARNING: keep the secret key used in production secret! 28 | SECRET_KEY = "django-insecure-o09l6#%2hy)u36=m81&+52fvq^=o%*$kt=lh6elye5c+k#x#dc" 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | DEBUG = True 32 | 33 | ALLOWED_HOSTS = [] 34 | 35 | 36 | # Application definition 37 | 38 | INSTALLED_APPS = [ 39 | "django.contrib.admin", 40 | "django.contrib.auth", 41 | "django.contrib.contenttypes", 42 | "django.contrib.sessions", 43 | "django.contrib.messages", 44 | "django.contrib.staticfiles", 45 | "documents", 46 | "storages", 47 | ] 48 | 49 | STORAGES = { 50 | "default": { 51 | "BACKEND": "storages.backends.s3.S3Storage", 52 | "OPTIONS": { 53 | "location": "media", 54 | "file_overwrite": False, 55 | }, 56 | }, 57 | "staticfiles": { 58 | "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", 59 | }, 60 | } 61 | 62 | MIDDLEWARE = [ 63 | "django.middleware.security.SecurityMiddleware", 64 | "django.contrib.sessions.middleware.SessionMiddleware", 65 | "django.middleware.common.CommonMiddleware", 66 | "django.middleware.csrf.CsrfViewMiddleware", 67 | "django.contrib.auth.middleware.AuthenticationMiddleware", 68 | "django.contrib.messages.middleware.MessageMiddleware", 69 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 70 | ] 71 | 72 | ROOT_URLCONF = "cloudbox.urls" 73 | 74 | TEMPLATES = [ 75 | { 76 | "BACKEND": "django.template.backends.django.DjangoTemplates", 77 | "DIRS": [], 78 | "APP_DIRS": True, 79 | "OPTIONS": { 80 | "context_processors": [ 81 | "django.template.context_processors.request", 82 | "django.contrib.auth.context_processors.auth", 83 | "django.contrib.messages.context_processors.messages", 84 | ], 85 | }, 86 | }, 87 | ] 88 | 89 | WSGI_APPLICATION = "cloudbox.wsgi.application" 90 | 91 | 92 | # Database 93 | # https://docs.djangoproject.com/en/5.2/ref/settings/#databases 94 | 95 | DATABASES = { 96 | "default": { 97 | "ENGINE": "django.db.backends.sqlite3", 98 | "NAME": BASE_DIR / "db.sqlite3", 99 | } 100 | } 101 | 102 | 103 | # Password validation 104 | # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators 105 | 106 | AUTH_PASSWORD_VALIDATORS = [ 107 | { 108 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 109 | }, 110 | { 111 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 112 | }, 113 | { 114 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 115 | }, 116 | { 117 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 118 | }, 119 | ] 120 | 121 | 122 | # Internationalization 123 | # https://docs.djangoproject.com/en/5.2/topics/i18n/ 124 | 125 | LANGUAGE_CODE = "en-us" 126 | 127 | TIME_ZONE = "UTC" 128 | 129 | USE_I18N = True 130 | 131 | USE_TZ = True 132 | 133 | 134 | # Static files (CSS, JavaScript, Images) 135 | # https://docs.djangoproject.com/en/5.2/howto/static-files/ 136 | 137 | STATIC_URL = "static/" 138 | 139 | # Default primary key field type 140 | # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field 141 | 142 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 143 | 144 | AWS_ACCESS_KEY_ID = str(os.getenv("AWS_ACCESS_KEY_ID")) 145 | AWS_SECRET_ACCESS_KEY = str(os.getenv("AWS_SECRET_ACCESS_KEY")) 146 | AWS_STORAGE_BUCKET_NAME = str(os.getenv("AWS_STORAGE_BUCKET_NAME")) 147 | AWS_S3_REGION_NAME = str(os.getenv("AWS_S3_REGION_NAME")) 148 | AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com" 149 | 150 | 151 | MEDIA_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/media/" 152 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/cloudbox/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for cloudbox project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.contrib import admin 19 | from django.urls import path, include 20 | 21 | urlpatterns = [ 22 | path("admin/", admin.site.urls), 23 | path("documents/", include("documents.urls")), 24 | ] 25 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/cloudbox/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for cloudbox project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cloudbox.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/cloud-services-django-s3/cloudbox/documents/__init__.py -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.utils.html import format_html 3 | from .models import Document 4 | from .utils import generate_custom_presigned_url 5 | 6 | 7 | # Register your models here. 8 | class DocumentAdmin(admin.ModelAdmin): 9 | list_display = ("title", "file_link") 10 | 11 | def file_link(self, obj): 12 | if obj.file: 13 | url = generate_custom_presigned_url(obj.file.name) 14 | return format_html('Download', url) 15 | return "-" 16 | 17 | file_link.short_description = "File URL" 18 | 19 | 20 | admin.site.register(Document, DocumentAdmin) 21 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DocumentsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'documents' 7 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.2 on 2025-05-05 03:06 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Document', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=100)), 19 | ('file', models.FileField(upload_to='documents/')), 20 | ('uploaded_at', models.DateTimeField(auto_now_add=True)), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/migrations/0002_alter_document_file.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.2 on 2025-05-05 03:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('documents', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='document', 15 | name='file', 16 | field=models.FileField(upload_to='uploads/'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/migrations/0003_alter_document_file.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.2 on 2025-05-07 00:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('documents', '0002_alter_document_file'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='document', 15 | name='file', 16 | field=models.FileField(upload_to='documents'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/cloud-services-django-s3/cloudbox/documents/migrations/__init__.py -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/models.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | from django.db import models 3 | 4 | 5 | # Create your models here. 6 | 7 | 8 | class Document(models.Model): 9 | title = models.CharField(max_length=100) 10 | file = models.FileField( 11 | max_length=100, 12 | upload_to="documents", 13 | ) 14 | uploaded_at = models.DateTimeField(auto_now_add=True) 15 | 16 | def __str__(self): 17 | return self.title 18 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ( 3 | UploadDocumentView, 4 | ListDocumentsView, 5 | DownloadDocumentView, 6 | DeleteDocumentView, 7 | ) 8 | 9 | urlpatterns = [ 10 | path("upload/", UploadDocumentView.as_view(), name="upload-document"), 11 | path("list/", ListDocumentsView.as_view(), name="list-documents"), 12 | path( 13 | "download//", DownloadDocumentView.as_view(), name="download-document" 14 | ), 15 | path("delete//", DeleteDocumentView.as_view(), name="delete-document"), 16 | ] 17 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/utils.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | import boto3 3 | from django.conf import settings 4 | 5 | 6 | def generate_custom_presigned_url(file_name, expiration=3600): 7 | file_path = f"media/{file_name}" 8 | s3 = boto3.client( 9 | "s3", 10 | aws_access_key_id=settings.AWS_ACCESS_KEY_ID, 11 | aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, 12 | region_name=settings.AWS_S3_REGION_NAME, 13 | ) 14 | 15 | try: 16 | response = s3.generate_presigned_url( 17 | "get_object", 18 | Params={"Bucket": settings.AWS_STORAGE_BUCKET_NAME, "Key": file_path}, 19 | ExpiresIn=expiration, 20 | ) 21 | except Exception as e: 22 | return None 23 | 24 | return response 25 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/documents/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | from django.views import View 5 | from django.http import JsonResponse 6 | from django.shortcuts import get_object_or_404 7 | from .models import Document 8 | from .utils import generate_custom_presigned_url 9 | from django.views.decorators.csrf import csrf_exempt 10 | from django.utils.decorators import method_decorator 11 | 12 | 13 | @method_decorator(csrf_exempt, name="dispatch") 14 | class UploadDocumentView(View): 15 | def post(self, request): 16 | file = request.FILES.get("file") 17 | title = request.POST.get("title") 18 | 19 | if not file or not title: 20 | return JsonResponse({"error": "File and title are required"}, status=400) 21 | 22 | document = Document.objects.create(title=title, file=file) 23 | file_url = generate_custom_presigned_url(document.file.name) 24 | return JsonResponse( 25 | { 26 | "id": document.id, 27 | "title": document.title, 28 | "file_url": file_url, 29 | "uploaded_at": document.uploaded_at, 30 | }, 31 | status=201, 32 | ) 33 | 34 | 35 | class ListDocumentsView(View): 36 | def get(self, request): 37 | documents = Document.objects.all() 38 | data = [] 39 | for doc in documents: 40 | file_url = generate_custom_presigned_url(doc.file.name) 41 | data.append( 42 | { 43 | "id": doc.id, 44 | "title": doc.title, 45 | "original_url": doc.file.url, 46 | "file_url": file_url, 47 | "uploaded_at": doc.uploaded_at, 48 | } 49 | ) 50 | return JsonResponse(data, safe=False) 51 | 52 | 53 | class DownloadDocumentView(View): 54 | def get(self, request, id): 55 | document = get_object_or_404(Document, id=id) 56 | file_url = generate_custom_presigned_url(document.file.name) 57 | if not file_url: 58 | return JsonResponse({"error": "Could not generate URL"}, status=500) 59 | return JsonResponse({"url": file_url}) 60 | 61 | 62 | @method_decorator(csrf_exempt, name="dispatch") 63 | class DeleteDocumentView(View): 64 | def delete(self, request, id): 65 | document = get_object_or_404(Document, id=id) 66 | document.file.delete(save=False) # Delete from S3 67 | document.delete() # Delete DB entry 68 | return JsonResponse({"message": "Document deleted"}) 69 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cloudbox.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /cloud-services-django-s3/cloudbox/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.8.1 2 | boto3==1.38.8 3 | botocore==1.38.8 4 | Django==5.2 5 | django-storages==1.14.6 6 | jmespath==1.0.1 7 | python-dateutil==2.9.0.post0 8 | python-dotenv==1.1.0 9 | s3transfer==0.12.0 10 | six==1.17.0 11 | sqlparse==0.5.3 12 | urllib3==2.4.0 13 | -------------------------------------------------------------------------------- /conceptos-python-backend/README.md: -------------------------------------------------------------------------------- 1 | # Conceptos de Python para el Backend 2 | 3 | ## Proceso de Instalación 4 | 5 | Para seguir los ejemplos se pueden seguir las siguientes opciones: 6 | 7 | 1. [Instalar Python](https://www.python.org/downloads/) 8 | 9 | 2. [Instalar Anaconda y ejecutar el notebook localmente](https://docs.conda.io/projects/conda/en/latest/user-guide/install/) 10 | ``` 11 | conda create --name bootcamp-backend 12 | conda activate bootcamp-backend 13 | conda install jupyter 14 | jupyter notebook 15 | ``` -------------------------------------------------------------------------------- /conceptos-python-backend/clase1 - conceptos_python_backend.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2a0b777d-fedc-4110-b537-4a8ea3cf725c", 6 | "metadata": {}, 7 | "source": [ 8 | "# Bootcamp Backend con Python V2" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "e358065b-ca44-4871-a3a9-c4161f0b7d7f", 14 | "metadata": {}, 15 | "source": [ 16 | "## Clase 1: Conceptos de Python para el Backend\n", 17 | "## ¿Qué son los decoradores?\n", 18 | "Son un patrón de diseño en Python que permite agregar funcionalidades a un objeto existente (funciones) sin modificar su estructura." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "4677d9bd-40bc-46e8-9a6d-952b1765fead", 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "#@example_decorator\n", 29 | "def test_function():\n", 30 | " return \"output\"" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "id": "8dde72b0-9ae9-4941-84ca-0d6cf6118df5", 36 | "metadata": {}, 37 | "source": [ 38 | "### Cómo trabajan las funciones\n", 39 | "Las funciones son muy importantes en Python y estas retornan un valor de acuerdo a los argumentos que les pasamos:" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 2, 45 | "id": "3a1c464b-bffb-42d1-ae3e-9a4d929dbb3c", 46 | "metadata": {}, 47 | "outputs": [ 48 | { 49 | "data": { 50 | "text/plain": [ 51 | "9" 52 | ] 53 | }, 54 | "execution_count": 2, 55 | "metadata": {}, 56 | "output_type": "execute_result" 57 | } 58 | ], 59 | "source": [ 60 | "def plus_one(number):\n", 61 | " return number + 1\n", 62 | "plus_one(8)" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "id": "cb216805-520a-44bf-8274-20ccf031f311", 68 | "metadata": {}, 69 | "source": [ 70 | "### Asignando funciones a variables" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 3, 76 | "id": "87b02332-cf15-4dbb-b546-3b4a980fb252", 77 | "metadata": {}, 78 | "outputs": [ 79 | { 80 | "data": { 81 | "text/plain": [ 82 | "6" 83 | ] 84 | }, 85 | "execution_count": 3, 86 | "metadata": {}, 87 | "output_type": "execute_result" 88 | } 89 | ], 90 | "source": [ 91 | "def plus_one(number):\n", 92 | " return number + 1\n", 93 | "\n", 94 | "add_one = plus_one\n", 95 | "add_one(5)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "id": "06c9180e-7930-40d4-b105-c77c4464d345", 101 | "metadata": {}, 102 | "source": [ 103 | "### Definiendo funciones dentro de otras funciones" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 4, 109 | "id": "9fad7220-4909-48b9-878a-e961980bcd79", 110 | "metadata": {}, 111 | "outputs": [ 112 | { 113 | "name": "stdout", 114 | "output_type": "stream", 115 | "text": [ 116 | "Executing plus_one\n", 117 | "Executing add_one\n" 118 | ] 119 | }, 120 | { 121 | "data": { 122 | "text/plain": [ 123 | "5" 124 | ] 125 | }, 126 | "execution_count": 4, 127 | "metadata": {}, 128 | "output_type": "execute_result" 129 | } 130 | ], 131 | "source": [ 132 | "def plus_one(number):\n", 133 | " def add_one(number):\n", 134 | " print(\"Executing add_one\")\n", 135 | " return number + 1\n", 136 | "\n", 137 | " print(\"Executing plus_one\")\n", 138 | " result = add_one(number)\n", 139 | " return result\n", 140 | "\n", 141 | "plus_one(4)" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "id": "49b0ff8e-24da-471c-924f-bb49b31c8847", 147 | "metadata": {}, 148 | "source": [ 149 | "### Pasando funciones como argumentos de otras funciones" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 5, 155 | "id": "04fa8559-8399-4433-95e5-4d315aa070d8", 156 | "metadata": {}, 157 | "outputs": [ 158 | { 159 | "name": "stdout", 160 | "output_type": "stream", 161 | "text": [ 162 | "Executing function_call\n", 163 | "Executing plus_one\n" 164 | ] 165 | }, 166 | { 167 | "data": { 168 | "text/plain": [ 169 | "6" 170 | ] 171 | }, 172 | "execution_count": 5, 173 | "metadata": {}, 174 | "output_type": "execute_result" 175 | } 176 | ], 177 | "source": [ 178 | "def plus_one(number):\n", 179 | " print(\"Executing plus_one\")\n", 180 | " return number + 1\n", 181 | "\n", 182 | "def function_call(function):\n", 183 | " print(\"Executing function_call\")\n", 184 | " number_to_add = 5\n", 185 | " return function(number_to_add)\n", 186 | "\n", 187 | "function_call(plus_one)" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "id": "a40504b8-ee18-450c-91a6-a08266adb18f", 193 | "metadata": {}, 194 | "source": [ 195 | "### Funciones retornando otras funciones" 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": 6, 201 | "id": "115df9ef-35c8-499b-8e28-05581ab91255", 202 | "metadata": {}, 203 | "outputs": [ 204 | { 205 | "name": "stdout", 206 | "output_type": "stream", 207 | "text": [ 208 | "Executing hello_function\n", 209 | "Executing say_hi\n" 210 | ] 211 | }, 212 | { 213 | "data": { 214 | "text/plain": [ 215 | "'Hi'" 216 | ] 217 | }, 218 | "execution_count": 6, 219 | "metadata": {}, 220 | "output_type": "execute_result" 221 | } 222 | ], 223 | "source": [ 224 | "def hello_function():\n", 225 | " def say_hi():\n", 226 | " print(\"Executing say_hi\")\n", 227 | " return \"Hi\"\n", 228 | " print(\"Executing hello_function\")\n", 229 | " return say_hi\n", 230 | "\n", 231 | "hello = hello_function()\n", 232 | "hello()" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "id": "085557f2-bf9a-4bff-8c93-e7b5484b0c30", 238 | "metadata": {}, 239 | "source": [ 240 | "### Las funciones anidadas tienen acceso al las variables de la función envolvente" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": 7, 246 | "id": "2f692465-c106-4984-aafb-60f721f73b02", 247 | "metadata": {}, 248 | "outputs": [ 249 | { 250 | "name": "stdout", 251 | "output_type": "stream", 252 | "text": [ 253 | "Some random message\n" 254 | ] 255 | } 256 | ], 257 | "source": [ 258 | "def print_message(message):\n", 259 | " \"\"\"Enclosing Function\"\"\"\n", 260 | "\n", 261 | " def message_sender():\n", 262 | " \"\"\"Nested Function\"\"\"\n", 263 | "\n", 264 | " print(message)\n", 265 | "\n", 266 | " message_sender()\n", 267 | "\n", 268 | "print_message(\"Some random message\")" 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "id": "31c7d7c2-609a-48d4-b6dd-3563878a06ab", 274 | "metadata": {}, 275 | "source": [ 276 | "## Creando decoradores" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 8, 282 | "id": "48188821-5c4a-4ff6-8b2f-c2adaf9fe29d", 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "name": "stdout", 287 | "output_type": "stream", 288 | "text": [ 289 | "Something is happening before the function is called.\n", 290 | "Whee!\n", 291 | "Something is happening after the function is called.\n" 292 | ] 293 | } 294 | ], 295 | "source": [ 296 | "def my_decorator(func):\n", 297 | " def wrapper():\n", 298 | " print(\"Something is happening before the function is called.\")\n", 299 | " func()\n", 300 | " print(\"Something is happening after the function is called.\")\n", 301 | " return wrapper\n", 302 | "\n", 303 | "def say_whee():\n", 304 | " print(\"Whee!\")\n", 305 | "\n", 306 | "say_whee = my_decorator(say_whee)\n", 307 | "say_whee()" 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 9, 313 | "id": "f26ee03e-1511-4cd6-b180-7ce795ab2329", 314 | "metadata": {}, 315 | "outputs": [ 316 | { 317 | "name": "stdout", 318 | "output_type": "stream", 319 | "text": [ 320 | "Something is happening before the function is called.\n", 321 | "Whee!\n", 322 | "Something is happening after the function is called.\n" 323 | ] 324 | } 325 | ], 326 | "source": [ 327 | "def my_decorator(func):\n", 328 | " def wrapper():\n", 329 | " print(\"Something is happening before the function is called.\")\n", 330 | " func()\n", 331 | " print(\"Something is happening after the function is called.\")\n", 332 | " return wrapper\n", 333 | "\n", 334 | "@my_decorator\n", 335 | "def say_whee():\n", 336 | " print(\"Whee!\")\n", 337 | " \n", 338 | "say_whee()" 339 | ] 340 | }, 341 | { 342 | "cell_type": "markdown", 343 | "id": "6e78cba9-84ea-4bba-b468-54c48f306df8", 344 | "metadata": {}, 345 | "source": [ 346 | "### Aplicando multiples decoradores a una misma función" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "execution_count": 10, 352 | "id": "dd321755-d435-4da9-b211-a340dcb11c72", 353 | "metadata": {}, 354 | "outputs": [], 355 | "source": [ 356 | "def uppercase_decorator(function):\n", 357 | " print(\"Applying uppercase decorator\")\n", 358 | " def wrapper():\n", 359 | " func = function()\n", 360 | " make_uppercase = func.upper()\n", 361 | " return make_uppercase\n", 362 | "\n", 363 | " return wrapper" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": 11, 369 | "id": "6a935092-a4fd-4a7e-adad-8c539dbc6572", 370 | "metadata": {}, 371 | "outputs": [], 372 | "source": [ 373 | "def split_string(function):\n", 374 | " print(\"Applying split decorator\")\n", 375 | " def wrapper():\n", 376 | " func = function()\n", 377 | " splitted_string = func.split()\n", 378 | " return splitted_string\n", 379 | "\n", 380 | " return wrapper" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": 12, 386 | "id": "a0f3ad5c-8617-43fe-9e4f-49b9ce486ddd", 387 | "metadata": {}, 388 | "outputs": [ 389 | { 390 | "name": "stdout", 391 | "output_type": "stream", 392 | "text": [ 393 | "Applying uppercase decorator\n", 394 | "Applying split decorator\n" 395 | ] 396 | }, 397 | { 398 | "data": { 399 | "text/plain": [ 400 | "['HELLO', 'THERE']" 401 | ] 402 | }, 403 | "execution_count": 12, 404 | "metadata": {}, 405 | "output_type": "execute_result" 406 | } 407 | ], 408 | "source": [ 409 | "@split_string\n", 410 | "@uppercase_decorator\n", 411 | "def say_hi():\n", 412 | " return 'hello there'\n", 413 | "say_hi()" 414 | ] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "id": "a49022f6-d17f-4b9e-b7f5-2cb09df3c051", 419 | "metadata": {}, 420 | "source": [ 421 | "### Decorando funciones con argumentos" 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": 13, 427 | "id": "5b0394e0-194d-4ee4-9d8e-d3e6af0259fb", 428 | "metadata": {}, 429 | "outputs": [ 430 | { 431 | "name": "stdout", 432 | "output_type": "stream", 433 | "text": [ 434 | "My arguments are: Pereira, Medellín\n", 435 | "Cities I love are Pereira and Medellín\n" 436 | ] 437 | } 438 | ], 439 | "source": [ 440 | "def decorator_with_arguments(function):\n", 441 | " def wrapper_accepting_arguments(arg1, arg2):\n", 442 | " print(f\"My arguments are: {arg1}, {arg2}\")\n", 443 | " function(arg1, arg2)\n", 444 | " return wrapper_accepting_arguments\n", 445 | "\n", 446 | "\n", 447 | "@decorator_with_arguments\n", 448 | "def cities(city_one, city_two):\n", 449 | " print(f\"Cities I love are {city_one} and {city_two}\")\n", 450 | "\n", 451 | "cities(\"Pereira\", \"Medellín\")" 452 | ] 453 | }, 454 | { 455 | "cell_type": "markdown", 456 | "id": "bb6a4ee5-d86a-4397-ad5e-8db9ef101252", 457 | "metadata": {}, 458 | "source": [ 459 | "### Definiendo decoradores de propósito general" 460 | ] 461 | }, 462 | { 463 | "cell_type": "code", 464 | "execution_count": 14, 465 | "id": "20d65934-e1ba-4ab5-b935-afa1fb23cae0", 466 | "metadata": {}, 467 | "outputs": [ 468 | { 469 | "name": "stdout", 470 | "output_type": "stream", 471 | "text": [ 472 | "Showing *args:\n", 473 | "Arguments of *args: (1, 2, 'Hola')\n", 474 | "Showing **kwargs:\n", 475 | "first_name = Carolina\n", 476 | "last_name = Gomez\n" 477 | ] 478 | } 479 | ], 480 | "source": [ 481 | "def test_args_and_kwargs(*args, **kwargs):\n", 482 | " print(\"Showing *args:\")\n", 483 | " for arg in args:\n", 484 | " print(\"Arguments of *args:\", arg)\n", 485 | " print(\"Showing **kwargs:\")\n", 486 | " for key, value in kwargs.items():\n", 487 | " print(f\"{key} = {value}\")\n", 488 | "\n", 489 | "args = (1, 2, \"Hola\")\n", 490 | "kwargs = {\"first_name\": \"Carolina\", \"last_name\": \"Gomez\"}\n", 491 | "test_args_and_kwargs(args, **kwargs)" 492 | ] 493 | }, 494 | { 495 | "cell_type": "code", 496 | "execution_count": 15, 497 | "id": "ca1011ff-14ee-4db4-834c-c5d624a172f6", 498 | "metadata": {}, 499 | "outputs": [ 500 | { 501 | "name": "stdout", 502 | "output_type": "stream", 503 | "text": [ 504 | "The positional arguments are (1, 2, 3)\n", 505 | "The keyword arguments are {}\n", 506 | "1 2 3\n" 507 | ] 508 | } 509 | ], 510 | "source": [ 511 | "def a_decorator_passing_arbitrary_arguments(function_to_decorate):\n", 512 | " def a_wrapper_accepting_arbitrary_arguments(*args,**kwargs):\n", 513 | " print('The positional arguments are', args)\n", 514 | " print('The keyword arguments are', kwargs)\n", 515 | " function_to_decorate(*args, **kwargs)\n", 516 | " return a_wrapper_accepting_arbitrary_arguments\n", 517 | "\n", 518 | "@a_decorator_passing_arbitrary_arguments\n", 519 | "def function_with_arguments(a, b, c):\n", 520 | " print(a, b, c)\n", 521 | "\n", 522 | "function_with_arguments(1,2,3)" 523 | ] 524 | }, 525 | { 526 | "cell_type": "code", 527 | "execution_count": 16, 528 | "id": "52b5b201-24f5-4e32-a85b-1bd55300107c", 529 | "metadata": {}, 530 | "outputs": [ 531 | { 532 | "name": "stdout", 533 | "output_type": "stream", 534 | "text": [ 535 | "The positional arguments are ()\n", 536 | "The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}\n", 537 | "This has shown keyword arguments: \n", 538 | "first_name: Derrick\n", 539 | "last_name: Mwiti\n", 540 | "country: Colombia\n" 541 | ] 542 | } 543 | ], 544 | "source": [ 545 | "@a_decorator_passing_arbitrary_arguments\n", 546 | "def function_with_keyword_arguments(first_name=\"\", last_name=\"\", country=\"Colombia\"):\n", 547 | " print(f\"This has shown keyword arguments: \")\n", 548 | " print(f\"first_name: {first_name}\")\n", 549 | " print(f\"last_name: {last_name}\")\n", 550 | " print(f\"country: {country}\")\n", 551 | "\n", 552 | "function_with_keyword_arguments(first_name=\"Derrick\", last_name=\"Mwiti\")" 553 | ] 554 | }, 555 | { 556 | "cell_type": "markdown", 557 | "id": "7827a857-ea7f-4c62-b7de-876d1773e901", 558 | "metadata": {}, 559 | "source": [ 560 | "### Pasando argumentos al decorador" 561 | ] 562 | }, 563 | { 564 | "cell_type": "code", 565 | "execution_count": 17, 566 | "id": "0025540a-7789-467c-98d9-6f1d17b2616b", 567 | "metadata": {}, 568 | "outputs": [ 569 | { 570 | "name": "stdout", 571 | "output_type": "stream", 572 | "text": [ 573 | "The wrapper can access all the variables\n", 574 | "\t- from the decorator maker: Pandas Numpy Scikit-learn\n", 575 | "\t- from the function call: Pandas Science Tools\n", 576 | "and pass them to the decorated function\n", 577 | "This is the decorated function and it only knows about its arguments: \n", 578 | "Pandas Science Tools\n" 579 | ] 580 | } 581 | ], 582 | "source": [ 583 | "def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):\n", 584 | " def decorator(func):\n", 585 | " def wrapper(function_arg1, function_arg2, function_arg3) :\n", 586 | " \"\"\"This is the wrapper function\"\"\"\n", 587 | " \n", 588 | " print(f\"The wrapper can access all the variables\\n\"\n", 589 | " f\"\\t- from the decorator maker: {decorator_arg1} {decorator_arg2} {decorator_arg3}\\n\"\n", 590 | " f\"\\t- from the function call: {function_arg1} {function_arg2} {function_arg3}\\n\"\n", 591 | " f\"and pass them to the decorated function\")\n", 592 | " return func(function_arg1, function_arg2, function_arg3)\n", 593 | "\n", 594 | " return wrapper\n", 595 | "\n", 596 | " return decorator\n", 597 | "\n", 598 | "pandas = \"Pandas\"\n", 599 | "@decorator_maker_with_arguments(pandas, \"Numpy\",\"Scikit-learn\")\n", 600 | "def decorated_function_with_arguments(function_arg1, function_arg2, function_arg3):\n", 601 | " print(\"This is the decorated function and it only knows about its arguments: \")\n", 602 | " print(f\"{function_arg1} {function_arg2} {function_arg3}\")\n", 603 | "\n", 604 | "decorated_function_with_arguments(pandas, \"Science\", \"Tools\")" 605 | ] 606 | }, 607 | { 608 | "cell_type": "markdown", 609 | "id": "fe0bccc2-dcfd-4382-81b2-d91730806912", 610 | "metadata": {}, 611 | "source": [ 612 | "### Debugueando decoradores" 613 | ] 614 | }, 615 | { 616 | "cell_type": "code", 617 | "execution_count": 18, 618 | "id": "e9af96d0-0960-4e65-bbaa-1303312547f9", 619 | "metadata": {}, 620 | "outputs": [ 621 | { 622 | "name": "stdout", 623 | "output_type": "stream", 624 | "text": [ 625 | "Hi! You are happy!\n", 626 | "Function name: speak\n", 627 | "Function doc: Returns a neutral message\n", 628 | "Function name with decorator: wrapper\n", 629 | "Function doc with decorator: None\n" 630 | ] 631 | } 632 | ], 633 | "source": [ 634 | "# decorator\n", 635 | "def make_geek_happy(func):\n", 636 | " def wrapper():\n", 637 | " neutral_message = func()\n", 638 | " happy_message = neutral_message + \" You are happy!\"\n", 639 | " return happy_message\n", 640 | " return wrapper\n", 641 | " \n", 642 | "def speak():\n", 643 | " \"\"\"Returns a neutral message\"\"\"\n", 644 | " return \"Hi!\"\n", 645 | " \n", 646 | " \n", 647 | "# wrapping the function in the decorator\n", 648 | "# and assigning it to positive_message\n", 649 | "positive_message = make_geek_happy(speak)\n", 650 | " \n", 651 | "print(positive_message())\n", 652 | " \n", 653 | "print(f\"Function name: {speak.__name__}\") \n", 654 | "print(f\"Function doc: {speak.__doc__}\") \n", 655 | "print(f\"Function name with decorator: {positive_message.__name__}\")\n", 656 | "print(f\"Function doc with decorator: {positive_message.__doc__}\")" 657 | ] 658 | }, 659 | { 660 | "cell_type": "code", 661 | "execution_count": 19, 662 | "id": "211258b1-470b-4aec-9f93-c0b959a341e5", 663 | "metadata": {}, 664 | "outputs": [ 665 | { 666 | "name": "stdout", 667 | "output_type": "stream", 668 | "text": [ 669 | "Hi! You are happy!\n", 670 | "Function name: speak\n", 671 | "Function doc: Returns a neutral message\n", 672 | "Function name with decorator: speak\n", 673 | "Function doc with decorator: Returns a neutral message\n" 674 | ] 675 | } 676 | ], 677 | "source": [ 678 | "# importing the module\n", 679 | "import functools\n", 680 | " \n", 681 | "# decorator\n", 682 | "def make_geek_happy(func):\n", 683 | " @functools.wraps(func)\n", 684 | " def wrapper():\n", 685 | " neutral_message = func()\n", 686 | " happy_message = neutral_message + \" You are happy!\"\n", 687 | " return happy_message\n", 688 | " return wrapper\n", 689 | " \n", 690 | "def speak():\n", 691 | " \"\"\"Returns a neutral message\"\"\"\n", 692 | " return \"Hi!\"\n", 693 | " \n", 694 | "positive_message = make_geek_happy(speak)\n", 695 | "print(positive_message())\n", 696 | " \n", 697 | "print(f\"Function name: {speak.__name__}\") \n", 698 | "print(f\"Function doc: {speak.__doc__}\") \n", 699 | "print(f\"Function name with decorator: {positive_message.__name__}\")\n", 700 | "print(f\"Function doc with decorator: {positive_message.__doc__}\")" 701 | ] 702 | }, 703 | { 704 | "cell_type": "markdown", 705 | "id": "d7c87eee-cb16-4c8b-9c29-95e082c8b654", 706 | "metadata": {}, 707 | "source": [ 708 | "## Funciones Lambda\n", 709 | "Son también conocidas como funciones anónimas y son funciones que pueden definir cualquier número de parámetros pero una única expresión. Esta expresión es evaluada y devuelta." 710 | ] 711 | }, 712 | { 713 | "cell_type": "code", 714 | "execution_count": 20, 715 | "id": "11a6797c-6cb4-458c-ae6e-53d5d78c55c6", 716 | "metadata": {}, 717 | "outputs": [], 718 | "source": [ 719 | "cuadrado = lambda x: x ** 2" 720 | ] 721 | }, 722 | { 723 | "cell_type": "code", 724 | "execution_count": 21, 725 | "id": "441b61ee-b997-47ed-926a-663b3801aeb1", 726 | "metadata": {}, 727 | "outputs": [ 728 | { 729 | "name": "stdout", 730 | "output_type": "stream", 731 | "text": [ 732 | "16\n" 733 | ] 734 | } 735 | ], 736 | "source": [ 737 | "print(cuadrado(4))" 738 | ] 739 | }, 740 | { 741 | "cell_type": "markdown", 742 | "id": "fae269eb-2955-4ed6-945b-a035f7c016bc", 743 | "metadata": {}, 744 | "source": [ 745 | "### Uso apropiado de las funciones lambda\n", 746 | "#### `map()`\n", 747 | "La función map() en Python aplica una función a cada uno de los elementos de una lista." 748 | ] 749 | }, 750 | { 751 | "cell_type": "code", 752 | "execution_count": 22, 753 | "id": "829ba118-ef23-432f-b18a-f20111bc85b4", 754 | "metadata": {}, 755 | "outputs": [ 756 | { 757 | "name": "stdout", 758 | "output_type": "stream", 759 | "text": [ 760 | "[1, 4, 16, 49]\n" 761 | ] 762 | } 763 | ], 764 | "source": [ 765 | "enteros = [1, 2, 4, 7]\n", 766 | "cuadrados = []\n", 767 | "for e in enteros:\n", 768 | " cuadrados.append(e ** 2)\n", 769 | " \n", 770 | "print(cuadrados)" 771 | ] 772 | }, 773 | { 774 | "cell_type": "code", 775 | "execution_count": 23, 776 | "id": "1bc53af7-5d2f-440b-86ad-29a821fda0cd", 777 | "metadata": {}, 778 | "outputs": [ 779 | { 780 | "name": "stdout", 781 | "output_type": "stream", 782 | "text": [ 783 | "[1, 4, 16, 49]\n" 784 | ] 785 | } 786 | ], 787 | "source": [ 788 | "enteros = [1, 2, 4, 7]\n", 789 | "cuadrados = list(map(lambda x : x ** 2, enteros))\n", 790 | "print(cuadrados)" 791 | ] 792 | }, 793 | { 794 | "cell_type": "markdown", 795 | "id": "90f83f3d-b6e2-4e20-9476-89caedd18b2c", 796 | "metadata": {}, 797 | "source": [ 798 | "#### `filter()`\n", 799 | "La función filter() filtra una lista de elementos para los que una función devuelve True." 800 | ] 801 | }, 802 | { 803 | "cell_type": "code", 804 | "execution_count": 25, 805 | "id": "40312582-af9f-47c4-9737-74b81ef83edf", 806 | "metadata": {}, 807 | "outputs": [ 808 | { 809 | "name": "stdout", 810 | "output_type": "stream", 811 | "text": [ 812 | "Pares-> [2, 4, 6, 8]\n" 813 | ] 814 | } 815 | ], 816 | "source": [ 817 | "valores = [1, 2, 3, 4, 5, 6, 7, 8, 9]\n", 818 | "pares = []\n", 819 | "for valor in valores:\n", 820 | " if valor % 2 == 0:\n", 821 | " pares.append(valor)\n", 822 | "print(\"Pares-> \", pares)" 823 | ] 824 | }, 825 | { 826 | "cell_type": "code", 827 | "execution_count": 26, 828 | "id": "10b24176-1c24-4bc0-a6ec-27bf4df70ef6", 829 | "metadata": {}, 830 | "outputs": [ 831 | { 832 | "name": "stdout", 833 | "output_type": "stream", 834 | "text": [ 835 | "Pares-> [2, 4, 6, 8]\n" 836 | ] 837 | } 838 | ], 839 | "source": [ 840 | "valores = [1, 2, 3, 4, 5, 6, 7, 8, 9]\n", 841 | "pares = list(filter(lambda x : x % 2 == 0, valores))\n", 842 | "print(\"Pares-> \", pares)" 843 | ] 844 | }, 845 | { 846 | "cell_type": "markdown", 847 | "id": "65c0c56c-5ddb-4e42-b37d-2060ce313684", 848 | "metadata": {}, 849 | "source": [ 850 | "#### `reduce()`\n", 851 | "Esta función se utiliza principalmente para llevar a cabo un cálculo acumulativo sobre una lista de valores y devolver el resultado." 852 | ] 853 | }, 854 | { 855 | "cell_type": "code", 856 | "execution_count": 27, 857 | "id": "9035809f-4c5b-43b1-b18f-e0a8fc0c27fc", 858 | "metadata": {}, 859 | "outputs": [ 860 | { 861 | "name": "stdout", 862 | "output_type": "stream", 863 | "text": [ 864 | "21\n" 865 | ] 866 | } 867 | ], 868 | "source": [ 869 | "valores = [2, 4, 6, 5, 4]\n", 870 | "suma = 0\n", 871 | "for el in valores:\n", 872 | " suma += el\n", 873 | "print(suma)" 874 | ] 875 | }, 876 | { 877 | "cell_type": "code", 878 | "execution_count": 28, 879 | "id": "2a171ddf-14eb-4f20-9652-748a58510a04", 880 | "metadata": {}, 881 | "outputs": [ 882 | { 883 | "name": "stdout", 884 | "output_type": "stream", 885 | "text": [ 886 | "21\n" 887 | ] 888 | } 889 | ], 890 | "source": [ 891 | "from functools import reduce\n", 892 | "suma = reduce(lambda x, y: x + y, valores)\n", 893 | "print(suma)" 894 | ] 895 | }, 896 | { 897 | "cell_type": "markdown", 898 | "id": "2e0d00c0-aaf8-4a18-8763-71f5d896290f", 899 | "metadata": {}, 900 | "source": [ 901 | "#### `sorted()`\n", 902 | "Esta función ordena una lista en forma lexicográfica." 903 | ] 904 | }, 905 | { 906 | "cell_type": "code", 907 | "execution_count": 29, 908 | "id": "a8063494-d555-4fd7-8575-fee6ed028ebc", 909 | "metadata": {}, 910 | "outputs": [ 911 | { 912 | "name": "stdout", 913 | "output_type": "stream", 914 | "text": [ 915 | "['id1', 'id100', 'id2', 'id22', 'id3', 'id30']\n" 916 | ] 917 | } 918 | ], 919 | "source": [ 920 | "ids = ['id1', 'id2', 'id30', 'id3', 'id22', 'id100']\n", 921 | "print(sorted(ids)) # Lexicographic sort" 922 | ] 923 | }, 924 | { 925 | "cell_type": "code", 926 | "execution_count": 30, 927 | "id": "6045e46c-6d76-45c5-8f1e-7f9df1655684", 928 | "metadata": {}, 929 | "outputs": [ 930 | { 931 | "name": "stdout", 932 | "output_type": "stream", 933 | "text": [ 934 | "['id1', 'id2', 'id3', 'id22', 'id30', 'id100']\n" 935 | ] 936 | } 937 | ], 938 | "source": [ 939 | "sorted_ids = sorted(ids, key=lambda x: int(x[2:])) # Integer sort\n", 940 | "print(sorted_ids)" 941 | ] 942 | }, 943 | { 944 | "cell_type": "markdown", 945 | "id": "e0bd2620-1d2a-48f6-820c-62a9ea35637f", 946 | "metadata": {}, 947 | "source": [ 948 | "## Properties\n", 949 | "Las properties son la forma pythonica de evitar la creación de métodos para obtener y modificar atributos de una clase. Esta función nos ayuda a convertir atributos de una clase en properties o managed attributes." 950 | ] 951 | }, 952 | { 953 | "cell_type": "code", 954 | "execution_count": 31, 955 | "id": "b4b31529-2312-4bca-b99d-bfdb5671e1e3", 956 | "metadata": {}, 957 | "outputs": [], 958 | "source": [ 959 | "class Point:\n", 960 | " def __init__(self, x, y):\n", 961 | " self._x = x\n", 962 | " self._y = y\n", 963 | "\n", 964 | " def get_x(self):\n", 965 | " return self._x\n", 966 | "\n", 967 | " def set_x(self, value):\n", 968 | " self._x = value\n", 969 | "\n", 970 | " def get_y(self):\n", 971 | " return self._y\n", 972 | "\n", 973 | " def set_y(self, value):\n", 974 | " self._y = value" 975 | ] 976 | }, 977 | { 978 | "cell_type": "code", 979 | "execution_count": 33, 980 | "id": "3fd7cc68-1e9a-48a7-acc5-57195f21408e", 981 | "metadata": {}, 982 | "outputs": [], 983 | "source": [ 984 | "point = Point(5, 9)" 985 | ] 986 | }, 987 | { 988 | "cell_type": "code", 989 | "execution_count": 34, 990 | "id": "55b24b42-a482-4eed-9d16-42a35d1e32da", 991 | "metadata": {}, 992 | "outputs": [ 993 | { 994 | "data": { 995 | "text/plain": [ 996 | "5" 997 | ] 998 | }, 999 | "execution_count": 34, 1000 | "metadata": {}, 1001 | "output_type": "execute_result" 1002 | } 1003 | ], 1004 | "source": [ 1005 | "point.get_x()" 1006 | ] 1007 | }, 1008 | { 1009 | "cell_type": "code", 1010 | "execution_count": 35, 1011 | "id": "8f35e10a-afd0-487f-8742-7daca03de39b", 1012 | "metadata": {}, 1013 | "outputs": [ 1014 | { 1015 | "data": { 1016 | "text/plain": [ 1017 | "9" 1018 | ] 1019 | }, 1020 | "execution_count": 35, 1021 | "metadata": {}, 1022 | "output_type": "execute_result" 1023 | } 1024 | ], 1025 | "source": [ 1026 | "point.get_y()" 1027 | ] 1028 | }, 1029 | { 1030 | "cell_type": "code", 1031 | "execution_count": 36, 1032 | "id": "33a5eade-8638-4a09-ad63-3d2abb1d699b", 1033 | "metadata": {}, 1034 | "outputs": [], 1035 | "source": [ 1036 | "point.set_x(8)" 1037 | ] 1038 | }, 1039 | { 1040 | "cell_type": "code", 1041 | "execution_count": 37, 1042 | "id": "2b8c4b57-c2d3-465e-8662-379439110fa6", 1043 | "metadata": {}, 1044 | "outputs": [ 1045 | { 1046 | "data": { 1047 | "text/plain": [ 1048 | "8" 1049 | ] 1050 | }, 1051 | "execution_count": 37, 1052 | "metadata": {}, 1053 | "output_type": "execute_result" 1054 | } 1055 | ], 1056 | "source": [ 1057 | "point.get_x()" 1058 | ] 1059 | }, 1060 | { 1061 | "cell_type": "code", 1062 | "execution_count": 38, 1063 | "id": "30b7f779-91a9-4ca7-8479-031b5edf9b2b", 1064 | "metadata": {}, 1065 | "outputs": [ 1066 | { 1067 | "data": { 1068 | "text/plain": [ 1069 | "8" 1070 | ] 1071 | }, 1072 | "execution_count": 38, 1073 | "metadata": {}, 1074 | "output_type": "execute_result" 1075 | } 1076 | ], 1077 | "source": [ 1078 | "point._x" 1079 | ] 1080 | }, 1081 | { 1082 | "cell_type": "markdown", 1083 | "id": "8ad678bd-5ea7-4667-9714-779c3c278325", 1084 | "metadata": {}, 1085 | "source": [ 1086 | "### Creando properties con la inicialización de la función `property()`" 1087 | ] 1088 | }, 1089 | { 1090 | "cell_type": "code", 1091 | "execution_count": 39, 1092 | "id": "9b72e5e0-6555-449b-85f0-a84a136270c8", 1093 | "metadata": {}, 1094 | "outputs": [], 1095 | "source": [ 1096 | "class Circle:\n", 1097 | " def __init__(self, radius):\n", 1098 | " self._radius = radius\n", 1099 | "\n", 1100 | " def _get_radius(self):\n", 1101 | " print(\"Get radius\")\n", 1102 | " return self._radius\n", 1103 | "\n", 1104 | " def _set_radius(self, value):\n", 1105 | " print(\"Set radius\")\n", 1106 | " self._radius = value\n", 1107 | "\n", 1108 | " def _del_radius(self):\n", 1109 | " print(\"Delete radius\")\n", 1110 | " del self._radius\n", 1111 | "\n", 1112 | " radius = property(\n", 1113 | " fget=_get_radius,\n", 1114 | " fset=_set_radius,\n", 1115 | " fdel=_del_radius,\n", 1116 | " doc=\"The radius property.\"\n", 1117 | " )" 1118 | ] 1119 | }, 1120 | { 1121 | "cell_type": "code", 1122 | "execution_count": 40, 1123 | "id": "7df35c32-967a-4b1b-9ab5-eebc3707f35d", 1124 | "metadata": {}, 1125 | "outputs": [], 1126 | "source": [ 1127 | "circle = Circle(42.0)" 1128 | ] 1129 | }, 1130 | { 1131 | "cell_type": "code", 1132 | "execution_count": 41, 1133 | "id": "03bf60ed-b5f2-4ddd-bb0d-0dc8bb20f6ac", 1134 | "metadata": {}, 1135 | "outputs": [ 1136 | { 1137 | "name": "stdout", 1138 | "output_type": "stream", 1139 | "text": [ 1140 | "Get radius\n" 1141 | ] 1142 | }, 1143 | { 1144 | "data": { 1145 | "text/plain": [ 1146 | "42.0" 1147 | ] 1148 | }, 1149 | "execution_count": 41, 1150 | "metadata": {}, 1151 | "output_type": "execute_result" 1152 | } 1153 | ], 1154 | "source": [ 1155 | "circle.radius" 1156 | ] 1157 | }, 1158 | { 1159 | "cell_type": "code", 1160 | "execution_count": 42, 1161 | "id": "f1d5070f-dbc8-4d7f-bc69-73558c1c3740", 1162 | "metadata": {}, 1163 | "outputs": [ 1164 | { 1165 | "name": "stdout", 1166 | "output_type": "stream", 1167 | "text": [ 1168 | "Set radius\n" 1169 | ] 1170 | } 1171 | ], 1172 | "source": [ 1173 | "circle.radius = 50" 1174 | ] 1175 | }, 1176 | { 1177 | "cell_type": "code", 1178 | "execution_count": 43, 1179 | "id": "b6c2c95c-a064-49bd-aac6-8930f74c843e", 1180 | "metadata": {}, 1181 | "outputs": [ 1182 | { 1183 | "name": "stdout", 1184 | "output_type": "stream", 1185 | "text": [ 1186 | "Get radius\n" 1187 | ] 1188 | }, 1189 | { 1190 | "data": { 1191 | "text/plain": [ 1192 | "50" 1193 | ] 1194 | }, 1195 | "execution_count": 43, 1196 | "metadata": {}, 1197 | "output_type": "execute_result" 1198 | } 1199 | ], 1200 | "source": [ 1201 | "circle.radius" 1202 | ] 1203 | }, 1204 | { 1205 | "cell_type": "code", 1206 | "execution_count": 44, 1207 | "id": "27ac73a7-f21a-4019-a6b8-eeb12315306d", 1208 | "metadata": {}, 1209 | "outputs": [ 1210 | { 1211 | "name": "stdout", 1212 | "output_type": "stream", 1213 | "text": [ 1214 | "Delete radius\n" 1215 | ] 1216 | } 1217 | ], 1218 | "source": [ 1219 | "del circle.radius" 1220 | ] 1221 | }, 1222 | { 1223 | "cell_type": "code", 1224 | "execution_count": 45, 1225 | "id": "0c4ffaff-e56b-4c97-b833-66cb0f4833c8", 1226 | "metadata": {}, 1227 | "outputs": [ 1228 | { 1229 | "name": "stdout", 1230 | "output_type": "stream", 1231 | "text": [ 1232 | "Get radius\n" 1233 | ] 1234 | }, 1235 | { 1236 | "ename": "AttributeError", 1237 | "evalue": "'Circle' object has no attribute '_radius'", 1238 | "output_type": "error", 1239 | "traceback": [ 1240 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 1241 | "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", 1242 | "Cell \u001b[0;32mIn[45], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mcircle\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mradius\u001b[49m\n", 1243 | "Cell \u001b[0;32mIn[39], line 7\u001b[0m, in \u001b[0;36mCircle._get_radius\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_get_radius\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mGet radius\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 7\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_radius\u001b[49m\n", 1244 | "\u001b[0;31mAttributeError\u001b[0m: 'Circle' object has no attribute '_radius'" 1245 | ] 1246 | } 1247 | ], 1248 | "source": [ 1249 | "circle.radius" 1250 | ] 1251 | }, 1252 | { 1253 | "cell_type": "markdown", 1254 | "id": "b6480c8d-445c-43f2-afcf-23ea7821dd74", 1255 | "metadata": {}, 1256 | "source": [ 1257 | "### Usando `property()` como decorador" 1258 | ] 1259 | }, 1260 | { 1261 | "cell_type": "code", 1262 | "execution_count": 46, 1263 | "id": "535b699e-d3cb-45c6-b32e-ddf7c786b015", 1264 | "metadata": {}, 1265 | "outputs": [], 1266 | "source": [ 1267 | "class Circle:\n", 1268 | " def __init__(self, radius):\n", 1269 | " self._radius = radius\n", 1270 | "\n", 1271 | " @property\n", 1272 | " def radius(self):\n", 1273 | " \"\"\"The radius property.\"\"\"\n", 1274 | " print(\"Get radius\")\n", 1275 | " return self._radius\n", 1276 | "\n", 1277 | " @radius.setter\n", 1278 | " def radius(self, value):\n", 1279 | " print(\"Set radius\")\n", 1280 | " self._radius = value\n", 1281 | "\n", 1282 | " @radius.deleter\n", 1283 | " def radius(self):\n", 1284 | " print(\"Delete radius\")\n", 1285 | " del self._radius" 1286 | ] 1287 | }, 1288 | { 1289 | "cell_type": "code", 1290 | "execution_count": 47, 1291 | "id": "b94c4aef-078c-46a5-a416-7aa1973d6c8f", 1292 | "metadata": {}, 1293 | "outputs": [], 1294 | "source": [ 1295 | "circle = Circle(42.0)" 1296 | ] 1297 | }, 1298 | { 1299 | "cell_type": "code", 1300 | "execution_count": 48, 1301 | "id": "c79a8209-6430-4172-9f1a-a691844dd488", 1302 | "metadata": {}, 1303 | "outputs": [ 1304 | { 1305 | "name": "stdout", 1306 | "output_type": "stream", 1307 | "text": [ 1308 | "Get radius\n" 1309 | ] 1310 | }, 1311 | { 1312 | "data": { 1313 | "text/plain": [ 1314 | "42.0" 1315 | ] 1316 | }, 1317 | "execution_count": 48, 1318 | "metadata": {}, 1319 | "output_type": "execute_result" 1320 | } 1321 | ], 1322 | "source": [ 1323 | "circle.radius" 1324 | ] 1325 | }, 1326 | { 1327 | "cell_type": "code", 1328 | "execution_count": 49, 1329 | "id": "83544895-dbc5-4495-9558-099dd713ea12", 1330 | "metadata": {}, 1331 | "outputs": [ 1332 | { 1333 | "name": "stdout", 1334 | "output_type": "stream", 1335 | "text": [ 1336 | "Set radius\n" 1337 | ] 1338 | } 1339 | ], 1340 | "source": [ 1341 | "circle.radius = 50" 1342 | ] 1343 | }, 1344 | { 1345 | "cell_type": "code", 1346 | "execution_count": 50, 1347 | "id": "50c56ebb-965c-4cee-a30e-f340f2e083b5", 1348 | "metadata": {}, 1349 | "outputs": [ 1350 | { 1351 | "name": "stdout", 1352 | "output_type": "stream", 1353 | "text": [ 1354 | "Get radius\n" 1355 | ] 1356 | }, 1357 | { 1358 | "data": { 1359 | "text/plain": [ 1360 | "50" 1361 | ] 1362 | }, 1363 | "execution_count": 50, 1364 | "metadata": {}, 1365 | "output_type": "execute_result" 1366 | } 1367 | ], 1368 | "source": [ 1369 | "circle.radius" 1370 | ] 1371 | }, 1372 | { 1373 | "cell_type": "code", 1374 | "execution_count": 51, 1375 | "id": "6c15901e-d849-4515-8b14-fc2302cc78a3", 1376 | "metadata": {}, 1377 | "outputs": [ 1378 | { 1379 | "name": "stdout", 1380 | "output_type": "stream", 1381 | "text": [ 1382 | "Delete radius\n" 1383 | ] 1384 | } 1385 | ], 1386 | "source": [ 1387 | "del circle.radius" 1388 | ] 1389 | }, 1390 | { 1391 | "cell_type": "code", 1392 | "execution_count": 52, 1393 | "id": "254fa8c1-b625-4682-8759-57d96d05f4fa", 1394 | "metadata": {}, 1395 | "outputs": [ 1396 | { 1397 | "name": "stdout", 1398 | "output_type": "stream", 1399 | "text": [ 1400 | "Get radius\n" 1401 | ] 1402 | }, 1403 | { 1404 | "ename": "AttributeError", 1405 | "evalue": "'Circle' object has no attribute '_radius'", 1406 | "output_type": "error", 1407 | "traceback": [ 1408 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 1409 | "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", 1410 | "Cell \u001b[0;32mIn[52], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mcircle\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mradius\u001b[49m\n", 1411 | "Cell \u001b[0;32mIn[46], line 9\u001b[0m, in \u001b[0;36mCircle.radius\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"The radius property.\"\"\"\u001b[39;00m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mGet radius\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 9\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_radius\u001b[49m\n", 1412 | "\u001b[0;31mAttributeError\u001b[0m: 'Circle' object has no attribute '_radius'" 1413 | ] 1414 | } 1415 | ], 1416 | "source": [ 1417 | "circle.radius" 1418 | ] 1419 | }, 1420 | { 1421 | "cell_type": "markdown", 1422 | "id": "b05fac00-439d-4373-90a4-516711fc043a", 1423 | "metadata": {}, 1424 | "source": [ 1425 | "#### Creando atributos de solo lectura" 1426 | ] 1427 | }, 1428 | { 1429 | "cell_type": "code", 1430 | "execution_count": 53, 1431 | "id": "48a34671-6932-4ba8-8e58-cfdcc61fa914", 1432 | "metadata": {}, 1433 | "outputs": [], 1434 | "source": [ 1435 | "class Point:\n", 1436 | " def __init__(self, x, y):\n", 1437 | " self._x = x\n", 1438 | " self._y = y\n", 1439 | "\n", 1440 | " @property\n", 1441 | " def x(self):\n", 1442 | " return self._x\n", 1443 | "\n", 1444 | " @property\n", 1445 | " def y(self):\n", 1446 | " return self._y" 1447 | ] 1448 | }, 1449 | { 1450 | "cell_type": "code", 1451 | "execution_count": 54, 1452 | "id": "cb28aaf5-9e2e-41e8-ae9c-26d7f5cfcac0", 1453 | "metadata": {}, 1454 | "outputs": [], 1455 | "source": [ 1456 | "point = Point(12, 5)" 1457 | ] 1458 | }, 1459 | { 1460 | "cell_type": "code", 1461 | "execution_count": 55, 1462 | "id": "ff45c1ef-feb8-45ea-aafe-a8e1b57c68e5", 1463 | "metadata": {}, 1464 | "outputs": [ 1465 | { 1466 | "data": { 1467 | "text/plain": [ 1468 | "12" 1469 | ] 1470 | }, 1471 | "execution_count": 55, 1472 | "metadata": {}, 1473 | "output_type": "execute_result" 1474 | } 1475 | ], 1476 | "source": [ 1477 | "point.x" 1478 | ] 1479 | }, 1480 | { 1481 | "cell_type": "code", 1482 | "execution_count": 56, 1483 | "id": "7ec8f718-882e-4017-9119-15a674bccf11", 1484 | "metadata": {}, 1485 | "outputs": [ 1486 | { 1487 | "data": { 1488 | "text/plain": [ 1489 | "5" 1490 | ] 1491 | }, 1492 | "execution_count": 56, 1493 | "metadata": {}, 1494 | "output_type": "execute_result" 1495 | } 1496 | ], 1497 | "source": [ 1498 | "point.y" 1499 | ] 1500 | }, 1501 | { 1502 | "cell_type": "code", 1503 | "execution_count": 57, 1504 | "id": "52ad8519-2440-4c67-a5d5-8f04616421a1", 1505 | "metadata": {}, 1506 | "outputs": [ 1507 | { 1508 | "ename": "AttributeError", 1509 | "evalue": "property 'x' of 'Point' object has no setter", 1510 | "output_type": "error", 1511 | "traceback": [ 1512 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 1513 | "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", 1514 | "Cell \u001b[0;32mIn[57], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mpoint\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mx\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m10\u001b[39m\n", 1515 | "\u001b[0;31mAttributeError\u001b[0m: property 'x' of 'Point' object has no setter" 1516 | ] 1517 | } 1518 | ], 1519 | "source": [ 1520 | "point.x = 10" 1521 | ] 1522 | }, 1523 | { 1524 | "cell_type": "markdown", 1525 | "id": "1bc8c7e5-58b9-44f0-b510-2085133378b6", 1526 | "metadata": {}, 1527 | "source": [ 1528 | "#### Creando atributos de solo escritura" 1529 | ] 1530 | }, 1531 | { 1532 | "cell_type": "code", 1533 | "execution_count": 58, 1534 | "id": "68fed35d-d579-4e7c-921b-df9ff20d31b9", 1535 | "metadata": {}, 1536 | "outputs": [], 1537 | "source": [ 1538 | "import hashlib\n", 1539 | "import os\n", 1540 | "\n", 1541 | "class User:\n", 1542 | " def __init__(self, name, password):\n", 1543 | " self.name = name\n", 1544 | " self.password = password\n", 1545 | "\n", 1546 | " @property\n", 1547 | " def password(self):\n", 1548 | " raise AttributeError(\"Password is write-only\")\n", 1549 | "\n", 1550 | " @password.setter\n", 1551 | " def password(self, plaintext):\n", 1552 | " salt = os.urandom(32)\n", 1553 | " self._hashed_password = hashlib.pbkdf2_hmac(\n", 1554 | " \"sha256\", plaintext.encode(\"utf-8\"), salt, 100_000\n", 1555 | " )" 1556 | ] 1557 | }, 1558 | { 1559 | "cell_type": "code", 1560 | "execution_count": 59, 1561 | "id": "0d0082dc-7753-4ccb-b24f-e2a5ebad542f", 1562 | "metadata": {}, 1563 | "outputs": [], 1564 | "source": [ 1565 | "user = User(\"Carolina\", \"secure_password\")" 1566 | ] 1567 | }, 1568 | { 1569 | "cell_type": "code", 1570 | "execution_count": 60, 1571 | "id": "10161fb9-8824-4783-8a7a-d548e6d41bf8", 1572 | "metadata": {}, 1573 | "outputs": [ 1574 | { 1575 | "ename": "AttributeError", 1576 | "evalue": "Password is write-only", 1577 | "output_type": "error", 1578 | "traceback": [ 1579 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 1580 | "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", 1581 | "Cell \u001b[0;32mIn[60], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43muser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpassword\u001b[49m\n", 1582 | "Cell \u001b[0;32mIn[58], line 11\u001b[0m, in \u001b[0;36mUser.password\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;129m@property\u001b[39m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mpassword\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[0;32m---> 11\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPassword is write-only\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", 1583 | "\u001b[0;31mAttributeError\u001b[0m: Password is write-only" 1584 | ] 1585 | } 1586 | ], 1587 | "source": [ 1588 | "user.password" 1589 | ] 1590 | }, 1591 | { 1592 | "cell_type": "code", 1593 | "execution_count": 61, 1594 | "id": "6763fc27-62d1-4695-a55b-aef8ef2f6a04", 1595 | "metadata": {}, 1596 | "outputs": [], 1597 | "source": [ 1598 | "user.password = \"another_Secure_password\"" 1599 | ] 1600 | }, 1601 | { 1602 | "cell_type": "markdown", 1603 | "id": "b8d596b6-77c3-45f3-b18d-14e09845b603", 1604 | "metadata": {}, 1605 | "source": [ 1606 | "### Algunos casos de uso de las `properties()`\n", 1607 | "#### 1. Validando atributos" 1608 | ] 1609 | }, 1610 | { 1611 | "cell_type": "code", 1612 | "execution_count": 62, 1613 | "id": "2ad33530-f267-4388-9755-03f215c61ee3", 1614 | "metadata": {}, 1615 | "outputs": [], 1616 | "source": [ 1617 | "class Point:\n", 1618 | " def __init__(self, x, y):\n", 1619 | " self.x = x\n", 1620 | " self.y = y\n", 1621 | "\n", 1622 | " @property\n", 1623 | " def x(self):\n", 1624 | " return self._x\n", 1625 | "\n", 1626 | " @x.setter\n", 1627 | " def x(self, value):\n", 1628 | " try:\n", 1629 | " self._x = float(value)\n", 1630 | " print(\"Validated!\")\n", 1631 | " except ValueError:\n", 1632 | " raise ValueError('\"x\" must be a number') from None\n", 1633 | "\n", 1634 | " @property\n", 1635 | " def y(self):\n", 1636 | " return self._y\n", 1637 | "\n", 1638 | " @y.setter\n", 1639 | " def y(self, value):\n", 1640 | " try:\n", 1641 | " self._y = float(value)\n", 1642 | " print(\"Validated!\")\n", 1643 | " except ValueError:\n", 1644 | " raise ValueError('\"y\" must be a number') from None" 1645 | ] 1646 | }, 1647 | { 1648 | "cell_type": "code", 1649 | "execution_count": 63, 1650 | "id": "fd1a24ba-9e19-4127-8e29-e0327ba8c197", 1651 | "metadata": {}, 1652 | "outputs": [ 1653 | { 1654 | "name": "stdout", 1655 | "output_type": "stream", 1656 | "text": [ 1657 | "Validated!\n", 1658 | "Validated!\n" 1659 | ] 1660 | } 1661 | ], 1662 | "source": [ 1663 | "point = Point(2, 5)" 1664 | ] 1665 | }, 1666 | { 1667 | "cell_type": "code", 1668 | "execution_count": 64, 1669 | "id": "721b2554-b4b2-49b3-99db-faac2df4be48", 1670 | "metadata": {}, 1671 | "outputs": [ 1672 | { 1673 | "ename": "ValueError", 1674 | "evalue": "\"x\" must be a number", 1675 | "output_type": "error", 1676 | "traceback": [ 1677 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 1678 | "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", 1679 | "Cell \u001b[0;32mIn[64], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mpoint\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mx\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhola\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", 1680 | "Cell \u001b[0;32mIn[62], line 16\u001b[0m, in \u001b[0;36mPoint.x\u001b[0;34m(self, value)\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValidated!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 15\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m:\n\u001b[0;32m---> 16\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mx\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m must be a number\u001b[39m\u001b[38;5;124m'\u001b[39m) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n", 1681 | "\u001b[0;31mValueError\u001b[0m: \"x\" must be a number" 1682 | ] 1683 | } 1684 | ], 1685 | "source": [ 1686 | "point.x = \"hola\"" 1687 | ] 1688 | }, 1689 | { 1690 | "cell_type": "markdown", 1691 | "id": "8ed30c25-0a0f-4531-a1e7-223682a4ed05", 1692 | "metadata": {}, 1693 | "source": [ 1694 | "#### 2. Para agregar atributos que requieren algún calculo" 1695 | ] 1696 | }, 1697 | { 1698 | "cell_type": "code", 1699 | "execution_count": 65, 1700 | "id": "bae30cc8-aea2-4bd4-94b7-144c07c64071", 1701 | "metadata": {}, 1702 | "outputs": [], 1703 | "source": [ 1704 | "class Rectangle:\n", 1705 | " def __init__(self, width, height):\n", 1706 | " self.width = width\n", 1707 | " self.height = height\n", 1708 | "\n", 1709 | " @property\n", 1710 | " def area(self):\n", 1711 | " return self.width * self.height" 1712 | ] 1713 | }, 1714 | { 1715 | "cell_type": "code", 1716 | "execution_count": 66, 1717 | "id": "169f003e-c25d-45de-8b0d-4b5510748caa", 1718 | "metadata": {}, 1719 | "outputs": [], 1720 | "source": [ 1721 | "rectangle = Rectangle(30, 50)" 1722 | ] 1723 | }, 1724 | { 1725 | "cell_type": "code", 1726 | "execution_count": 67, 1727 | "id": "dd0705ed-2ca7-4730-8e02-6025ea5ee5d7", 1728 | "metadata": {}, 1729 | "outputs": [ 1730 | { 1731 | "data": { 1732 | "text/plain": [ 1733 | "1500" 1734 | ] 1735 | }, 1736 | "execution_count": 67, 1737 | "metadata": {}, 1738 | "output_type": "execute_result" 1739 | } 1740 | ], 1741 | "source": [ 1742 | "rectangle.area" 1743 | ] 1744 | }, 1745 | { 1746 | "cell_type": "markdown", 1747 | "id": "f0a5e117-e51e-4445-b51a-169bcdc5576a", 1748 | "metadata": {}, 1749 | "source": [ 1750 | "## Métodos de instancia, métodos de clase y métodos estáticos\n", 1751 | "Los **métodos de instancia** son creados para modificar un objeto instanciado de una clase.\n", 1752 | "\n", 1753 | "Los **métodos de clase** trabajan directamente con la clase, desde que su parámetro es la clase en sí.\n", 1754 | "\n", 1755 | "Los **métodos estáticos** no saben nada acerca de la clase, solo trabajan con los parámetros recibidos." 1756 | ] 1757 | }, 1758 | { 1759 | "cell_type": "code", 1760 | "execution_count": 68, 1761 | "id": "6178572f-4efb-4aec-9764-bbc8e4db796a", 1762 | "metadata": {}, 1763 | "outputs": [], 1764 | "source": [ 1765 | "class MyClass:\n", 1766 | " def method(self):\n", 1767 | " \"\"\" Instance method \"\"\"\n", 1768 | " return 'instance method called', self\n", 1769 | "\n", 1770 | " @classmethod\n", 1771 | " def class_method(cls):\n", 1772 | " \"\"\" Class method \"\"\"\n", 1773 | " return 'class method called', cls\n", 1774 | "\n", 1775 | " @staticmethod\n", 1776 | " def static_method():\n", 1777 | " \"\"\" Static method \"\"\"\n", 1778 | " return 'static method called'" 1779 | ] 1780 | }, 1781 | { 1782 | "cell_type": "code", 1783 | "execution_count": 69, 1784 | "id": "d306a441-7ff4-4220-a58e-bcb33179a3c2", 1785 | "metadata": {}, 1786 | "outputs": [], 1787 | "source": [ 1788 | "obj = MyClass()" 1789 | ] 1790 | }, 1791 | { 1792 | "cell_type": "code", 1793 | "execution_count": 70, 1794 | "id": "c63f2d66-81d1-401f-bb87-a1a961da52bf", 1795 | "metadata": {}, 1796 | "outputs": [ 1797 | { 1798 | "data": { 1799 | "text/plain": [ 1800 | "('instance method called', <__main__.MyClass at 0x10668ea90>)" 1801 | ] 1802 | }, 1803 | "execution_count": 70, 1804 | "metadata": {}, 1805 | "output_type": "execute_result" 1806 | } 1807 | ], 1808 | "source": [ 1809 | "obj.method()" 1810 | ] 1811 | }, 1812 | { 1813 | "cell_type": "code", 1814 | "execution_count": 71, 1815 | "id": "75dc1e77-0c1e-44cf-a4ca-cb52d748564d", 1816 | "metadata": {}, 1817 | "outputs": [ 1818 | { 1819 | "data": { 1820 | "text/plain": [ 1821 | "('instance method called', <__main__.MyClass at 0x10668ea90>)" 1822 | ] 1823 | }, 1824 | "execution_count": 71, 1825 | "metadata": {}, 1826 | "output_type": "execute_result" 1827 | } 1828 | ], 1829 | "source": [ 1830 | "MyClass.method(obj)" 1831 | ] 1832 | }, 1833 | { 1834 | "cell_type": "code", 1835 | "execution_count": 72, 1836 | "id": "6d8dc0da-df16-4324-80f7-126180a11feb", 1837 | "metadata": {}, 1838 | "outputs": [ 1839 | { 1840 | "data": { 1841 | "text/plain": [ 1842 | "('class method called', __main__.MyClass)" 1843 | ] 1844 | }, 1845 | "execution_count": 72, 1846 | "metadata": {}, 1847 | "output_type": "execute_result" 1848 | } 1849 | ], 1850 | "source": [ 1851 | "obj.class_method()" 1852 | ] 1853 | }, 1854 | { 1855 | "cell_type": "code", 1856 | "execution_count": 73, 1857 | "id": "7e055b0d-2f18-43c4-885f-f3120eaa8860", 1858 | "metadata": {}, 1859 | "outputs": [ 1860 | { 1861 | "data": { 1862 | "text/plain": [ 1863 | "'static method called'" 1864 | ] 1865 | }, 1866 | "execution_count": 73, 1867 | "metadata": {}, 1868 | "output_type": "execute_result" 1869 | } 1870 | ], 1871 | "source": [ 1872 | "obj.static_method()" 1873 | ] 1874 | }, 1875 | { 1876 | "cell_type": "markdown", 1877 | "id": "1703785b-4a52-451c-94e1-b1bd4067ee54", 1878 | "metadata": {}, 1879 | "source": [ 1880 | "### ¿Qué pasa si accedemos a los metodos sin instanciar un objeto de la clase?\n" 1881 | ] 1882 | }, 1883 | { 1884 | "cell_type": "code", 1885 | "execution_count": 74, 1886 | "id": "6f5390b3-8bef-4ac2-a71c-1c524ae266e2", 1887 | "metadata": {}, 1888 | "outputs": [ 1889 | { 1890 | "data": { 1891 | "text/plain": [ 1892 | "('class method called', __main__.MyClass)" 1893 | ] 1894 | }, 1895 | "execution_count": 74, 1896 | "metadata": {}, 1897 | "output_type": "execute_result" 1898 | } 1899 | ], 1900 | "source": [ 1901 | "MyClass.class_method()" 1902 | ] 1903 | }, 1904 | { 1905 | "cell_type": "code", 1906 | "execution_count": 75, 1907 | "id": "40e95922-40dd-47f8-875c-4f33479395a4", 1908 | "metadata": {}, 1909 | "outputs": [ 1910 | { 1911 | "data": { 1912 | "text/plain": [ 1913 | "'static method called'" 1914 | ] 1915 | }, 1916 | "execution_count": 75, 1917 | "metadata": {}, 1918 | "output_type": "execute_result" 1919 | } 1920 | ], 1921 | "source": [ 1922 | "MyClass.static_method()" 1923 | ] 1924 | }, 1925 | { 1926 | "cell_type": "code", 1927 | "execution_count": 76, 1928 | "id": "68e8e0b2-5f1b-4175-a0df-05df66f547f5", 1929 | "metadata": {}, 1930 | "outputs": [ 1931 | { 1932 | "ename": "TypeError", 1933 | "evalue": "MyClass.method() missing 1 required positional argument: 'self'", 1934 | "output_type": "error", 1935 | "traceback": [ 1936 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 1937 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", 1938 | "Cell \u001b[0;32mIn[76], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mMyClass\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", 1939 | "\u001b[0;31mTypeError\u001b[0m: MyClass.method() missing 1 required positional argument: 'self'" 1940 | ] 1941 | } 1942 | ], 1943 | "source": [ 1944 | "MyClass.method()" 1945 | ] 1946 | }, 1947 | { 1948 | "cell_type": "markdown", 1949 | "id": "638a2132-dcde-41f1-a9b6-db5fc91294c1", 1950 | "metadata": {}, 1951 | "source": [ 1952 | "### Ejemplos de uso" 1953 | ] 1954 | }, 1955 | { 1956 | "cell_type": "code", 1957 | "execution_count": 77, 1958 | "id": "c6931fa3-c9f8-493b-9b2d-6d3a3fbb8d73", 1959 | "metadata": {}, 1960 | "outputs": [], 1961 | "source": [ 1962 | "class Pizza:\n", 1963 | " def __init__(self, ingredients):\n", 1964 | " self.ingredients = ingredients\n", 1965 | "\n", 1966 | " def __repr__(self):\n", 1967 | " return f'Pizza({self.ingredients})'" 1968 | ] 1969 | }, 1970 | { 1971 | "cell_type": "code", 1972 | "execution_count": 78, 1973 | "id": "cb3baca9-e3ec-4f93-8bf3-5f4670e15e00", 1974 | "metadata": {}, 1975 | "outputs": [], 1976 | "source": [ 1977 | "margherita = Pizza(['mozzarella', 'tomatoes'])\n", 1978 | "cheese = Pizza(['mozzarella', 'provolone', 'cheddar', 'Parmesan'])" 1979 | ] 1980 | }, 1981 | { 1982 | "cell_type": "code", 1983 | "execution_count": 79, 1984 | "id": "e88848fa-1245-4dd6-8d0c-ad16ffe24ad0", 1985 | "metadata": {}, 1986 | "outputs": [ 1987 | { 1988 | "data": { 1989 | "text/plain": [ 1990 | "Pizza(['mozzarella', 'tomatoes'])" 1991 | ] 1992 | }, 1993 | "execution_count": 79, 1994 | "metadata": {}, 1995 | "output_type": "execute_result" 1996 | } 1997 | ], 1998 | "source": [ 1999 | "margherita" 2000 | ] 2001 | }, 2002 | { 2003 | "cell_type": "code", 2004 | "execution_count": 80, 2005 | "id": "5cee1c8c-069b-40c4-94d3-6191a271b61e", 2006 | "metadata": {}, 2007 | "outputs": [ 2008 | { 2009 | "data": { 2010 | "text/plain": [ 2011 | "Pizza(['mozzarella', 'provolone', 'cheddar', 'Parmesan'])" 2012 | ] 2013 | }, 2014 | "execution_count": 80, 2015 | "metadata": {}, 2016 | "output_type": "execute_result" 2017 | } 2018 | ], 2019 | "source": [ 2020 | "cheese" 2021 | ] 2022 | }, 2023 | { 2024 | "cell_type": "code", 2025 | "execution_count": 81, 2026 | "id": "2856be27-0c08-4b87-93de-fd804ce9032d", 2027 | "metadata": {}, 2028 | "outputs": [], 2029 | "source": [ 2030 | "class Pizza:\n", 2031 | " def __init__(self, ingredients):\n", 2032 | " self.ingredients = ingredients\n", 2033 | "\n", 2034 | " def __repr__(self):\n", 2035 | " return f'Pizza({self.ingredients})'\n", 2036 | " \n", 2037 | " def create_custom_pizza(self, ingredients):\n", 2038 | " self.ingredients = ingredients\n", 2039 | " return self\n", 2040 | "\n", 2041 | " @classmethod\n", 2042 | " def margherita(cls):\n", 2043 | " return cls(['mozzarella', 'tomatoes'])\n", 2044 | "\n", 2045 | " @classmethod\n", 2046 | " def cheese(cls):\n", 2047 | " return cls(['mozzarella', 'provolone', 'cheddar', 'Parmesan'])" 2048 | ] 2049 | }, 2050 | { 2051 | "cell_type": "code", 2052 | "execution_count": 82, 2053 | "id": "0efffa5a-c804-4218-b6ae-552336337570", 2054 | "metadata": {}, 2055 | "outputs": [ 2056 | { 2057 | "data": { 2058 | "text/plain": [ 2059 | "Pizza(['mozzarella', 'tomatoes'])" 2060 | ] 2061 | }, 2062 | "execution_count": 82, 2063 | "metadata": {}, 2064 | "output_type": "execute_result" 2065 | } 2066 | ], 2067 | "source": [ 2068 | "Pizza.margherita()" 2069 | ] 2070 | }, 2071 | { 2072 | "cell_type": "code", 2073 | "execution_count": 83, 2074 | "id": "f46dafc9-c125-4571-b418-044eff982b68", 2075 | "metadata": {}, 2076 | "outputs": [ 2077 | { 2078 | "data": { 2079 | "text/plain": [ 2080 | "Pizza(['mozzarella', 'provolone', 'cheddar', 'Parmesan'])" 2081 | ] 2082 | }, 2083 | "execution_count": 83, 2084 | "metadata": {}, 2085 | "output_type": "execute_result" 2086 | } 2087 | ], 2088 | "source": [ 2089 | "Pizza.cheese()" 2090 | ] 2091 | }, 2092 | { 2093 | "cell_type": "code", 2094 | "execution_count": 84, 2095 | "id": "8e4b6894-f33e-48fb-8a03-704ee7fd006b", 2096 | "metadata": {}, 2097 | "outputs": [], 2098 | "source": [ 2099 | "first_pizza = Pizza([])" 2100 | ] 2101 | }, 2102 | { 2103 | "cell_type": "code", 2104 | "execution_count": 85, 2105 | "id": "c744f33f-3e55-4a2e-9800-75de111776c1", 2106 | "metadata": {}, 2107 | "outputs": [ 2108 | { 2109 | "data": { 2110 | "text/plain": [ 2111 | "[]" 2112 | ] 2113 | }, 2114 | "execution_count": 85, 2115 | "metadata": {}, 2116 | "output_type": "execute_result" 2117 | } 2118 | ], 2119 | "source": [ 2120 | "first_pizza.ingredients" 2121 | ] 2122 | }, 2123 | { 2124 | "cell_type": "code", 2125 | "execution_count": 86, 2126 | "id": "f31c278e-b114-47b7-8075-b42a74585075", 2127 | "metadata": {}, 2128 | "outputs": [ 2129 | { 2130 | "data": { 2131 | "text/plain": [ 2132 | "Pizza(['chicken', 'mozzarella'])" 2133 | ] 2134 | }, 2135 | "execution_count": 86, 2136 | "metadata": {}, 2137 | "output_type": "execute_result" 2138 | } 2139 | ], 2140 | "source": [ 2141 | "first_pizza.create_custom_pizza(['chicken', 'mozzarella'])" 2142 | ] 2143 | }, 2144 | { 2145 | "cell_type": "code", 2146 | "execution_count": 87, 2147 | "id": "4097f3d9-7fae-4c67-b6f1-30b3aadd5956", 2148 | "metadata": {}, 2149 | "outputs": [ 2150 | { 2151 | "data": { 2152 | "text/plain": [ 2153 | "Pizza(['mozzarella', 'tomatoes'])" 2154 | ] 2155 | }, 2156 | "execution_count": 87, 2157 | "metadata": {}, 2158 | "output_type": "execute_result" 2159 | } 2160 | ], 2161 | "source": [ 2162 | "# Podemos mostrar los metodos de clase asociados a la clase Pizza sin cambiar el comportamiento de la instancia\n", 2163 | "first_pizza.margherita()" 2164 | ] 2165 | }, 2166 | { 2167 | "cell_type": "code", 2168 | "execution_count": 88, 2169 | "id": "ffad901d-fd1d-4ef4-83c6-a5acca1efb95", 2170 | "metadata": {}, 2171 | "outputs": [ 2172 | { 2173 | "data": { 2174 | "text/plain": [ 2175 | "['chicken', 'mozzarella']" 2176 | ] 2177 | }, 2178 | "execution_count": 88, 2179 | "metadata": {}, 2180 | "output_type": "execute_result" 2181 | } 2182 | ], 2183 | "source": [ 2184 | "first_pizza.ingredients" 2185 | ] 2186 | }, 2187 | { 2188 | "cell_type": "code", 2189 | "execution_count": 89, 2190 | "id": "713b7abf-743d-4e04-98b6-a0ddf2fd4c51", 2191 | "metadata": {}, 2192 | "outputs": [], 2193 | "source": [ 2194 | "# Usando metodos estáticos\n", 2195 | "import math\n", 2196 | "\n", 2197 | "class Pizza:\n", 2198 | " def __init__(self, radius, ingredients):\n", 2199 | " self.radius = radius\n", 2200 | " self.ingredients = ingredients\n", 2201 | "\n", 2202 | " def __repr__(self):\n", 2203 | " return (f'Pizza({self.radius}, '\n", 2204 | " f'{self.ingredients})')\n", 2205 | "\n", 2206 | " def area(self):\n", 2207 | " return self.circle_area(self.radius)\n", 2208 | "\n", 2209 | " @staticmethod\n", 2210 | " def circle_area(r):\n", 2211 | " return r ** 2 * math.pi" 2212 | ] 2213 | }, 2214 | { 2215 | "cell_type": "code", 2216 | "execution_count": 90, 2217 | "id": "5a65b11b-c4ea-43b5-a1a0-072d5a157bf1", 2218 | "metadata": {}, 2219 | "outputs": [], 2220 | "source": [ 2221 | "p = Pizza(4, ['mozzarella', 'tomatoes'])" 2222 | ] 2223 | }, 2224 | { 2225 | "cell_type": "code", 2226 | "execution_count": 91, 2227 | "id": "2ba20bad-b61f-45cd-9b06-6ad3463bcfa8", 2228 | "metadata": {}, 2229 | "outputs": [ 2230 | { 2231 | "data": { 2232 | "text/plain": [ 2233 | "50.26548245743669" 2234 | ] 2235 | }, 2236 | "execution_count": 91, 2237 | "metadata": {}, 2238 | "output_type": "execute_result" 2239 | } 2240 | ], 2241 | "source": [ 2242 | "p.area()" 2243 | ] 2244 | }, 2245 | { 2246 | "cell_type": "code", 2247 | "execution_count": 92, 2248 | "id": "d6a8fa9c-aa5f-4329-8509-7babfee6ea3c", 2249 | "metadata": {}, 2250 | "outputs": [ 2251 | { 2252 | "data": { 2253 | "text/plain": [ 2254 | "50.26548245743669" 2255 | ] 2256 | }, 2257 | "execution_count": 92, 2258 | "metadata": {}, 2259 | "output_type": "execute_result" 2260 | } 2261 | ], 2262 | "source": [ 2263 | "Pizza.circle_area(4)" 2264 | ] 2265 | }, 2266 | { 2267 | "cell_type": "code", 2268 | "execution_count": 93, 2269 | "id": "f20cdbca-5b67-4aad-88de-2500d1a727b8", 2270 | "metadata": {}, 2271 | "outputs": [ 2272 | { 2273 | "data": { 2274 | "text/plain": [ 2275 | "78.53981633974483" 2276 | ] 2277 | }, 2278 | "execution_count": 93, 2279 | "metadata": {}, 2280 | "output_type": "execute_result" 2281 | } 2282 | ], 2283 | "source": [ 2284 | "Pizza.circle_area(5)" 2285 | ] 2286 | }, 2287 | { 2288 | "cell_type": "code", 2289 | "execution_count": 94, 2290 | "id": "4f9ed85b-7b0b-40e3-b18a-9379fea74edd", 2291 | "metadata": {}, 2292 | "outputs": [ 2293 | { 2294 | "data": { 2295 | "text/plain": [ 2296 | "50.26548245743669" 2297 | ] 2298 | }, 2299 | "execution_count": 94, 2300 | "metadata": {}, 2301 | "output_type": "execute_result" 2302 | } 2303 | ], 2304 | "source": [ 2305 | "p.area()" 2306 | ] 2307 | }, 2308 | { 2309 | "cell_type": "code", 2310 | "execution_count": null, 2311 | "id": "8d3a0140-ef95-4c10-832a-ee0d086150f1", 2312 | "metadata": {}, 2313 | "outputs": [], 2314 | "source": [] 2315 | } 2316 | ], 2317 | "metadata": { 2318 | "kernelspec": { 2319 | "display_name": "Python 3 (ipykernel)", 2320 | "language": "python", 2321 | "name": "python3" 2322 | }, 2323 | "language_info": { 2324 | "codemirror_mode": { 2325 | "name": "ipython", 2326 | "version": 3 2327 | }, 2328 | "file_extension": ".py", 2329 | "mimetype": "text/x-python", 2330 | "name": "python", 2331 | "nbconvert_exporter": "python", 2332 | "pygments_lexer": "ipython3", 2333 | "version": "3.11.4" 2334 | } 2335 | }, 2336 | "nbformat": 4, 2337 | "nbformat_minor": 5 2338 | } 2339 | -------------------------------------------------------------------------------- /intro-flask/README.md: -------------------------------------------------------------------------------- 1 | # Introducción a Flask 2 | 3 | ## Proceso de Instalación 4 | 5 | 1. Crea un ambiente virtual: 6 | ``` 7 | python3 -m venv env 8 | ``` 9 | 2. Activa el ambiente virtual: 10 | ``` 11 | # Activación en Unix 12 | source env/bin/activate 13 | 14 | # Activación en Windows 15 | env\Scripts\activate 16 | ``` 17 | 3. Instala los requerimientos del proyecto: 18 | ``` 19 | pip install -r requirements.txt 20 | ``` 21 | 4. Corre tu aplicación: 22 | ``` 23 | python app.py 24 | 25 | or 26 | 27 | flask run --debug 28 | ``` 29 | -------------------------------------------------------------------------------- /intro-flask/app.py: -------------------------------------------------------------------------------- 1 | # import the Flask class from the flask module 2 | from flask import Flask, render_template, redirect, url_for, request, session, flash 3 | from functools import wraps 4 | 5 | 6 | # create the application object 7 | app = Flask(__name__) 8 | 9 | app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' 10 | 11 | 12 | # login required decorator 13 | def login_required(f): 14 | @wraps(f) 15 | def wrap(*args, **kwargs): 16 | if 'logged_in' in session: 17 | return f(*args, **kwargs) 18 | else: 19 | flash('You need to login first.') 20 | return redirect(url_for('login')) 21 | return wrap 22 | 23 | 24 | # use decorators to link the function to a url 25 | @app.route('/') 26 | @login_required 27 | def home(): 28 | # return "Hello, World!" # return a string 29 | return render_template('index.html') # render a template 30 | 31 | 32 | @app.route('/welcome') 33 | def welcome(): 34 | return render_template('welcome.html') # render a template 35 | 36 | 37 | # Route for handling the login page logic 38 | @app.route('/login', methods=['GET', 'POST']) 39 | def login(): 40 | error = None 41 | if request.method == 'POST': 42 | if request.form['username'] != 'admin' or request.form['password'] != 'admin': 43 | error = 'Invalid Credentials. Please try again.' 44 | else: 45 | session['logged_in'] = True 46 | flash('You were logged in.') 47 | return redirect(url_for('home')) 48 | return render_template('login.html', error=error) 49 | 50 | 51 | @app.route('/logout') 52 | @login_required 53 | def logout(): 54 | session.pop('logged_in', None) 55 | flash('You were logged out.') 56 | return redirect(url_for('home')) 57 | 58 | 59 | # start the server with the 'run()' method 60 | if __name__ == '__main__': 61 | app.run(debug=True) 62 | -------------------------------------------------------------------------------- /intro-flask/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.6.2 2 | click==8.1.6 3 | Flask==2.3.2 4 | itsdangerous==2.1.2 5 | Jinja2==3.1.2 6 | MarkupSafe==2.1.3 7 | Werkzeug==2.3.6 8 | -------------------------------------------------------------------------------- /intro-flask/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Flask Intro 5 | 6 | 7 | 8 | 9 |
10 |
11 |

Welcome to Flask!

12 |
13 |

Click here to logout.

14 | {% for message in get_flashed_messages() %} 15 | 18 | {% endfor %} 19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /intro-flask/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Flask Intro - login page 4 | 5 | 6 | 7 | 8 |
9 |
10 |

Please login

11 |
12 |
13 | @ 14 | 15 | 16 | 17 |
18 | {% if error %} 19 | 22 | {% endif %} 23 | {% for message in get_flashed_messages() %} 24 | 27 | {% endfor %} 28 | 29 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /intro-flask/templates/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Flask Intro 5 | 6 | 7 | 8 | 9 |
10 |
11 |

Welcome to Flask!

12 |
13 |

Click here to go home.

14 | {% for message in get_flashed_messages() %} 15 | 18 | {% endfor %} 19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /orm-flask/.env.example: -------------------------------------------------------------------------------- 1 | SECRET_KEY=supersecretkey123 2 | DB_NAME=store 3 | DB_USER=root 4 | DB_PASSWORD=admin 5 | DB_PORT=3306 6 | DB_HOST=localhost 7 | -------------------------------------------------------------------------------- /orm-flask/README.md: -------------------------------------------------------------------------------- 1 | # Bases de Datos en Flask 2 | 3 | ## Proceso de Instalación 4 | 1. [Instala MySQL](https://dev.mysql.com/downloads/) 5 | 2. Crea un ambiente virtual: 6 | ``` 7 | python3 -m venv env 8 | ``` 9 | 3. Activa el ambiente virtual: 10 | ``` 11 | # Activación en Unix 12 | source env/bin/activate 13 | 14 | # Activación en Windows 15 | env\Scripts\activate 16 | ``` 17 | 4. Instala los requerimientos del proyecto: 18 | ``` 19 | pip install -r requirements.txt 20 | ``` 21 | 5. Copia las variables de entorno: 22 | ```commandline 23 | cp .env.example .env 24 | ``` 25 | 6. Conectate en una nueva terminal y abre la consola de MySQL: 26 | ``` 27 | mysql -u root -p 28 | ``` 29 | 6. Crea una base de datos para nuestro proyecto: 30 | ```sql 31 | CREATE DATABASE store; 32 | ``` 33 | 5. Abre una nueva terminal en tu proyecto y corre tu aplicación: 34 | ``` 35 | flask run --debug 36 | ``` 37 | -------------------------------------------------------------------------------- /orm-flask/app.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | from flask import Flask, render_template, request, session, redirect, url_for 4 | 5 | from database import User, Product 6 | 7 | # blueprint 8 | 9 | app = Flask(__name__) 10 | app.secret_key = environ.get("SECRET_KEY") 11 | 12 | 13 | @app.route("/") 14 | def index(): 15 | return render_template("index.html") 16 | 17 | # GET = Obtener un recurso 18 | # POST = Crear un recurso 19 | 20 | 21 | @app.route("/register", methods=["GET", "POST"]) 22 | def register(): 23 | if request.method == "POST": 24 | print(request.form) # Dic 25 | 26 | username = request.form.get("username") # None 27 | password = request.form.get("password") 28 | 29 | if username and password: 30 | user = User.create_user(username, password) # INSERT 31 | session["user_id"] = user.id # ID del usuario en la base de datos 32 | 33 | return redirect(url_for("products")) 34 | 35 | return render_template("register.html") 36 | 37 | @app.route("/login", methods=["GET", "POST"]) 38 | def login(): 39 | if request.method == "POST": 40 | username = request.form.get("username") # None 41 | password = request.form.get("password") 42 | 43 | user = User.select().where(User.username == username & User.password == password).first() 44 | if user: 45 | session["user_id"] = user.id 46 | return redirect(url_for("products")) 47 | 48 | return render_template("login.html") 49 | 50 | @app.route("/logout") 51 | def logout(): 52 | session["user_id"] = None 53 | return render_template("login.html") 54 | 55 | 56 | @app.route("/products") 57 | def products(): 58 | user = User.get(session["user_id"]) 59 | 60 | # _products = Product.select().where(Product.user == user) # 1 61 | _products = user.products 62 | 63 | return render_template("products/index.html", products=_products) 64 | 65 | 66 | @app.route("/products/create", methods=["GET", "POST"]) 67 | def products_create(): 68 | if request.method == "POST": 69 | name = request.form.get("name") 70 | price = request.form.get("price") 71 | 72 | if name and price: 73 | # SELECT * FROM users WHERE id = 74 | user = User.get(session["user_id"]) 75 | 76 | # INSERT INTO products(name, price, user_id) VALUES (name, price, user_id) 77 | Product.create(name=name, price=price, user=user) 78 | return redirect(url_for("products")) 79 | 80 | return render_template("products/create.html") 81 | 82 | 83 | @app.route("/products/update/", methods=["GET", "POST"]) 84 | def products_update(id:int): 85 | _product = Product.select().where(Product.id == id).first() 86 | 87 | if request.method == "POST": 88 | _product.name = request.form.get("name") 89 | _product.price = request.form.get("price") 90 | _product.save() # UPDATE products SET name="" 91 | 92 | return redirect(url_for("products")) 93 | 94 | return render_template("products/update.html", product=_product) 95 | 96 | 97 | if __name__ == "__main__": 98 | app.run(debug=True, load_dotenv=True) 99 | -------------------------------------------------------------------------------- /orm-flask/database.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | from peewee import MySQLDatabase, Model, TextField, DateTimeField, IntegerField, ForeignKeyField, CharField 4 | 5 | import datetime 6 | 7 | 8 | db = MySQLDatabase( 9 | environ.get("DB_NAME"), 10 | user=environ.get("DB_USER"), 11 | password=environ.get("DB_PASSWORD"), 12 | port=int(environ.get("DB_PORT")), 13 | host=environ.get("DB_HOST") 14 | ) 15 | 16 | 17 | class User(Model): # Tables 18 | username = CharField() 19 | password = CharField() 20 | created_at = DateTimeField(default=datetime.datetime.now) 21 | 22 | class Meta: 23 | database = db 24 | db_table = "users" 25 | 26 | @classmethod 27 | def create_user(cls, _username, _password): 28 | _password = "cody_" + _password 29 | return User.create(username=_username, password=_password) 30 | 31 | 32 | # Migrations 33 | class Product(Model): # Tables 34 | name = TextField() 35 | price = IntegerField() 36 | user = ForeignKeyField(User, backref="products") 37 | created_at = DateTimeField(default=datetime.datetime.now) 38 | 39 | @property 40 | def price_format(self): 41 | return f"$ {self.price} dollars" 42 | 43 | class Meta: 44 | database = db 45 | db_table = "products" 46 | 47 | 48 | db.create_tables([User, Product]) 49 | -------------------------------------------------------------------------------- /orm-flask/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.6.2 2 | click==8.1.6 3 | Flask==2.3.2 4 | itsdangerous==2.1.2 5 | Jinja2==3.1.2 6 | MarkupSafe==2.1.3 7 | mysqlclient==2.2.0 8 | peewee==3.16.2 9 | PyMySQL==1.1.1 10 | python-dotenv==1.0.0 11 | Werkzeug==2.3.6 12 | -------------------------------------------------------------------------------- /orm-flask/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PyStore 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 23 |
24 | Bootcamp Backend con Python 25 |

PyStore Online. 🐍

26 |

27 | Web page developed con Flask, Peewee y Python. 28 |

29 | 30 | Start now! 31 | 32 |
33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /orm-flask/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PyStore 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |

PyStore

21 |
22 |
23 |
24 |
25 |
26 | 27 | Landing 28 | 29 |

Login with your account

30 | 31 |
32 | 33 | Your username 34 |
35 |
36 | 37 | Password 38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /orm-flask/templates/products/create.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Products 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 | Logout 18 |
19 |
20 |
21 |
22 |
23 | List of Products 24 |

New Products

25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | Name 33 |
34 |
35 | 36 | Price 37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /orm-flask/templates/products/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Products 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 | Logout 18 |
19 |
20 | New Product 21 |

List of Products

22 |
23 |
24 | 25 | {% for product in products %} 26 |
27 |
28 |
29 | Category 30 | 34 |
35 | {{ product.created_at }} 36 |

{{ product.name }}

37 |

{{ product.price_format }}

38 | Edit 39 | Delete 40 |
41 |
42 | {% endfor %} 43 | 44 |
45 |
46 |
47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /orm-flask/templates/products/update.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Product 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 | Logout 18 |
19 |
20 |
21 |
22 |
23 | List of Products 24 |

Edit Products

25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | Name 33 |
34 |
35 | 36 | Price 37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /orm-flask/templates/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PyStore 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |

PyStore

22 |

Create a free account!

23 |
24 |
25 |
26 |
27 |
28 | 29 | Landing 30 | 31 |

Create new account

32 | 33 |
34 | 35 | Your username 36 |
37 |
38 | 39 | Password 40 |
41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /rest-api-with-DRF/README.md: -------------------------------------------------------------------------------- 1 | # REST API con Django REST Framework 2 | 3 | ## Proceso de Instalación 4 | 1. Crea un ambiente virtual: 5 | ``` 6 | python3 -m venv env 7 | ``` 8 | 2. Activa el ambiente virtual: 9 | ``` 10 | # Activación en Unix 11 | source env/bin/activate 12 | 13 | # Activación en Windows 14 | env\Scripts\activate 15 | ``` 16 | 3. Instala Django y DRF: 17 | ``` 18 | pip install django 19 | pip install djangorestframework 20 | ``` 21 | 4. Crea un nuevo proyecto en Django: 22 | ``` 23 | django-admin startproject shopping_cart 24 | ``` 25 | 5. Crea una nueva aplicación en Django: 26 | ``` 27 | cd shopping_cart 28 | python manage.py startapp api 29 | ``` 30 | 6. Agrega la aplicación de `rest_framework` y la que acabamos de crear en el archivo de `settings.py`: 31 | ``` 32 | INSTALLED_APPS = [ 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'rest_framework', 40 | 'api' 41 | ] 42 | ``` 43 | 7. Genera las migraciones y ejeculatas: 44 | ``` 45 | python manage.py makemigrations 46 | python manage.py migrate 47 | ``` 48 | 8. Crea un super usuario: 49 | ``` 50 | python manage.py createsuperuser 51 | ``` 52 | 9. Corre la aplicación: 53 | ``` 54 | python manage.py runserver 55 | ``` 56 | ### Probando nuestra API 57 | 58 | Para probar nuestra REST API vamos a usar Postman para hacer peticiones a nuestra API, descargala [aquí.](https://www.postman.com/downloads/) 59 | 60 | Abre postman e importa la colección que esta en el repositorio. 61 | 62 | 63 | _Ejemplo base tomado de: [Creating a REST API with Django REST Framework](https://stackabuse.com/creating-a-rest-api-with-django-rest-framework/)_ 64 | -------------------------------------------------------------------------------- /rest-api-with-DRF/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.8.1 2 | Django==5.2 3 | django-filter==23.2 4 | djangorestframework==3.16.0 5 | pytz==2023.3 6 | sqlparse==0.4.4 7 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/rest-api-with-DRF/shopping_cart/__init__.py -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/rest-api-with-DRF/shopping_cart/api/__init__.py -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Product, Cart, CartItem 3 | 4 | # Register your models here. 5 | admin.site.register(Product) 6 | admin.site.register(Cart) 7 | admin.site.register(CartItem) 8 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.2 on 2025-04-22 02:29 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Cart', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created_at', models.DateTimeField(auto_now_add=True)), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='Product', 24 | fields=[ 25 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('name', models.CharField(max_length=200)), 27 | ('price', models.FloatField()), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name='CartItem', 32 | fields=[ 33 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('quantity', models.PositiveIntegerField(default=1)), 35 | ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='api.cart')), 36 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.product')), 37 | ], 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/rest-api-with-DRF/shopping_cart/api/migrations/__init__.py -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | # Create your models here. 5 | class Product(models.Model): 6 | name = models.CharField(max_length=200) 7 | price = models.FloatField() 8 | 9 | def __str__(self): 10 | return self.name 11 | 12 | 13 | class Cart(models.Model): 14 | created_at = models.DateTimeField(auto_now_add=True) 15 | 16 | def __str__(self): 17 | return f"Cart #{self.id}" 18 | 19 | def total(self): 20 | return sum(item.total_price() for item in self.items.all()) 21 | 22 | 23 | class CartItem(models.Model): 24 | cart = models.ForeignKey(Cart, related_name="items", on_delete=models.CASCADE) 25 | product = models.ForeignKey(Product, on_delete=models.CASCADE) 26 | quantity = models.PositiveIntegerField(default=1) 27 | 28 | def __str__(self): 29 | return f"Cart #{self.cart.id} - Product: {self.product.name}" 30 | 31 | def total_price(self): 32 | return self.product.price * self.quantity 33 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Cart, CartItem, Product 3 | 4 | 5 | class ProductSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Product 8 | fields = ["id", "name", "price"] 9 | 10 | 11 | class CartItemSerializer(serializers.ModelSerializer): 12 | product = ProductSerializer(read_only=True) 13 | 14 | class Meta: 15 | model = CartItem 16 | fields = ["id", "product", "quantity", "total_price"] 17 | 18 | 19 | class CartSerializer(serializers.ModelSerializer): 20 | items = CartItemSerializer(many=True, read_only=True) 21 | total = serializers.SerializerMethodField() 22 | 23 | class Meta: 24 | model = Cart 25 | fields = ["id", "created_at", "total", "items"] 26 | 27 | def get_total(self, obj): 28 | return obj.total() 29 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import ( 4 | CartDetailView, 5 | CartItemCreateView, 6 | CartItemUpdateView, 7 | ProductListView, 8 | ) 9 | 10 | urlpatterns = [ 11 | path("products/", ProductListView.as_view()), 12 | path("cart//", CartDetailView.as_view()), 13 | path("cart//add/", CartItemCreateView.as_view()), 14 | path("cart//update/", CartItemUpdateView.as_view()), 15 | ] 16 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/api/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404 2 | from rest_framework import status 3 | from rest_framework.generics import GenericAPIView 4 | from rest_framework.pagination import PageNumberPagination 5 | from rest_framework.response import Response 6 | from rest_framework.views import APIView 7 | 8 | from .models import Cart, CartItem, Product 9 | from .serializers import CartItemSerializer, CartSerializer, ProductSerializer 10 | 11 | 12 | # Create your views here. 13 | # class ProductListView(GenericAPIView): 14 | class ProductListView(APIView): 15 | serializer_class = ProductSerializer 16 | # pagination_class = PageNumberPagination 17 | 18 | def get_queryset(self): 19 | return Product.objects.all() 20 | 21 | def get(self, request): 22 | products = Product.objects.all() 23 | serializer = ProductSerializer(products, many=True) 24 | return Response( 25 | {"status": "success", "data": serializer.data}, status=status.HTTP_200_OK 26 | ) 27 | # return self.get_paginated_response({"status": "success", "data": self.paginate_queryset(serializer.data)}) 28 | 29 | 30 | class CartDetailView(APIView): 31 | serializer_class = CartSerializer 32 | 33 | def get_queryset(self): 34 | return Cart.objects.all() 35 | 36 | def get(self, request, id=None): 37 | try: 38 | cart = Cart.objects.get(id=id) 39 | except Cart.DoesNotExist: 40 | return Response( 41 | {"error": "Cart not found"}, status=status.HTTP_404_NOT_FOUND 42 | ) 43 | 44 | serializer = CartSerializer(cart) 45 | return Response( 46 | {"status": "success", "data": serializer.data}, status=status.HTTP_200_OK 47 | ) 48 | 49 | 50 | class CartItemCreateView(APIView): 51 | serializer_class = CartItemSerializer 52 | 53 | def post(self, request, id): 54 | try: 55 | cart = Cart.objects.get(id=id) 56 | except Cart.DoesNotExist: 57 | return Response( 58 | {"error": "Cart not found"}, status=status.HTTP_404_NOT_FOUND 59 | ) 60 | 61 | product_id = request.data.get("product_id") 62 | quantity = int(request.data.get("quantity", 1)) 63 | 64 | try: 65 | product = Product.objects.get(id=product_id) 66 | except Product.DoesNotExist: 67 | return Response( 68 | {"error": "Product not found"}, status=status.HTTP_404_NOT_FOUND 69 | ) 70 | 71 | item, _ = CartItem.objects.get_or_create(cart=cart, product=product) 72 | serializer = CartItemSerializer(item, data=request.data, partial=True) 73 | if serializer.is_valid(): 74 | serializer.save() 75 | return Response( 76 | {"message": "Product added to cart"}, status=status.HTTP_200_OK 77 | ) 78 | else: 79 | return Response( 80 | {"status": "error", "data": serializer.errors}, 81 | status=status.HTTP_400_BAD_REQUEST, 82 | ) 83 | 84 | 85 | class CartItemUpdateView(APIView): 86 | serializer_class = CartItemSerializer 87 | 88 | def patch(self, request, id): 89 | try: 90 | cart = Cart.objects.get(id=id) 91 | except Cart.DoesNotExist: 92 | return Response( 93 | {"error": "Cart not found"}, status=status.HTTP_404_NOT_FOUND 94 | ) 95 | 96 | product_id = request.data.get("product_id") 97 | 98 | try: 99 | product = Product.objects.get(id=product_id) 100 | except Product.DoesNotExist: 101 | return Response( 102 | {"error": "Product not found"}, status=status.HTTP_404_NOT_FOUND 103 | ) 104 | 105 | cart_item = get_object_or_404(CartItem, cart=cart, product=product) 106 | serializer = CartItemSerializer(cart_item, data=request.data, partial=True) 107 | if serializer.is_valid(): 108 | serializer.save() 109 | return Response( 110 | {"status": "success", "data": serializer.data}, 111 | status=status.HTTP_200_OK, 112 | ) 113 | else: 114 | return Response( 115 | {"status": "error", "data": serializer.errors}, 116 | status=status.HTTP_400_BAD_REQUEST, 117 | ) 118 | 119 | def delete(self, request, id=None): 120 | try: 121 | cart = Cart.objects.get(id=id) 122 | except Cart.DoesNotExist: 123 | return Response( 124 | {"error": "Cart not found"}, status=status.HTTP_404_NOT_FOUND 125 | ) 126 | 127 | product_id = request.data.get("product_id") 128 | 129 | try: 130 | product = Product.objects.get(id=product_id) 131 | except Product.DoesNotExist: 132 | return Response( 133 | {"error": "Product not found"}, status=status.HTTP_404_NOT_FOUND 134 | ) 135 | 136 | item = get_object_or_404(CartItem, cart=cart, product=product) 137 | item.delete() 138 | return Response( 139 | {"status": "success", "data": "Item Deleted"}, 140 | status=status.HTTP_204_NO_CONTENT, 141 | ) 142 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopping_cart.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/shopping_cart/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/rest-api-with-DRF/shopping_cart/shopping_cart/__init__.py -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/shopping_cart/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for shopping_cart project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopping_cart.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/shopping_cart/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for shopping_cart project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.2.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'django-insecure-t1fd1&bo1vz^044+!wj50(umpvw=m4esz+hm@##1=m^w(!---m' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'rest_framework', 41 | 'api' 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | REST_FRAMEWORK = { 55 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 56 | 'PAGE_SIZE': 5, 57 | } 58 | 59 | ROOT_URLCONF = 'shopping_cart.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': [], 65 | 'APP_DIRS': True, 66 | 'OPTIONS': { 67 | 'context_processors': [ 68 | 'django.template.context_processors.debug', 69 | 'django.template.context_processors.request', 70 | 'django.contrib.auth.context_processors.auth', 71 | 'django.contrib.messages.context_processors.messages', 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'shopping_cart.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/4.2/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.sqlite3', 86 | 'NAME': BASE_DIR / 'db.sqlite3', 87 | } 88 | } 89 | 90 | 91 | # Password validation 92 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators 93 | 94 | AUTH_PASSWORD_VALIDATORS = [ 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 106 | }, 107 | ] 108 | 109 | 110 | # Internationalization 111 | # https://docs.djangoproject.com/en/4.2/topics/i18n/ 112 | 113 | LANGUAGE_CODE = 'en-us' 114 | 115 | TIME_ZONE = 'UTC' 116 | 117 | USE_I18N = True 118 | 119 | USE_TZ = True 120 | 121 | 122 | # Static files (CSS, JavaScript, Images) 123 | # https://docs.djangoproject.com/en/4.2/howto/static-files/ 124 | 125 | STATIC_URL = 'static/' 126 | 127 | # Default primary key field type 128 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field 129 | 130 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 131 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/shopping_cart/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for shopping_cart project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/4.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | 20 | urlpatterns = [ 21 | path('admin/', admin.site.urls), 22 | path('api/', include('api.urls')), 23 | ] 24 | -------------------------------------------------------------------------------- /rest-api-with-DRF/shopping_cart/shopping_cart/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for shopping_cart project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopping_cart.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /rest-api-with-django/README.md: -------------------------------------------------------------------------------- 1 | # REST API con Django 2 | 3 | ## Proceso de Instalación 4 | 1. Crea un ambiente virtual: 5 | ``` 6 | python3 -m venv env 7 | ``` 8 | 2. Activa el ambiente virtual: 9 | ``` 10 | # Activación en Unix 11 | source env/bin/activate 12 | 13 | # Activación en Windows 14 | env\Scripts\activate 15 | ``` 16 | 3. Instala Django: 17 | ``` 18 | pip install django 19 | ``` 20 | 4. Crea un nuevo proyecto en Django: 21 | ``` 22 | django-admin startproject shopping_cart 23 | ``` 24 | 5. Crea una nueva aplicación en Django: 25 | ``` 26 | cd shopping_cart 27 | python manage.py startapp api 28 | ``` 29 | 6. Agrega esta aplicación en el archivo de `settings.py`: 30 | ``` 31 | INSTALLED_APPS = [ 32 | 'django.contrib.admin', 33 | 'django.contrib.auth', 34 | 'django.contrib.contenttypes', 35 | 'django.contrib.sessions', 36 | 'django.contrib.messages', 37 | 'django.contrib.staticfiles', 38 | 'api' 39 | ] 40 | ``` 41 | 7. Genera las migraciones y ejeculatas: 42 | ``` 43 | python manage.py makemigrations 44 | python manage.py migrate 45 | ``` 46 | 8. Crea un super usuario: 47 | ``` 48 | python manage.py createsuperuser 49 | ``` 50 | 9. Corre la aplicación: 51 | ``` 52 | python manage.py runserver 53 | ``` 54 | ### Probando nuestra API 55 | 56 | Para probar nuestra REST API vamos a usar Postman para hacer peticiones a nuestra API, descargala [aquí.](https://www.postman.com/downloads/) 57 | 58 | Abre postman e importa la colección que esta en el repositorio. 59 | -------------------------------------------------------------------------------- /rest-api-with-django/REST API con Django - Shopping Cart.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "680f6ac6-ee26-4e08-b6e4-793dfd128dce", 4 | "name": "REST API con Django - Shopping Cart", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "4903572" 7 | }, 8 | "item": [ 9 | { 10 | "name": "/api/products", 11 | "request": { 12 | "method": "GET", 13 | "header": [], 14 | "url": { 15 | "raw": "http://127.0.0.1:8000/api/products/", 16 | "protocol": "http", 17 | "host": [ 18 | "127", 19 | "0", 20 | "0", 21 | "1" 22 | ], 23 | "port": "8000", 24 | "path": [ 25 | "api", 26 | "products", 27 | "" 28 | ] 29 | } 30 | }, 31 | "response": [] 32 | }, 33 | { 34 | "name": "/api/cart/", 35 | "request": { 36 | "method": "GET", 37 | "header": [], 38 | "url": { 39 | "raw": "http://127.0.0.1:8000/api/cart/1", 40 | "protocol": "http", 41 | "host": [ 42 | "127", 43 | "0", 44 | "0", 45 | "1" 46 | ], 47 | "port": "8000", 48 | "path": [ 49 | "api", 50 | "cart", 51 | "1" 52 | ] 53 | } 54 | }, 55 | "response": [] 56 | }, 57 | { 58 | "name": "/api/cart//add", 59 | "request": { 60 | "method": "POST", 61 | "header": [], 62 | "body": { 63 | "mode": "raw", 64 | "raw": "{\n \"product_id\": 1,\n \"quantity\": 1\n}", 65 | "options": { 66 | "raw": { 67 | "language": "json" 68 | } 69 | } 70 | }, 71 | "url": { 72 | "raw": "http://127.0.0.1:8000/api/cart/1/add/", 73 | "protocol": "http", 74 | "host": [ 75 | "127", 76 | "0", 77 | "0", 78 | "1" 79 | ], 80 | "port": "8000", 81 | "path": [ 82 | "api", 83 | "cart", 84 | "1", 85 | "add", 86 | "" 87 | ] 88 | } 89 | }, 90 | "response": [] 91 | }, 92 | { 93 | "name": "/api/cart//update", 94 | "request": { 95 | "method": "PATCH", 96 | "header": [], 97 | "body": { 98 | "mode": "raw", 99 | "raw": "{\n \"product_id\": 1,\n \"quantity\": 2\n}", 100 | "options": { 101 | "raw": { 102 | "language": "json" 103 | } 104 | } 105 | }, 106 | "url": { 107 | "raw": "http://127.0.0.1:8000/api/cart/1/update/", 108 | "protocol": "http", 109 | "host": [ 110 | "127", 111 | "0", 112 | "0", 113 | "1" 114 | ], 115 | "port": "8000", 116 | "path": [ 117 | "api", 118 | "cart", 119 | "1", 120 | "update", 121 | "" 122 | ] 123 | } 124 | }, 125 | "response": [] 126 | }, 127 | { 128 | "name": "/api/cart//update", 129 | "request": { 130 | "method": "DELETE", 131 | "header": [], 132 | "body": { 133 | "mode": "raw", 134 | "raw": "{\n \"product_id\": 1\n}", 135 | "options": { 136 | "raw": { 137 | "language": "json" 138 | } 139 | } 140 | }, 141 | "url": { 142 | "raw": "http://127.0.0.1:8000/api/cart/1/update/", 143 | "protocol": "http", 144 | "host": [ 145 | "127", 146 | "0", 147 | "0", 148 | "1" 149 | ], 150 | "port": "8000", 151 | "path": [ 152 | "api", 153 | "cart", 154 | "1", 155 | "update", 156 | "" 157 | ] 158 | } 159 | }, 160 | "response": [] 161 | } 162 | ] 163 | } -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/rest-api-with-django/shopping_cart/api/__init__.py -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Product, Cart, CartItem 4 | 5 | # Register your models here. 6 | admin.site.register(Product) 7 | admin.site.register(Cart) 8 | admin.site.register(CartItem) 9 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2025-04-21 01:37 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Cart', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created_at', models.DateTimeField(auto_now_add=True)), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='Product', 24 | fields=[ 25 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('name', models.CharField(max_length=200)), 27 | ('price', models.FloatField()), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name='CartItem', 32 | fields=[ 33 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('quantity', models.PositiveIntegerField(default=1)), 35 | ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='api.cart')), 36 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.product')), 37 | ], 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/rest-api-with-django/shopping_cart/api/migrations/__init__.py -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | # Create your models here. 5 | class Product(models.Model): 6 | name = models.CharField(max_length=200) 7 | price = models.FloatField() 8 | 9 | def __str__(self): 10 | return self.name 11 | 12 | 13 | class Cart(models.Model): 14 | created_at = models.DateTimeField(auto_now_add=True) 15 | 16 | def __str__(self): 17 | return f"Cart #{self.id}" 18 | 19 | def total(self): 20 | return sum(item.total_price() for item in self.items.all()) 21 | 22 | 23 | class CartItem(models.Model): 24 | cart = models.ForeignKey(Cart, related_name="items", on_delete=models.CASCADE) 25 | product = models.ForeignKey(Product, on_delete=models.CASCADE) 26 | quantity = models.PositiveIntegerField(default=1) 27 | 28 | def __str__(self): 29 | return f"Cart #{self.cart.id} - Product: {self.product.name}" 30 | 31 | def total_price(self): 32 | return self.product.price * self.quantity 33 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ( 3 | ProductListView, 4 | CartDetailView, 5 | CartItemCreateView, 6 | CartItemUpdateView, 7 | ) 8 | 9 | urlpatterns = [ 10 | path("products/", ProductListView.as_view()), 11 | path("cart//", CartDetailView.as_view()), 12 | path("cart//add/", CartItemCreateView.as_view()), 13 | path("cart//update/", CartItemUpdateView.as_view()), 14 | ] 15 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/api/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.http import JsonResponse 4 | from django.utils.decorators import method_decorator 5 | from django.views import View 6 | from django.forms.models import model_to_dict 7 | from django.views.decorators.csrf import csrf_exempt 8 | 9 | from .models import Product, Cart, CartItem 10 | 11 | 12 | class ProductListView(View): 13 | def get(self, request): 14 | products = Product.objects.all() 15 | data = [model_to_dict(p) for p in products] 16 | return JsonResponse(data, safe=False) 17 | 18 | 19 | class CartDetailView(View): 20 | def get(self, request, id): 21 | try: 22 | cart = Cart.objects.get(id=id) 23 | except Cart.DoesNotExist: 24 | return JsonResponse({"error": "Cart not found"}, status=404) 25 | 26 | items_data = [] 27 | for item in cart.items.select_related("product"): 28 | items_data.append( 29 | { 30 | "product": item.product.name, 31 | "price": float(item.product.price), 32 | "quantity": item.quantity, 33 | "total_price": float(item.total_price()), 34 | } 35 | ) 36 | 37 | cart_data = { 38 | "id": cart.id, 39 | "created_at": cart.created_at, 40 | "total": cart.total(), 41 | "items": items_data, 42 | } 43 | return JsonResponse(cart_data, safe=False) 44 | 45 | 46 | # Agregamos este decorador para evitar problemas por Cross Site Request Forgery Attacks (CSRF) 47 | @method_decorator(csrf_exempt, name="dispatch") 48 | class CartItemCreateView(View): 49 | def post(self, request, id): 50 | try: 51 | cart = Cart.objects.get(id=id) 52 | except Cart.DoesNotExist: 53 | return JsonResponse({"error": "Cart not found"}, status=404) 54 | 55 | try: 56 | data = json.loads(request.body.decode("utf-8")) 57 | product_id = data.get("product_id") 58 | quantity = int(data.get("quantity", 1)) 59 | except (KeyError, ValueError, json.JSONDecodeError): 60 | return JsonResponse({"error": "Invalid input"}, status=400) 61 | 62 | try: 63 | product = Product.objects.get(id=product_id) 64 | except Product.DoesNotExist: 65 | return JsonResponse({"error": "Product not found"}, status=404) 66 | 67 | item, created = CartItem.objects.get_or_create(cart=cart, product=product) 68 | if not created: 69 | item.quantity += quantity 70 | else: 71 | item.quantity = quantity 72 | item.save() 73 | 74 | return JsonResponse( 75 | { 76 | "message": "Product added to cart", 77 | "cart_id": cart.id, 78 | "product": product.name, 79 | "quantity": item.quantity, 80 | } 81 | ) 82 | 83 | 84 | @method_decorator(csrf_exempt, name="dispatch") 85 | class CartItemUpdateView(View): 86 | def patch(self, request, id): 87 | try: 88 | cart = Cart.objects.get(id=id) 89 | except Cart.DoesNotExist: 90 | return JsonResponse({"error": "Cart not found"}, status=404) 91 | 92 | try: 93 | data = json.loads(request.body.decode("utf-8")) 94 | product_id = data.get("product_id") 95 | quantity = int(data.get("quantity", 1)) 96 | except (KeyError, ValueError, json.JSONDecodeError): 97 | return JsonResponse({"error": "Invalid input"}, status=400) 98 | 99 | try: 100 | product = Product.objects.get(id=product_id) 101 | except Product.DoesNotExist: 102 | return JsonResponse({"error": "Product not found"}, status=404) 103 | 104 | cart_item = CartItem.objects.get(cart=cart, product=product) 105 | cart_item.quantity = quantity 106 | 107 | cart_item.save() 108 | 109 | return JsonResponse( 110 | { 111 | "message": "Product updated in the cart", 112 | "cart_id": cart.id, 113 | "product": product.name, 114 | "quantity": cart_item.quantity, 115 | } 116 | ) 117 | 118 | def delete(self, request, id): 119 | try: 120 | cart = Cart.objects.get(id=id) 121 | except Cart.DoesNotExist: 122 | return JsonResponse({"error": "Cart not found"}, status=404) 123 | 124 | try: 125 | data = json.loads(request.body.decode("utf-8")) 126 | product_id = data.get("product_id") 127 | except (KeyError, ValueError, json.JSONDecodeError): 128 | return JsonResponse({"error": "Invalid input"}, status=400) 129 | 130 | try: 131 | product = Product.objects.get(id=product_id) 132 | except Product.DoesNotExist: 133 | return JsonResponse({"error": "Product not found"}, status=404) 134 | 135 | cart_item = CartItem.objects.get(cart=cart, product=product) 136 | 137 | cart_item.delete() 138 | 139 | return JsonResponse( 140 | { 141 | "message": "Card Item has been deleted", 142 | } 143 | ) 144 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopping_cart.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/shopping_cart/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carogomezt/bootcamp-backend-python2/f4e266c9f66c77083cfe0e3c198d17368527291b/rest-api-with-django/shopping_cart/shopping_cart/__init__.py -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/shopping_cart/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for shopping_cart project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopping_cart.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/shopping_cart/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for shopping_cart project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.2.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-%c4j_2s%*o3v2=^lec#m@8$vwfh!ogfc#i_=37pd3ivu4b+^)&" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "api", 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | "django.middleware.security.SecurityMiddleware", 45 | "django.contrib.sessions.middleware.SessionMiddleware", 46 | "django.middleware.common.CommonMiddleware", 47 | "django.middleware.csrf.CsrfViewMiddleware", 48 | "django.contrib.auth.middleware.AuthenticationMiddleware", 49 | "django.contrib.messages.middleware.MessageMiddleware", 50 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 51 | ] 52 | 53 | ROOT_URLCONF = "shopping_cart.urls" 54 | 55 | TEMPLATES = [ 56 | { 57 | "BACKEND": "django.template.backends.django.DjangoTemplates", 58 | "DIRS": [], 59 | "APP_DIRS": True, 60 | "OPTIONS": { 61 | "context_processors": [ 62 | "django.template.context_processors.debug", 63 | "django.template.context_processors.request", 64 | "django.contrib.auth.context_processors.auth", 65 | "django.contrib.messages.context_processors.messages", 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = "shopping_cart.wsgi.application" 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/4.2/ref/settings/#databases 76 | 77 | DATABASES = { 78 | "default": { 79 | "ENGINE": "django.db.backends.sqlite3", 80 | "NAME": BASE_DIR / "db.sqlite3", 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 91 | }, 92 | { 93 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/4.2/topics/i18n/ 106 | 107 | LANGUAGE_CODE = "en-us" 108 | 109 | TIME_ZONE = "UTC" 110 | 111 | USE_I18N = True 112 | 113 | USE_TZ = True 114 | 115 | 116 | # Static files (CSS, JavaScript, Images) 117 | # https://docs.djangoproject.com/en/4.2/howto/static-files/ 118 | 119 | STATIC_URL = "static/" 120 | 121 | # Default primary key field type 122 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field 123 | 124 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 125 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/shopping_cart/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for shopping_cart project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/4.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.contrib import admin 19 | from django.urls import path, include 20 | 21 | urlpatterns = [ 22 | path("admin/", admin.site.urls), 23 | path("api/", include("api.urls")), 24 | ] 25 | -------------------------------------------------------------------------------- /rest-api-with-django/shopping_cart/shopping_cart/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for shopping_cart project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopping_cart.settings') 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------