diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7670574 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,67 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +.dockerignore +Dockerfile* +docker-compose* + +# Documentation +README.md +*.md + +# Database (SQLite file should not be in the container) +db.sqlite3 +*.db + +# Logs +*.log diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6952917 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Fill in the .env.example file with your environment variables. +# Copy this file to .env and fill in the values. +MYSQL_ROOT_PASSWORD=1234 +SECRET_KEY=django-insecure5ai70emuksy&%!7-2lbs=d438o*kf^7*au%4%ypn4&33ex-a-l +DEBUG=True +DJANGO_SETTINGS_MODULE=core.settings.dev +ALLOWED_HOSTS=* \ No newline at end of file diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..378b95a --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,26 @@ +# .github/actions/setup/action.yml +name: 'Setup Python Project' +description: 'Checks out code, sets up Python and uv, and installs dependencies.' + +inputs: + python-version-file: + required: true + type: string + default: 'pyproject.toml' + +runs: + using: 'composite' + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: '${{ inputs.python-version-file }}' + + - name: Setup UV + uses: astral-sh/setup-uv@v1 + with: + enable-cache: true + + - name: Install Dependencies + run: uv sync --all-extras + shell: bash diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..716d7f5 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,56 @@ +# .github/workflows/pr-checks.yml +name: Pull Request Checks + +on: + workflow_dispatch: + inputs: + debug: + required: false + type: boolean + default: false + pull_request: + branches: + - 'main' + - 'dev' + +jobs: + lint-and-format: + name: Lint & Format Check + # This job only runs for PRs targeting the 'dev' branch + if: github.event.inputs.debug == 'true' || github.base_ref == 'dev' + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Project + uses: ./.github/actions/setup + + - name: Run Linter + run: uvx ruff check . + shell: bash + + - name: Run Formatter Check + run: uvx ruff format --check . + shell: bash + + test: + name: Run Django Tests + # This job only runs for PRs targeting the 'main' branch + if: github.event.inputs.debug == 'true' || github.base_ref == 'main' + runs-on: ubuntu-latest + steps: + - name: "Verify source branch is 'dev'" + if: ${{ github.head_ref != 'dev' && github.event_name == 'pull_request' }} + run: | + echo "ERROR: Pull requests to 'main' must come from the 'dev' branch." + exit 1 + + - name: Checkout Code + uses: actions/checkout@v4 + - name: Setup Project + uses: ./.github/actions/setup + + - name: Run Tests + run: uv run python manage.py test --verbosity 2 + shell: bash diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d88b4a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,207 @@ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +**/migrations/?*__init__.py +__pycache__/ +# Ignore all migrations except the initial one + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f342b47 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Use a Python image with uv pre-installed +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim + +# Install the project into `/app` +WORKDIR /app + +# Enable bytecode compilation +ENV UV_COMPILE_BYTECODE=1 + +# Copy from the cache instead of linking since it's a mounted volume +ENV UV_LINK_MODE=copy + +# Install the project's dependencies using the lockfile and settings +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --locked --no-install-project --no-dev + +# Then, add the rest of the project source code and install it +# Installing separately from its dependencies allows optimal layer caching +COPY . /app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --locked --no-dev + +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" + +# Add static files for django +RUN --mount=type=cache,target=/root/.cache/uv \ + uv run manage.py collectstatic --noinput + +# Run Migrations +RUN --mount=type=cache,target=/root/.cache/uv \ + uv run manage.py makemigrations &&\ + uv run manage.py migrate + +CMD ["uv", "run","manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/app/__pycache__/__init__.cpython-311.pyc b/app/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index cbda3d5..0000000 Binary files a/app/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/app/__pycache__/admin.cpython-311.pyc b/app/__pycache__/admin.cpython-311.pyc deleted file mode 100644 index 911515b..0000000 Binary files a/app/__pycache__/admin.cpython-311.pyc and /dev/null differ diff --git a/app/__pycache__/apps.cpython-311.pyc b/app/__pycache__/apps.cpython-311.pyc deleted file mode 100644 index 153b101..0000000 Binary files a/app/__pycache__/apps.cpython-311.pyc and /dev/null differ diff --git a/app/__pycache__/forms.cpython-311.pyc b/app/__pycache__/forms.cpython-311.pyc deleted file mode 100644 index b41fe89..0000000 Binary files a/app/__pycache__/forms.cpython-311.pyc and /dev/null differ diff --git a/app/__pycache__/models.cpython-311.pyc b/app/__pycache__/models.cpython-311.pyc deleted file mode 100644 index 2ad6132..0000000 Binary files a/app/__pycache__/models.cpython-311.pyc and /dev/null differ diff --git a/app/__pycache__/urls.cpython-311.pyc b/app/__pycache__/urls.cpython-311.pyc deleted file mode 100644 index 02e0e13..0000000 Binary files a/app/__pycache__/urls.cpython-311.pyc and /dev/null differ diff --git a/app/__pycache__/views.cpython-311.pyc b/app/__pycache__/views.cpython-311.pyc deleted file mode 100644 index b21421c..0000000 Binary files a/app/__pycache__/views.cpython-311.pyc and /dev/null differ diff --git a/app/admin.py b/app/admin.py index ba6a92f..6760e81 100644 --- a/app/admin.py +++ b/app/admin.py @@ -1,7 +1,27 @@ from django.contrib import admin -from .models import Cliente, Producto, Contacto +from .models import Producto, Contacto, NuevoUsuario, Comentario +from django.contrib.auth.admin import UserAdmin # Register your models here. -admin.site.register(Cliente) + +# admin.site.register(Cliente) admin.site.register(Producto) -admin.site.register(Contacto) \ No newline at end of file +admin.site.register(Contacto) + + +class NuevoUsuarioAdmin(UserAdmin): + list_display = ('username', 'email', 'fecha_nacimiento', 'telefono') + fieldsets = UserAdmin.fieldsets + ( + ('Información adicional', { + 'fields': ('fecha_nacimiento', 'telefono') + }), + ) + +admin.site.register(NuevoUsuario, NuevoUsuarioAdmin) + +class ComentarioAdmin(admin.ModelAdmin): + list_display = ('usuario', 'texto', 'fecha_creacion', 'activo') + list_filter = ('activo', 'fecha_creacion') + search_fields = ('texto', 'usuario__username') + +admin.site.register(Comentario, ComentarioAdmin) \ No newline at end of file diff --git a/app/forms.py b/app/forms.py index 163d852..86f68b6 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,8 +1,6 @@ from django import forms -from .models import Producto,Contacto - +from .models import Producto, Contacto, NuevoUsuario from django.contrib.auth.forms import UserCreationForm -from django.contrib.auth.models import User #------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -23,29 +21,68 @@ class Meta: #------------------------------------------------------------------------------------------------------------------------------------------------------------ # FORMULARIO CREADO PARA REGISTRO DE USUARIO class UserRegisterForm(UserCreationForm): - email = forms.EmailField() - password1 = forms.CharField(label="Contraseña",widget=forms.PasswordInput) - password2 = forms.CharField(label="Repita su contraseña",widget=forms.PasswordInput) + email = forms.EmailField(label="Correo electrónico") + password1 = forms.CharField(label="Contraseña",widget=forms.PasswordInput, help_text="Mínimo 8 caracteres.") + password2 = forms.CharField(label="Confirmar contraseña",widget=forms.PasswordInput) + fecha_nacimiento = forms.DateField(required=False, widget=forms.DateInput(attrs={'type': 'date'}), label="Fecha de nacimiento") + telefono = forms.CharField(required=False, max_length=50, label="Teléfono") + + # Validacion para datos unicos + def clean_email(self): + email = self.cleaned_data.get('email') + if NuevoUsuario.objects.filter(email=email).exists(): + raise forms.ValidationError("Este correo electrónico ya está registrado") + return email.lower() + + def clean_username(self): + username = self.cleaned_data.get('username') + return username.lower() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields['username'].label = "Nombre de usuario" + self.fields['username'].widget.attrs.update({'placeholder': 'Ej: juan123'}) + + self.fields['email'].widget.attrs.update({'placeholder': 'Ej: juan@mail.com'}) + self.fields['password1'].widget.attrs.update({'placeholder': 'Contraseña segura'}) + self.fields['password2'].widget.attrs.update({'placeholder': 'Confirmar contraseña'}) + self.fields['fecha_nacimiento'].widget.attrs.update({'placeholder': 'dd/mm/aaaa'}) + self.fields['telefono'].widget.attrs.update({'placeholder': 'Ej: 3815551234'}) class Meta: - model = User - fields = ['username', 'email', 'password1', 'password2'] + model = NuevoUsuario + fields = ['username', 'email', 'password1', 'password2', 'fecha_nacimiento', 'telefono'] # Saca los mensajes de ayuda help_texts = {k:"" for k in fields} + #------------------------------------------------------------------------------------------------------------------------------------------------------------ # FORMULARIO CREADO PARA EDICION DE USUARIO class UserEditForm(UserCreationForm): # Obligatorios - email = forms.EmailField(label="Ingrese su email:") - password1 = forms.CharField(label='Contraseña', widget=forms.PasswordInput) - password2 = forms.CharField( - label='Repetir la contraseña', widget=forms.PasswordInput) + email = forms.EmailField(label="Correo electrónico") + password1 = forms.CharField(label='Contraseña', widget=forms.PasswordInput, required=False) + password2 = forms.CharField(label='Confirmar contraseña', widget=forms.PasswordInput, required=False) + first_name = forms.CharField(label='Nombre') + last_name = forms.CharField(label='Apellido') + + # Campos personalizados de NuevoUsuario + fecha_nacimiento = forms.DateField(required=False, widget=forms.DateInput(attrs={'type': 'date'}), label='Fecha de nacimiento') + telefono = forms.CharField(required=False, max_length=50, label='Teléfono') - last_name = forms.CharField() - first_name = forms.CharField() + # Validacion de email unico en edicion + def clean_email(self): + email = self.cleaned_data.get('email') + # Excluye el email actual del usuario que esta editando + if NuevoUsuario.objects.filter(email=email).exclude(pk=self.instance.pk).exists(): + raise forms.ValidationError('Este correo ya está registrado') + return email.lower() class Meta: - model = User - fields = ['email', 'password1', 'password2', 'last_name', 'first_name'] \ No newline at end of file + model = NuevoUsuario + fields = ['email', 'last_name', 'first_name', 'fecha_nacimiento', 'telefono', 'password1', 'password2'] + labels = {'first_name': 'Nombre', 'last_name': 'Apellido', 'telefono': 'Teléfono'} + help_texts = {'password1': 'Dejar en blanco para mantener la contraseña actual', + 'fecha_nacimiento': 'Formato: DD/MM/AAAA'} \ No newline at end of file diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py index 5b2f3b0..768cfdc 100644 --- a/app/migrations/0001_initial.py +++ b/app/migrations/0001_initial.py @@ -1,6 +1,10 @@ -# Generated by Django 4.0.1 on 2025-07-12 15:44 +# Generated by Django 4.0.1 on 2025-08-05 17:30 +import django.contrib.auth.models +import django.contrib.auth.validators +import django.core.validators from django.db import migrations, models +import django.utils.timezone class Migration(migrations.Migration): @@ -8,6 +12,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ @@ -37,11 +42,39 @@ class Migration(migrations.Migration): name='Producto', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('articulo', models.CharField(max_length=50)), - ('seccion', models.CharField(max_length=50)), - ('descripcion', models.CharField(max_length=100)), - ('precio_unitario', models.IntegerField()), - ('imagen', models.ImageField(blank=True, null=True, upload_to='')), + ('articulo', models.CharField(max_length=50, verbose_name='Película')), + ('seccion', models.CharField(choices=[('ACC', 'Acción'), ('COM', 'Comedia'), ('DRA', 'Drama'), ('TER', 'Terror'), ('SCI', 'Ciencia Ficción'), ('FAN', 'Fantasía'), ('ROM', 'Romance'), ('ANI', 'Animación')], max_length=3, verbose_name='Género')), + ('descripcion', models.TextField(verbose_name='Reseña')), + ('precio_unitario', models.IntegerField(help_text='Puntaje de 1 a 5 estrellas', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)], verbose_name='Puntaje (1-5)')), + ('imagen', models.ImageField(blank=True, null=True, upload_to='', verbose_name='Portada')), + ], + ), + migrations.CreateModel( + name='NuevoUsuario', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('fecha_nacimiento', models.DateField(blank=True, null=True)), + ('telefono', models.CharField(blank=True, max_length=50, null=True)), + ('email', models.EmailField(max_length=254, unique=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), ], ), ] diff --git a/app/migrations/0002_delete_cliente.py b/app/migrations/0002_delete_cliente.py new file mode 100644 index 0000000..af45d7c --- /dev/null +++ b/app/migrations/0002_delete_cliente.py @@ -0,0 +1,16 @@ +# Generated by Django 4.0.1 on 2025-08-05 18:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0001_initial'), + ] + + operations = [ + migrations.DeleteModel( + name='Cliente', + ), + ] diff --git a/app/migrations/0003_producto_autor.py b/app/migrations/0003_producto_autor.py new file mode 100644 index 0000000..b663e8d --- /dev/null +++ b/app/migrations/0003_producto_autor.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2.5 on 2025-08-09 21:54 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0002_delete_cliente'), + ] + + operations = [ + migrations.AddField( + model_name='producto', + name='autor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/app/migrations/0004_comentario.py b/app/migrations/0004_comentario.py new file mode 100644 index 0000000..49f08db --- /dev/null +++ b/app/migrations/0004_comentario.py @@ -0,0 +1,32 @@ +# Generated by Django 5.2.5 on 2025-08-11 19:04 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0003_producto_autor'), + ] + + operations = [ + migrations.CreateModel( + name='Comentario', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('texto', models.TextField(max_length=1000, verbose_name='Texto del comentario')), + ('fecha_creacion', models.DateTimeField(auto_now_add=True, verbose_name='Fecha de creación')), + ('fecha_actualizacion', models.DateTimeField(auto_now=True, verbose_name='Última actualización')), + ('activo', models.BooleanField(default=True, verbose_name='¿Activo?')), + ('respuesta_a', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.comentario', verbose_name='Respuesta a')), + ('usuario', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Usuario')), + ], + options={ + 'verbose_name': 'Comentario', + 'verbose_name_plural': 'Comentarios', + 'ordering': ['-fecha_creacion'], + }, + ), + ] diff --git a/app/migrations/0005_comentario_producto.py b/app/migrations/0005_comentario_producto.py new file mode 100644 index 0000000..f8d1589 --- /dev/null +++ b/app/migrations/0005_comentario_producto.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.5 on 2025-08-11 19:37 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0004_comentario'), + ] + + operations = [ + migrations.AddField( + model_name='comentario', + name='producto', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comentarios', to='app.producto'), + ), + ] diff --git a/app/migrations/__pycache__/0001_initial.cpython-311.pyc b/app/migrations/__pycache__/0001_initial.cpython-311.pyc deleted file mode 100644 index 0aaa19f..0000000 Binary files a/app/migrations/__pycache__/0001_initial.cpython-311.pyc and /dev/null differ diff --git a/app/migrations/__pycache__/__init__.cpython-311.pyc b/app/migrations/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 9a4216d..0000000 Binary files a/app/migrations/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/app/models.py b/app/models.py index 37c3dda..4dd6780 100644 --- a/app/models.py +++ b/app/models.py @@ -1,21 +1,40 @@ from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator +from django.contrib.auth.models import AbstractUser +from django.urls import reverse +from django.utils import timezone +from django.conf import settings # Create your models here. # Clase Cliente -class Cliente(models.Model): - nombre = models.CharField(max_length=50) - apellido = models.CharField(max_length=50) - direccion = models.CharField(max_length=100) - email = models.EmailField() - telefono = models.CharField(max_length=30) +# class Cliente(models.Model): +# nombre = models.CharField(max_length=50) +# apellido = models.CharField(max_length=50) +# direccion = models.CharField(max_length=100) +# email = models.EmailField() +# telefono = models.CharField(max_length=30) - def __str__(self) -> str: - return f'Nombre: {self.nombre} | Apellido: {self.apellido} | Dirección: {self.direccion} | email: {self.email} | telefono: {self.telefono}' +# def __str__(self) -> str: +# return f'Nombre: {self.nombre} | Apellido: {self.apellido} | Dirección: {self.direccion} | email: {self.email} | telefono: {self.telefono}' #------------------------------------------------------------------------------------------------------------------------------------------------------------ +# Registro de Usuario + +class NuevoUsuario(AbstractUser): + fecha_nacimiento = models.DateField(blank=True, null=True) + telefono = models.CharField(max_length=50, blank=True, null=True) + email = models.EmailField(unique=True) + + def get_absolute_url(self): + return reverse('index') + + def __str__(self): + return self.username + +#------------------------------------------------------------------------------------------------------------------------------------------------------------ + # Clase Producto class Producto(models.Model): GENERO_CHOICES = [ @@ -38,6 +57,12 @@ class Producto(models.Model): help_text="Puntaje de 1 a 5 estrellas" ) imagen = models.ImageField('Portada', null=True, blank=True) + autor = models.ForeignKey(NuevoUsuario, on_delete=models.CASCADE, null=True, blank=True) + + def save(self, *args, **kwargs): + if not self.autor and hasattr(self, 'request_user'): + self.autor = self.request_user + super().save(*args, **kwargs) def __str__(self) -> str: return f'{self.articulo} ({self.get_seccion_display()}) - {self.precio_unitario}★' @@ -55,3 +80,21 @@ def __str__(self) -> str: return f'Mensaje - Asunto: {self.asunto} | Mensaje: {self.mensaje}' #------------------------------------------------------------------------------------------------------------------------------------------------------------ + +# Clase comentario +class Comentario(models.Model): + usuario = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True, verbose_name='Usuario') + texto = models.TextField(max_length=1000, verbose_name='Texto del comentario') + fecha_creacion = models.DateTimeField(auto_now_add=True, verbose_name='Fecha de creación') + fecha_actualizacion = models.DateTimeField(auto_now=True, verbose_name='Última actualización') + activo = models.BooleanField(default=True, verbose_name='¿Activo?') + respuesta_a = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='Respuesta a') + producto = models.ForeignKey('Producto', on_delete=models.CASCADE, null=True, blank=True, related_name='comentarios') + + def __str__(self): + return f'Comentario de {self.usuario.username if self.usuario else 'Anónimo'} - {self.texto[:50]}...' + + class Meta: + ordering = ['-fecha_creacion'] + verbose_name = 'Comentario' + verbose_name_plural = 'Comentarios' \ No newline at end of file diff --git a/app/tests.py b/app/tests.py index 7ce503c..4929020 100644 --- a/app/tests.py +++ b/app/tests.py @@ -1,3 +1,2 @@ -from django.test import TestCase # Create your tests here. diff --git a/app/urls.py b/app/urls.py index 1c2acfc..8d41ccc 100644 --- a/app/urls.py +++ b/app/urls.py @@ -8,7 +8,7 @@ urlpatterns = [ - path('clientes/',ClientListView.as_view(),name="clientes"), + # path('clientes/',ClientListView.as_view(),name="clientes"), path('productos/',ProductListView.as_view(),name="productos"), path('buscar_producto/',SearchProductView,name="buscar_producto"), path('producto_buscado/',ToFindProductView,name='producto_buscado'), @@ -17,10 +17,11 @@ path('/modificar/',ProductUpdateView.as_view(),name="modificar_producto"), path('/delete/',ProductDeleteView.as_view(),name="borrar_producto"), path('login',login_request,name="login"), - path('registro',register,name="registro"), + path('registro/', RegistrarUsuario.as_view(), name="registro"), path('editar_perfil', EditProfile,name="editar_perfil"), - path('logout',LogoutView.as_view(),name="logout"), - path('nosotros/',AboutUsView,name="nosotros"), + path('logout/', LogoutView.as_view(next_page='app:login'), name="logout"), + path('nosotros/',AboutUsView,name="nosotros"), + path('pelicula//comentar/', agregar_comentario, name='agregar_comentario'), ] diff --git a/app/views.py b/app/views.py index 38aa23f..edeb19e 100644 --- a/app/views.py +++ b/app/views.py @@ -1,14 +1,20 @@ from django.http import HttpResponse -from django.shortcuts import redirect, render -from django.views.generic import View, UpdateView, DeleteView,ListView -from .forms import ProductCreateForms,ContactCreateForms,UserRegisterForm,UserEditForm -from .models import Cliente,Producto,Contacto -from django.urls import reverse_lazy +from django.shortcuts import redirect, render, get_object_or_404 +from django.views.generic import View, UpdateView, DeleteView, ListView, CreateView +from app.forms import ProductCreateForms, ContactCreateForms, UserRegisterForm, UserEditForm +from app.models import Producto, Contacto, Comentario +from django.urls import reverse, reverse_lazy +from django.contrib import messages +from django.contrib.auth.models import Group +from django.contrib.auth.views import LoginView, LogoutView, PasswordResetView, PasswordResetDoneView #AGREGADOS PARA EL LOGIN/LOGOUT from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth import login,authenticate from django.contrib.auth.decorators import login_required +from django.utils.decorators import method_decorator +from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.exceptions import PermissionDenied #AGREGADO PARA EL ENVIO DE MAIL from django.core.mail import EmailMessage @@ -16,27 +22,70 @@ #---------------------------------------------------------------------------------------------- +## VISTA PARA REGISTRAR USUARIO +class RegistrarUsuario(CreateView): + template_name = 'registro.html' + form_class = UserRegisterForm + success_url = reverse_lazy('app:login') + + def form_valid(self, form): + response = super().form_valid(form) + + try: + group = Group.objects.get(name='Registrado') + self.object.groups.add(group) + messages.success(self.request, 'Registro exitoso. Puede iniciar sesión.') + + except Group.DoesNotExist: + messages.warning(self.request, 'Registro exitoso, pero no se pudo asignar grupo.') + return redirect(self.get_success_url()) + +#---------------------------------------------------------------------------------------------- + +## VISTA PARA LOGOUT +class LogoutView(LogoutView): + template_name = 'logout.html' + + def dispatch(self, request, *args, **kwargs): + response = super().dispatch(request, *args, **kwargs) + messages.success(request, 'Logout exitoso') + return response + +#---------------------------------------------------------------------------------------------- + ## VISTAS DE CLIENTE # Vista de Clase que lista los clientes -class ClientListView(ListView): - def get(self,request): - clientes = Cliente.objects.all() - context = { - 'clientes':clientes - } - return render(request,'clientes_list.html',context) +# class ClientListView(ListView): +# def get(self,request): +# clientes = Cliente.objects.all() +# context = { +# 'clientes':clientes +# } +# return render(request,'clientes_list.html',context) #---------------------------------------------------------------------------------------------- ## VISTAS DE PRODUCTO # Vista de Clase que lista los productos class ProductListView(ListView): - def get(self,request,*args,**kwargs): - productos = Producto.objects.all() - context = { - 'productos':productos - } - return render(request,'productos_list.html',context) + model = Producto + template_name = 'productos_list.html' + context_object_name = 'productos' + + def get_queryset(self): + queryset = super().get_queryset() + genero_seleccionado = self.request.GET.get('genero') + + if genero_seleccionado: + queryset = queryset.filter(seccion=genero_seleccionado) + + return queryset + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['generos'] = Producto.GENERO_CHOICES + context['genero_seleccionado'] = self.request.GET.get('genero') + return context # Vista para realizar búsqueda de productos (por articulo) @@ -63,31 +112,33 @@ def ToFindProductView(request): # Vista de clase - permite crear un nuevo Producto +@method_decorator(login_required, name='dispatch') class ProductCreateView(View): - def get(self,request,*args,**kwargs): - #parte creada para llamar el contenido del en forms.py (donde generamos producto) + def get(self, request, *args, **kwargs): form = ProductCreateForms() - context={ - 'form':form + context = { + 'form': form } - return render(request,'crear_producto.html',context) - def post(self,request): - if request.method=="POST": - form = ProductCreateForms(request.POST,request.FILES) + return render(request, 'crear_producto.html', context) + + def post(self, request): + if request.method == "POST": + form = ProductCreateForms(request.POST, request.FILES) if form.is_valid(): - articulo = form.cleaned_data.get('articulo') - seccion = form.cleaned_data.get('seccion') - descripcion = form.cleaned_data.get('descripcion') - precio_unitario = form.cleaned_data.get('precio_unitario') - imagen = form.cleaned_data.get('imagen') - - p, created = Producto.objects.get_or_create(articulo=articulo,seccion=seccion,descripcion=descripcion,precio_unitario=precio_unitario,imagen=imagen) - p.save() + # Crear instancia sin guardar aún + producto = form.save(commit=False) + # Asignar el usuario actual como autor + producto.autor = request.user + # Ahora guardar en la base de datos + producto.save() + + messages.success(request, 'Post creado exitosamente!') return redirect('app:productos') context = { + 'form': form } - return render(request,'crear_producto.html',context) + return render(request, 'crear_producto.html', context) # Vista de clase que permite modificar (update) nuestro producto @@ -96,10 +147,15 @@ class ProductUpdateView(UpdateView): fields = ['articulo','seccion','descripcion','precio_unitario','imagen'] template_name = 'modifcar_producto.html' + def dispatch(self, request, *args, **kwargs): + producto = self.get_object() + if not (request.user.is_superuser or request.user == producto.autor): + raise PermissionDenied('No tienes permiso para editar este post') + return super().dispatch(request, *args, **kwargs) + def get_success_url(self): - pk = self.kwargs['pk'] return reverse_lazy('app:productos') - + # Vista de clase que nos permite eliminar un producto class ProductDeleteView(DeleteView): @@ -107,16 +163,22 @@ class ProductDeleteView(DeleteView): template_name = 'borrar_producto.html' success_url = reverse_lazy('app:productos') + def dispatch(self, request, *args, **kwargs): + producto = self.get_object() + if not (request.user.is_superuser or request.user == producto.autor): + raise PermissionDenied('No tienes permiso para eliminar este post') + return super().dispatch(request, *args, **kwargs) + #---------------------------------------------------------------------------------------------- ## VISTAS DE CONTACTO # Vista de clase - permite crear un nuevo Contacto class ContactCreateView(View): def get(self,request,*args,**kwargs): - #parte creada para llamar el contenido en forms.py + #parte creada para llamar el contenido en forms.py form = ContactCreateForms() context={ - 'form':form + 'form':form } return render(request,'contacto.html',context) def post(self,request): @@ -130,33 +192,33 @@ def post(self,request): mensaje = form.cleaned_data.get('mensaje') #AGREGADO PARA ENVIO DE MAIL - asunto_mail_usuario = 'contacto - lambda3d' + asunto_mail_usuario = 'contacto - lambda3d' mensaje_a_usuario = """ Gracias por contactarte con lambda-3D impresiones. En breve nos comunicaremos para asesorarte en lo que necesites. Saludos! """ - contenido_mail_usuario = """ + contenido_mail_usuario = """

Hola %s

-

%s

+

%s

-
nuestro mail: ✉️ %s
+
nuestro mail: ✉️ %s
""" % (nombre, mensaje_a_usuario, settings.EMAIL_HOST_USER) mail_a_usuario = EmailMessage(asunto_mail_usuario, contenido_mail_usuario, to=[email]) - mail_a_usuario.content_subtype = "html" # para heredar atributos de formato HTML - + mail_a_usuario.content_subtype = "html" # para heredar atributos de formato HTML + # Si la cuenta de mail HOST se encuetra configurada... try: mail_a_usuario.send() msg_alerta = "" - # caso contrario, enviará el mensaje... + # caso contrario, enviará el mensaje... except: msg_alerta = """ Para que la página envíe mail al usuario, se debe configurar cuenta de mail HOST en 'settings.py'. @@ -164,7 +226,7 @@ def post(self,request): 'EMAIL_HOST_USER = cuenta de mail válida.'\n 'EMAIL_HOST_PASSWORD = contraseña de la cuenta.' """ - + c, created = Contacto.objects.get_or_create(nombre=nombre,email=email,telefono=telefono,asunto=asunto,mensaje=mensaje) c.save() msg = {'mensaje': f'Gracias "{nombre}". Hemos recibido tu mensaje!!. \n{msg_alerta}'} @@ -178,7 +240,7 @@ def post(self,request): #------------------------------------------------------------------------------ # VISTAS DE LOGIN -# Vista de función (def) - Login usuarios +# Vista de función (def) - Login usuarios def login_request(request): if request.method == "POST": form = AuthenticationForm(request,data = request.POST) @@ -196,30 +258,30 @@ def login_request(request): else: msg = {"mensaje": "Error, datos incorrectos."} return render(request,"resultado_login.html",msg) - + else: msg = {"mensaje": "Error, datos incorrectos."} return render(request,"resultado_login.html",msg) - + form = AuthenticationForm() return render(request,"login.html",{'form':form}) # Vista para registro de usuario (crear nuevo) -def register(request): - if request.method == "POST": - form = UserRegisterForm(request.POST) - if form.is_valid(): - username = form.cleaned_data['username'] - form.save() - msg = {'mensaje': f'Usuario "{username}" creado con éxito!!'} - return render(request,"resultado_registro.html",msg) - - else: - form = UserRegisterForm() - - return render(request,'registro.html',{'form':form}) +# def register(request): +# if request.method == "POST": +# form = UserRegisterForm(request.POST) +# if form.is_valid(): +# username = form.cleaned_data['username'] +# form.save() +# msg = {'mensaje': f'Usuario "{username}" creado con éxito!!'} +# return render(request,"resultado_registro.html",msg) + +# else: +# form = UserRegisterForm() + +# return render(request,'registro.html',{'form':form}) # Vista de editar el perfil @@ -260,4 +322,21 @@ def AboutUsView(request): context = { } - return render(request,"nosotros.html",context) \ No newline at end of file + return render(request,"nosotros.html",context) + + +#------------------------------------------------------------------------------ + +# Vista para comentario +@login_required +def agregar_comentario(request, producto_id): + producto = get_object_or_404(Producto, id = producto_id) + if request.method == 'POST': + texto = request.POST.get('texto') + Comentario.objects.create( + usuario = request.user, + texto = texto, + producto = producto + ) + return redirect('app:productos') + return render(request, 'agregar_comentario.html', {'producto' : producto}) \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..4441dde --- /dev/null +++ b/compose.yaml @@ -0,0 +1,50 @@ +# Define the services (containers) for the application +services: + # The Django web application service + webapp: + # Build the image from the Dockerfile in the current directory + build: + context: . + dockerfile: Dockerfile + volumes: + - .:/app + ports: + # Map port 8000 on the host to port 8000 in the container + - '8000:8000' + env_file: + - ./.env + restart: always + depends_on: + # Define a dependency on the 'db' service + db-service: + # For production, wait until the db service is fully healthy + # before starting the webapp. This prevents connection errors on startup. + condition: service_healthy + + # The MySQL database service + db-service: + # Use the official MySQL 8.0 image + image: mysql:8.0.43 + # Always restart the container if it stops, unless it was manually stopped + restart: always + env_file: + # Load database credentials from the.env file + - ./.env + volumes: + # Mount a named volume to persist database data across container restarts + - mysql_data:/var/lib/mysql + # For development, you might expose the port to connect with a local GUI client. + # For production, it's more secure to not expose the database port publicly. + ports: + - '3306:3306' + healthcheck: + # Define a health check to ensure the MySQL server is ready + test: + # Use the MySQL client to check if the server is up and running + ['CMD', 'mysqladmin', 'ping', '-h', 'localhost'] + timeout: 1s + retries: 10 + +# Define the named volumes used by the services +volumes: + mysql_data: diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 6b04bdb..0000000 Binary files a/core/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/core/__pycache__/settings.cpython-311.pyc b/core/__pycache__/settings.cpython-311.pyc deleted file mode 100644 index e324629..0000000 Binary files a/core/__pycache__/settings.cpython-311.pyc and /dev/null differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc deleted file mode 100644 index 0452805..0000000 Binary files a/core/__pycache__/urls.cpython-311.pyc and /dev/null differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc deleted file mode 100644 index f468e02..0000000 Binary files a/core/__pycache__/views.cpython-311.pyc and /dev/null differ diff --git a/core/__pycache__/wsgi.cpython-311.pyc b/core/__pycache__/wsgi.cpython-311.pyc deleted file mode 100644 index 35438e2..0000000 Binary files a/core/__pycache__/wsgi.cpython-311.pyc and /dev/null differ diff --git a/core/asgi.py b/core/asgi.py index 04e6d00..fbcffad 100644 --- a/core/asgi.py +++ b/core/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings.prod') application = get_asgi_application() diff --git a/core/settings/__init__.py b/core/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/settings.py b/core/settings/dev.py similarity index 92% rename from core/settings.py rename to core/settings/dev.py index e2e1bdb..e691fc6 100644 --- a/core/settings.py +++ b/core/settings/dev.py @@ -18,19 +18,18 @@ environ.Env.read_env() # Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - +BASE_DIR = Path(__file__).resolve().parent.parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY='django-insecure5ai70emuksy&%!7-2lbs=d438o*kf^7*au%4%ypn4&33ex-a-l' +SECRET_KEY = 'django-insecure-4!@#%&*()_+abc1234567890' # SECURITY WARNING: don't run with debug turned on in production! -DEBUG=True +DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['*'] @@ -45,8 +44,11 @@ 'app', 'fontawesomefree', 'crispy_forms', + 'crispy_bootstrap4', ] +AUTH_USER_MODEL = 'app.NuevoUsuario' + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -123,6 +125,7 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ +STATIC_ROOT = BASE_DIR / "staticfiles" STATIC_URL = 'static/' MEDIA_URL = '/images/' @@ -138,7 +141,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - +CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap4' # Crispy Forms Template CRISPY_TEMPLATE_PACK = 'bootstrap4' @@ -149,3 +152,6 @@ EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') EMAIL_USE_TLS = True + + +LOGIN_URL = 'app:login' \ No newline at end of file diff --git a/core/settings/prod.py b/core/settings/prod.py new file mode 100644 index 0000000..9b3b880 --- /dev/null +++ b/core/settings/prod.py @@ -0,0 +1,8 @@ +from dev import * # noqa: F401, F403 +print("Using production settings for WSGI application.") +# This file is used to override settings for production. +# Ensure that sensitive information is not hardcoded here. + +# SECURITY WARNING: keep the secret key used in production secret! + +DEBUG = False diff --git a/core/wsgi.py b/core/wsgi.py index 08cbd4c..8b47661 100644 --- a/core/wsgi.py +++ b/core/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings.prod') application = get_wsgi_application() diff --git a/db.sqlite3 b/db.sqlite3 index fa4e1aa..7534ae6 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/entorno/Scripts/Activate.ps1 b/entorno/Scripts/Activate.ps1 new file mode 100644 index 0000000..b63e7b7 --- /dev/null +++ b/entorno/Scripts/Activate.ps1 @@ -0,0 +1,502 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" + +# SIG # Begin signature block +# MIIvIwYJKoZIhvcNAQcCoIIvFDCCLxACAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBnL745ElCYk8vk +# dBtMuQhLeWJ3ZGfzKW4DHCYzAn+QB6CCE8MwggWQMIIDeKADAgECAhAFmxtXno4h +# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK +# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV +# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z +# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ +# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 +# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z +# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ +# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s +# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL +# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb +# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3 +# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c +# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx +# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0 +# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL +# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud +# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf +# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk +# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS +# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK +# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB +# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp +# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg +# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri +# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7 +# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5 +# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3 +# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H +# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G +# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ +# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 +# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla +# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE +# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz +# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C +# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce +# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da +# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T +# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA +# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh +# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM +# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z +# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05 +# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY +# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP +# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T +# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD +# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG +# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY +# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj +# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV +# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU +# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN +# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry +# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL +# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf +# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh +# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh +# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV +# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j +# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH +# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC +# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l +# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW +# eE4wggd3MIIFX6ADAgECAhAHHxQbizANJfMU6yMM0NHdMA0GCSqGSIb3DQEBCwUA +# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE +# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz +# ODQgMjAyMSBDQTEwHhcNMjIwMTE3MDAwMDAwWhcNMjUwMTE1MjM1OTU5WjB8MQsw +# CQYDVQQGEwJVUzEPMA0GA1UECBMGT3JlZ29uMRIwEAYDVQQHEwlCZWF2ZXJ0b24x +# IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMSMwIQYDVQQDExpQ +# eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +# ADCCAgoCggIBAKgc0BTT+iKbtK6f2mr9pNMUTcAJxKdsuOiSYgDFfwhjQy89koM7 +# uP+QV/gwx8MzEt3c9tLJvDccVWQ8H7mVsk/K+X+IufBLCgUi0GGAZUegEAeRlSXx +# xhYScr818ma8EvGIZdiSOhqjYc4KnfgfIS4RLtZSrDFG2tN16yS8skFa3IHyvWdb +# D9PvZ4iYNAS4pjYDRjT/9uzPZ4Pan+53xZIcDgjiTwOh8VGuppxcia6a7xCyKoOA +# GjvCyQsj5223v1/Ig7Dp9mGI+nh1E3IwmyTIIuVHyK6Lqu352diDY+iCMpk9Zanm +# SjmB+GMVs+H/gOiofjjtf6oz0ki3rb7sQ8fTnonIL9dyGTJ0ZFYKeb6BLA66d2GA +# LwxZhLe5WH4Np9HcyXHACkppsE6ynYjTOd7+jN1PRJahN1oERzTzEiV6nCO1M3U1 +# HbPTGyq52IMFSBM2/07WTJSbOeXjvYR7aUxK9/ZkJiacl2iZI7IWe7JKhHohqKuc +# eQNyOzxTakLcRkzynvIrk33R9YVqtB4L6wtFxhUjvDnQg16xot2KVPdfyPAWd81w +# tZADmrUtsZ9qG79x1hBdyOl4vUtVPECuyhCxaw+faVjumapPUnwo8ygflJJ74J+B +# Yxf6UuD7m8yzsfXWkdv52DjL74TxzuFTLHPyARWCSCAbzn3ZIly+qIqDAgMBAAGj +# ggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfROQjAdBgNVHQ4E +# FgQUt/1Teh2XDuUj2WW3siYWJgkZHA8wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM +# MAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRp +# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI +# QTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20v +# RGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0Ex +# LmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8v +# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUF +# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6 +# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWdu +# aW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZI +# hvcNAQELBQADggIBABxv4AeV/5ltkELHSC63fXAFYS5tadcWTiNc2rskrNLrfH1N +# s0vgSZFoQxYBFKI159E8oQQ1SKbTEubZ/B9kmHPhprHya08+VVzxC88pOEvz68nA +# 82oEM09584aILqYmj8Pj7h/kmZNzuEL7WiwFa/U1hX+XiWfLIJQsAHBla0i7QRF2 +# de8/VSF0XXFa2kBQ6aiTsiLyKPNbaNtbcucaUdn6vVUS5izWOXM95BSkFSKdE45O +# q3FForNJXjBvSCpwcP36WklaHL+aHu1upIhCTUkzTHMh8b86WmjRUqbrnvdyR2yd +# I5l1OqcMBjkpPpIV6wcc+KY/RH2xvVuuoHjlUjwq2bHiNoX+W1scCpnA8YTs2d50 +# jDHUgwUo+ciwpffH0Riq132NFmrH3r67VaN3TuBxjI8SIZM58WEDkbeoriDk3hxU +# 8ZWV7b8AW6oyVBGfM06UgkfMb58h+tJPrFx8VI/WLq1dTqMfZOm5cuclMnUHs2uq +# rRNtnV8UfidPBL4ZHkTcClQbCoz0UbLhkiDvIS00Dn+BBcxw/TKqVL4Oaz3bkMSs +# M46LciTeucHY9ExRVt3zy7i149sd+F4QozPqn7FrSVHXmem3r7bjyHTxOgqxRCVa +# 18Vtx7P/8bYSBeS+WHCKcliFCecspusCDSlnRUjZwyPdP0VHxaZg2unjHY3rMYIa +# tjCCGrICAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu +# Yy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJT +# QTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAHHxQbizANJfMU6yMM0NHdMA0GCWCGSAFl +# AwQCAQUAoIHIMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC +# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBnAZ6P7YvTwq0fbF62 +# o7E75R0LxsW5OtyYiFESQckLhjBcBgorBgEEAYI3AgEMMU4wTKBGgEQAQgB1AGkA +# bAB0ADoAIABSAGUAbABlAGEAcwBlAF8AdgAzAC4AMQAyAC4ANABfADIAMAAyADQA +# MAA2ADAANgAuADAAMaECgAAwDQYJKoZIhvcNAQEBBQAEggIAV29hYhi09QNyGtav +# HZIo33y/iqXsIa4o88S5gzBa7Nnkwra0QLitSjvRfVbcFvq54Id+VIn00di4Nde0 +# maAUKPGXtTQL48esG/F/TLDOWd/jb9qCYHyNZYpJjKdXqI8IbyG6Pl05IMSas7wX +# DHsK19ZEGuGrmKCAxh6JbFXADgeUbftg3i9UxpMnfSugZjjdKIdyVWlzUnpYkKuI +# fpafwvNHfIYzfxOeV9CWsdqe34D6fRrEs8ZDEZSQl+Mw9aGaT39vuryFE1iKOzj0 +# uqrX/wN/wwu8oLWNC7JWE8SDG3eD0QLy+x7zEnlPkWsRV9nGOgrP9Khge0LgL+jP +# Km8iDs7fSGEOB/7PPxAl8yshEULOZAhBhcsGeGs+kQrVzlqZ9WlrU1Z1cylpLWzX +# Kkvs2DXD+zrplhpiVv6Gnn3YMBr4BKf0mXESTX9/BzIwvxlkhpv/BT0OWwrDlgPM +# hNj8jA5r2/WSqCg15DYjJ0RlnCerC/ORhSbs7v/HjpmH3DhaICJF7tdyFSIFXgNV +# W0GyQJMulQDEPd2+o+PNyAPElvGC3SYTjVnRLPcJTGhAt+VuHfnMG4HNkmyeU+nk +# OAMShxEax6NLeRsjKqqABUgZb2g4FSmXzHy7HgQOPmCQMv8xH4m8u992YMLyxh5U +# gGRUOUiAhrHXNZ6wG6T52NGQppehghc/MIIXOwYKKwYBBAGCNwMDATGCFyswghcn +# BgkqhkiG9w0BBwKgghcYMIIXFAIBAzEPMA0GCWCGSAFlAwQCAQUAMHcGCyqGSIb3 +# DQEJEAEEoGgEZjBkAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQg+eJt +# Pwl5Hz89rrpf2qbsjNAUNlBq9SGjVuw+Erci2HcCEDPjoeI//+uRP30fqUoeHIAY +# DzIwMjQwNjA2MTk1MDE0WqCCEwkwggbCMIIEqqADAgECAhAFRK/zlJ0IOaa/2z9f +# 5WEWMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp +# Q2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2 +# IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0MDAwMDAwWhcNMzQxMDEz +# MjM1OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x +# IDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIzMIICIjANBgkqhkiG9w0B +# AQEFAAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DPn9fl0uddoQ4J3C9Io5d6 +# OyqcZ9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX65RQjxwg6seaOy+WZuNp +# 52n+W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSkghraarrYO8pd3hkYhftF +# 6g1hbJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2SC1eRXWWdf7dEKEbg8G4 +# 5lKVtUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG7rSkIWRw69XloNpjsy7p +# Be6q9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k/XtzPjLuUjT71Lvr1KAs +# NJvj3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP4FhB+9ixLOFRr7StFQYU +# 6mIIE9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7Qv1zfe7dCv95NBB+plwK +# WEwAPoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOuprAbD3+yqG7HtSOKmYCaFx +# smxxrz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBxPZySkwS0aXAnDU+3tTbR +# yV8IpHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E42FEHypS34lCh8zrTioPL +# QHsCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG +# A1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG +# SAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4E +# FgQUpbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDov +# L2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1 +# NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUH +# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDov +# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNI +# QTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAgRrW3qCp +# tZgXvHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn48XtJoKKcS8Y3U623mzX +# 4WCcK+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S2sJAOJ9dyKAuJXglnSoF +# eoQpmLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedtQVyMadG5K8TGe8+c+nji +# kxp2oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6ubzBaRm6zxbygzc0brBB +# Jt3eWpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXhRsUo063nQwBw3syYnhmJ +# A+rUkTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC1o/jF5HRqsBV44a/rCcs +# QdCaM0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH5U81PAC9vpwqbHkB3NpE +# 5jreODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLKO+uagjVXKBbLafIymrLS +# 2Dq4sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc9bahuEMs305MfR5ocMB3 +# CtQC4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdEQcm4RtNsMnxYL2dHZeUb +# c7aZ+WssBkbvQR7w8F/g29mtkIBEr4AQQYowggauMIIElqADAgECAhAHNje3JFR8 +# 2Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK +# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV +# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0z +# NzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg +# SW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1 +# NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +# AQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI +# 82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9 +# xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ +# 3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5Emfv +# DqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDET +# qVcplicu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHe +# IhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jo +# n7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ +# 9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/T +# Xkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJg +# o1gJASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkw +# EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+e +# yG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQD +# AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEF +# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRw +# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy +# dDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln +# aUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg +# hkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGw +# GC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0 +# MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1D +# X+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw +# 1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY +# +/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0I +# SQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr +# 5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7y +# Rp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDop +# hrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/ +# AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMO +# Hds3OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkq +# hkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j +# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB +# c3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5 +# WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +# ExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJv +# b3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1K +# PDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2r +# snnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C +# 8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBf +# sXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +# QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8 +# rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaY +# dj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+ +# wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw +# ++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+N +# P8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7F +# wI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUw +# AwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAU +# Reuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEB +# BG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsG +# AQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1 +# cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp +# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAow +# CDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/ +# Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLe +# JLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE +# 1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9Hda +# XFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbO +# byMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYID +# djCCA3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu +# Yy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYg +# VGltZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0GCWCGSAFlAwQCAQUA +# oIHRMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcN +# MjQwNjA2MTk1MDE0WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBRm8CsywsLJD4Jd +# zqqKycZPGZzPQDAvBgkqhkiG9w0BCQQxIgQgUvswt0fWRoofHUAuTE0/8V9tLmHP +# zr/l2RTobZjBdqYwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQg0vbkbe10IszR1EBX +# aEE2b4KK2lWarjMWr00amtQMeCgwDQYJKoZIhvcNAQEBBQAEggIAc7/uG/S8kf0i +# 2kaDQkE8NSfiXCYfN7z/2sgi6RNrkipvs/KTWfEKuMbhu9qWjjusZFgywn/IrZqw +# td4Js1kmaN+HJ02t/HXYUCr+KTJye4mDaBGvaXXHllCqsK7bhsJxJYE0uYiL03MP +# g64jyu9WdJD3N26MW/DkO6HTVhYzRzjafbAKbrr8KCvaFan1KZERzYwbA8XVjm88 +# HOodLCA9h+91Iqdc+uSz3Sg9/+Ns4zCp4BonvnsPYTlWTitiB5cpfPe/v4lBvCNu +# x0ha6whvKMdRLZJgXsiDXo2NwwB55kkWEBwD3a1RnBJQmyJxFEGpSXOrhmdcEWPg +# fjoHVIfowKBrIgINdWJbvIu+pLzQRMkVhuJzB32xpiZBIvbzkPETYQMOmKIu40I9 +# 5EAL0xNakPxYiT3nTkncn6woLOhiOXFm7crE+gO4IzDNauYuT9Vfe36K1CqtuYSy +# JesLIey9Z81OQqOo6n2/lW110MKMEV2PkPU7YW/bYO2uKsZ3OAjUWr63nMT+M2wk +# VdUAcqm0QdZsELY75Q3ekRxHje/B9ePP4Q4RMQGOZvmgqdtEeFhsmRwufR4fzfqx +# WMttmOHelTd8Sc0sfA9B+1dxtiC9GFn3de5/o+T2s/jQn6eNp2hvlCqGV0iFzSQp +# InPTBa9Na/+5UeXZ3NBWRvarfZ62TVM= +# SIG # End signature block diff --git a/entorno/Scripts/activate b/entorno/Scripts/activate new file mode 100644 index 0000000..7669a35 --- /dev/null +++ b/entorno/Scripts/activate @@ -0,0 +1,70 @@ +# This file must be used with "source bin/activate" *from bash* +# You cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # Call hash to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + hash -r 2> /dev/null + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +# on Windows, a path can contain colons and backslashes and has to be converted: +if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + export VIRTUAL_ENV=$(cygpath "C:\Users\MARCE\OneDrive\Escritorio\INFORMATORIO\Etapa 2\proyectoFinalDjango\entorno") +else + # use the path as-is + export VIRTUAL_ENV="C:\Users\MARCE\OneDrive\Escritorio\INFORMATORIO\Etapa 2\proyectoFinalDjango\entorno" +fi + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/Scripts:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1="(entorno) ${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT="(entorno) " + export VIRTUAL_ENV_PROMPT +fi + +# Call hash to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +hash -r 2> /dev/null diff --git a/entorno/Scripts/activate.bat b/entorno/Scripts/activate.bat new file mode 100644 index 0000000..88a2ed0 --- /dev/null +++ b/entorno/Scripts/activate.bat @@ -0,0 +1,34 @@ +@echo off + +rem This file is UTF-8 encoded, so we need to update the current code page while executing it +for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do ( + set _OLD_CODEPAGE=%%a +) +if defined _OLD_CODEPAGE ( + "%SystemRoot%\System32\chcp.com" 65001 > nul +) + +set VIRTUAL_ENV=C:\Users\MARCE\OneDrive\Escritorio\INFORMATORIO\Etapa 2\proyectoFinalDjango\entorno + +if not defined PROMPT set PROMPT=$P$G + +if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT% +if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% + +set _OLD_VIRTUAL_PROMPT=%PROMPT% +set PROMPT=(entorno) %PROMPT% + +if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% +set PYTHONHOME= + +if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH% +if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH% + +set PATH=%VIRTUAL_ENV%\Scripts;%PATH% +set VIRTUAL_ENV_PROMPT=(entorno) + +:END +if defined _OLD_CODEPAGE ( + "%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul + set _OLD_CODEPAGE= +) diff --git a/entorno/Scripts/deactivate.bat b/entorno/Scripts/deactivate.bat new file mode 100644 index 0000000..62a39a7 --- /dev/null +++ b/entorno/Scripts/deactivate.bat @@ -0,0 +1,22 @@ +@echo off + +if defined _OLD_VIRTUAL_PROMPT ( + set "PROMPT=%_OLD_VIRTUAL_PROMPT%" +) +set _OLD_VIRTUAL_PROMPT= + +if defined _OLD_VIRTUAL_PYTHONHOME ( + set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%" + set _OLD_VIRTUAL_PYTHONHOME= +) + +if defined _OLD_VIRTUAL_PATH ( + set "PATH=%_OLD_VIRTUAL_PATH%" +) + +set _OLD_VIRTUAL_PATH= + +set VIRTUAL_ENV= +set VIRTUAL_ENV_PROMPT= + +:END diff --git a/entorno/Scripts/django-admin.exe b/entorno/Scripts/django-admin.exe new file mode 100644 index 0000000..5bb68e2 Binary files /dev/null and b/entorno/Scripts/django-admin.exe differ diff --git a/entorno/Scripts/pip.exe b/entorno/Scripts/pip.exe new file mode 100644 index 0000000..73eadb5 Binary files /dev/null and b/entorno/Scripts/pip.exe differ diff --git a/entorno/Scripts/pip3.12.exe b/entorno/Scripts/pip3.12.exe new file mode 100644 index 0000000..73eadb5 Binary files /dev/null and b/entorno/Scripts/pip3.12.exe differ diff --git a/entorno/Scripts/pip3.exe b/entorno/Scripts/pip3.exe new file mode 100644 index 0000000..73eadb5 Binary files /dev/null and b/entorno/Scripts/pip3.exe differ diff --git a/entorno/Scripts/python.exe b/entorno/Scripts/python.exe new file mode 100644 index 0000000..53121ae Binary files /dev/null and b/entorno/Scripts/python.exe differ diff --git a/entorno/Scripts/pythonw.exe b/entorno/Scripts/pythonw.exe new file mode 100644 index 0000000..a09f6e9 Binary files /dev/null and b/entorno/Scripts/pythonw.exe differ diff --git a/entorno/Scripts/sqlformat.exe b/entorno/Scripts/sqlformat.exe new file mode 100644 index 0000000..9248b12 Binary files /dev/null and b/entorno/Scripts/sqlformat.exe differ diff --git a/entorno/pyvenv.cfg b/entorno/pyvenv.cfg new file mode 100644 index 0000000..1ee348f --- /dev/null +++ b/entorno/pyvenv.cfg @@ -0,0 +1,5 @@ +home = C:\Users\MARCE\AppData\Local\Programs\Python\Python312 +include-system-site-packages = false +version = 3.12.4 +executable = C:\Users\MARCE\AppData\Local\Programs\Python\Python312\python.exe +command = C:\Users\MARCE\AppData\Local\Programs\Python\Python312\python.exe -m venv C:\Users\MARCE\OneDrive\Escritorio\INFORMATORIO\Etapa 2\proyectoFinalDjango\entorno diff --git a/manage.py b/manage.py index f2a662c..7646113 100644 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings.dev') try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..db0cae1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[project] + dependencies = [ + "django-crispy-forms>=2.4", + "django-environ>=0.12.0", + "django>=5.2.4", + "fontawesomefree>=6.6.0", + "pillow>=11.3.0", +] + name = "proyectofinaldjango" + requires-python = ">=3.13" + version = "0.1.0" diff --git a/requirements.txt b/requirements.txt index 89ac537..57b72bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ django==4.0.1 django-environ==0.8.1 fontawesomefree==6.0.0 -Pillow==10.0.0 +Pillow==10.1.0 django_crispy_forms==1.14.0 diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh new file mode 100644 index 0000000..633dee0 --- /dev/null +++ b/scripts/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# entrypoing for docker container + +# This script is used to run the Django development server with uvicorn +# It sets the DJANGO_SETTINGS_MODULE environment variable to use the development settings +# and then starts the server. +# Set the settings module to development +export DJANGO_SETTINGS_MODULE=core.settings.dev + +# Run the Django development server with uvicorn +uv run manage.py runserver \ No newline at end of file diff --git a/static/images/Captura_de_pantalla_2025-06-05_111158.png b/static/images/Captura_de_pantalla_2025-06-05_111158.png new file mode 100644 index 0000000..b1bbdb3 Binary files /dev/null and b/static/images/Captura_de_pantalla_2025-06-05_111158.png differ diff --git a/static/images/Captura_de_pantalla_2025-06-11_161922.png b/static/images/Captura_de_pantalla_2025-06-11_161922.png new file mode 100644 index 0000000..86588c0 Binary files /dev/null and b/static/images/Captura_de_pantalla_2025-06-11_161922.png differ diff --git a/templates/agregar_comentario.html b/templates/agregar_comentario.html new file mode 100644 index 0000000..49b9810 --- /dev/null +++ b/templates/agregar_comentario.html @@ -0,0 +1,46 @@ +{% extends "padre.html" %} + +{% block loquecambia %} +
+
+
+ +
+ {% if producto.imagen %} + {{ producto.articulo }} + {% endif %} +
+
{{ producto.articulo }}
+

{{ producto.get_seccion_display }}

+
+
+ + +
+
+

Nuevo comentario

+
+
+
+ {% csrf_token %} +
+ + +
+
+ + Cancelar + + +
+
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/error_403.html b/templates/error_403.html new file mode 100644 index 0000000..7bdb2db --- /dev/null +++ b/templates/error_403.html @@ -0,0 +1,8 @@ +{% extends "padre.html" %} +{% block loquecambia %} +
+

Acceso Denegado

+

{{ exception }}

+ Volver al listado +
+{% endblock %} \ No newline at end of file diff --git a/templates/logout.html b/templates/logout.html new file mode 100644 index 0000000..2ac4961 --- /dev/null +++ b/templates/logout.html @@ -0,0 +1,32 @@ +{% extends "padre.html" %} +{% load crispy_forms_tags %} +{% block loquecambia %} + +
+
+
+
+
+

Cierre de sesión exitoso

+ + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + +

Has cerrado tu sesión correctamente. ¿Quiéres volver a iniciar sesión?

+ + Iniciar sesión nuevamente + + Ir a la página principal +
+
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/templates/padre.html b/templates/padre.html index 79f5914..752e70a 100644 --- a/templates/padre.html +++ b/templates/padre.html @@ -1,15 +1,19 @@ {% load static %} + - + @@ -17,102 +21,146 @@ - + - + +
- +
- {% block loquecambia %} + {% if messages and request.resolver_match.url_name != 'registro' %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + + {% block loquecambia %} {% endblock %}
-
-
- - - - - - -
-
-
- © 2025 Copyright: - INFORMATORIO -
+
+
+ + + + + + +
+
+
+ © 2025 Copyright: + INFORMATORIO +
- + - - - + + + - + - + + \ No newline at end of file diff --git a/templates/productos_list.html b/templates/productos_list.html index 55e886b..0c70254 100644 --- a/templates/productos_list.html +++ b/templates/productos_list.html @@ -1,21 +1,48 @@ {% extends "padre.html" %} {% block loquecambia %}
-

LISTADO DE PELÍCULAS

+ +

+ + {% if genero_seleccionado %} + PELÍCULAS DE {{ productos.0.get_seccion_display|upper }} + {% else %} + LISTADO DE PELÍCULAS + {% endif %} + +

+ - + + +
+ + Todas + + {% for genero in generos %} + + {{ genero.1 }} + + {% endfor %} +
+ +
{% for p in productos %}
{% if p.imagen %} - {{ p.articulo }} + {{ p.articulo }} {% else %} -
+
Sin portada
{% endif %} @@ -25,19 +52,51 @@
{{ p.articulo }}

{{ p.descripcion|truncatechars:100 }}

{% for i in "12345" %} - {% if forloop.counter <= p.precio_unitario %} - + {% if forloop.counter <= p.precio_unitario %} {% else %} - + {% endif %} - {% endfor %} - {{ p.precio_unitario }}/5 + {% endfor %} + {{ p.precio_unitario }}/5 +
+ + + + + + {% if p.comentario_set.all %} +
+
Comentarios:
+
    + {% for comentario in p.comentario_set.all|slice:":2" %} +
  • + {{ comentario.usuario.username }}: + {{ comentario.texto|truncatechars:50 }} +
    + {{ comentario.fecha_creacion|date:"d/m/Y" }} +
  • + {% endfor %} +
+ {% if p.comentario_set.count > 2 %} + Ver todos ({{ p.comentario_set.count }}) + {% endif %}
+ {% endif %}
diff --git a/templates/registro.html b/templates/registro.html index a07dea7..f4aece2 100644 --- a/templates/registro.html +++ b/templates/registro.html @@ -1,18 +1,28 @@ {% extends "padre.html" %} {% load crispy_forms_tags %} {% block loquecambia %} -
-
- {% csrf_token %} +
+
+
+
+

Registro de Usuario

- {{form|crispy}} + + {% csrf_token %} - + {{ form|crispy }} - + + +
+
+
- + {% endblock %} \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..9666b10 --- /dev/null +++ b/uv.lock @@ -0,0 +1,149 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "asgiref" +version = "3.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142", size = 36870, upload-time = "2025-07-08T09:07:43.344Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/3c/0464dcada90d5da0e71018c04a140ad6349558afb30b3051b4264cc5b965/asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c", size = 23790, upload-time = "2025-07-08T09:07:41.548Z" }, +] + +[[package]] +name = "django" +version = "5.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/7e/034f0f9fb10c029a02daaf44d364d6bf2eced8c73f0d38c69da359d26b01/django-5.2.4.tar.gz", hash = "sha256:a1228c384f8fa13eebc015196db7b3e08722c5058d4758d20cb287503a540d8f", size = 10831909, upload-time = "2025-07-02T18:47:39.19Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/ae/706965237a672434c8b520e89a818e8b047af94e9beb342d0bee405c26c7/django-5.2.4-py3-none-any.whl", hash = "sha256:60c35bd96201b10c6e7a78121bd0da51084733efa303cc19ead021ab179cef5e", size = 8302187, upload-time = "2025-07-02T18:47:35.373Z" }, +] + +[[package]] +name = "django-crispy-forms" +version = "2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/a1/ffd7b0e160296121d88e3e173165370000ee4de7328f5c4f4b266638dcd9/django_crispy_forms-2.4.tar.gz", hash = "sha256:915e1ffdeb2987d78b33fabfeff8e5203c8776aa910a3a659a2c514ca125f3bd", size = 278932, upload-time = "2025-04-13T07:25:00.176Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/ec/a25f81e56a674e63cf6c3dd8e36b1b3fecc238fecd6098504adc0cc61402/django_crispy_forms-2.4-py3-none-any.whl", hash = "sha256:5a4b99876cfb1bdd3e47727731b6d4197c51c0da502befbfbec6a93010b02030", size = 31446, upload-time = "2025-04-13T07:24:58.516Z" }, +] + +[[package]] +name = "django-environ" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/04/65d2521842c42f4716225f20d8443a50804920606aec018188bbee30a6b0/django_environ-0.12.0.tar.gz", hash = "sha256:227dc891453dd5bde769c3449cf4a74b6f2ee8f7ab2361c93a07068f4179041a", size = 56804, upload-time = "2025-01-13T17:03:37.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957, upload-time = "2025-01-13T17:03:32.918Z" }, +] + +[[package]] +name = "fontawesomefree" +version = "6.6.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/e9/d43f5133b73e7ef9047bda28daaa6905e00b7d39f093b547f7e78ee2fc40/fontawesomefree-6.6.0-py3-none-any.whl", hash = "sha256:599b574431c9bd92ed5fc054d1045a07c42335da36c17884f2b934755eef9089", size = 25645298, upload-time = "2024-07-16T18:35:44.818Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, +] + +[[package]] +name = "proyectofinaldjango" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "django" }, + { name = "django-crispy-forms" }, + { name = "django-environ" }, + { name = "fontawesomefree" }, + { name = "pillow" }, +] + +[package.metadata] +requires-dist = [ + { name = "django", specifier = ">=5.2.4" }, + { name = "django-crispy-forms", specifier = ">=2.4" }, + { name = "django-environ", specifier = ">=0.12.0" }, + { name = "fontawesomefree", specifier = ">=6.6.0" }, + { name = "pillow", specifier = ">=11.3.0" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +]