| @ -0,0 +1,3 @@ | |||||
| from django.contrib import admin | |||||
| # Register your models here. | |||||
| @ -0,0 +1,6 @@ | |||||
| from django.apps import AppConfig | |||||
| class AccountsConfig(AppConfig): | |||||
| default_auto_field = 'django.db.models.BigAutoField' | |||||
| name = 'accounts' | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.db import models | |||||
| # Create your models here. | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.test import TestCase | |||||
| # Create your tests here. | |||||
| @ -0,0 +1,9 @@ | |||||
| # accounts/urls.py | |||||
| from django.urls import path | |||||
| from .views import SignUpView | |||||
| urlpatterns = [ | |||||
| path("signup/", SignUpView.as_view(), name="signup"), | |||||
| ] | |||||
| @ -0,0 +1,10 @@ | |||||
| # accounts/views.py | |||||
| from django.contrib.auth.forms import UserCreationForm | |||||
| from django.urls import reverse_lazy | |||||
| from django.views.generic import CreateView | |||||
| class SignUpView(CreateView): | |||||
| form_class = UserCreationForm | |||||
| success_url = reverse_lazy("login") | |||||
| template_name = "registration/signup.html" | |||||
| @ -0,0 +1,16 @@ | |||||
| """ | |||||
| ASGI config for django_project project. | |||||
| It exposes the ASGI callable as a module-level variable named ``application``. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ | |||||
| """ | |||||
| import os | |||||
| from django.core.asgi import get_asgi_application | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') | |||||
| application = get_asgi_application() | |||||
| @ -0,0 +1,131 @@ | |||||
| """ | |||||
| Django settings for django_project project. | |||||
| Generated by 'django-admin startproject' using Django 5.1. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/topics/settings/ | |||||
| For the full list of settings and their values, see | |||||
| https://docs.djangoproject.com/en/5.1/ref/settings/ | |||||
| """ | |||||
| from pathlib import Path | |||||
| # Build paths inside the project like this: BASE_DIR / 'subdir'. | |||||
| BASE_DIR = Path(__file__).resolve().parent.parent | |||||
| # Quick-start development settings - unsuitable for production | |||||
| # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ | |||||
| # SECURITY WARNING: keep the secret key used in production secret! | |||||
| SECRET_KEY = 'django-insecure-0ji$fkweyrscq+#fg73b+him8zsw26$7!iaxvy^rpy^)h#=br$' | |||||
| # SECURITY WARNING: don't run with debug turned on in production! | |||||
| DEBUG = True | |||||
| ALLOWED_HOSTS = [] | |||||
| # Application definition | |||||
| INSTALLED_APPS = [ | |||||
| 'django.contrib.admin', | |||||
| 'django.contrib.auth', | |||||
| 'django.contrib.contenttypes', | |||||
| 'django.contrib.sessions', | |||||
| 'django.contrib.messages', | |||||
| 'django.contrib.staticfiles', | |||||
| 'accounts', | |||||
| ] | |||||
| MIDDLEWARE = [ | |||||
| 'django.middleware.security.SecurityMiddleware', | |||||
| 'django.contrib.sessions.middleware.SessionMiddleware', | |||||
| 'django.middleware.common.CommonMiddleware', | |||||
| 'django.middleware.csrf.CsrfViewMiddleware', | |||||
| 'django.contrib.auth.middleware.AuthenticationMiddleware', | |||||
| 'django.contrib.messages.middleware.MessageMiddleware', | |||||
| 'django.middleware.clickjacking.XFrameOptionsMiddleware', | |||||
| ] | |||||
| ROOT_URLCONF = 'django_project.urls' | |||||
| TEMPLATES = [ | |||||
| { | |||||
| 'BACKEND': 'django.template.backends.django.DjangoTemplates', | |||||
| "DIRS": [BASE_DIR / "templates"], # new | |||||
| 'APP_DIRS': True, | |||||
| 'OPTIONS': { | |||||
| 'context_processors': [ | |||||
| 'django.template.context_processors.debug', | |||||
| 'django.template.context_processors.request', | |||||
| 'django.contrib.auth.context_processors.auth', | |||||
| 'django.contrib.messages.context_processors.messages', | |||||
| ], | |||||
| }, | |||||
| }, | |||||
| ] | |||||
| WSGI_APPLICATION = 'django_project.wsgi.application' | |||||
| # Database | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#databases | |||||
| DATABASES = { | |||||
| 'default': { | |||||
| 'ENGINE': 'django.db.backends.sqlite3', | |||||
| 'NAME': BASE_DIR / 'db.sqlite3', | |||||
| } | |||||
| } | |||||
| # Password validation | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators | |||||
| AUTH_PASSWORD_VALIDATORS = [ | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', | |||||
| }, | |||||
| ] | |||||
| # Internationalization | |||||
| # https://docs.djangoproject.com/en/5.1/topics/i18n/ | |||||
| LANGUAGE_CODE = 'en-us' | |||||
| TIME_ZONE = 'UTC' | |||||
| USE_I18N = True | |||||
| USE_TZ = True | |||||
| # Static files (CSS, JavaScript, Images) | |||||
| # https://docs.djangoproject.com/en/5.1/howto/static-files/ | |||||
| STATIC_URL = 'static/' | |||||
| # Default primary key field type | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field | |||||
| DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' | |||||
| # django_project/settings.py | |||||
| LOGIN_REDIRECT_URL = "home" # new | |||||
| LOGOUT_REDIRECT_URL = "home" # new | |||||
| # django_project/settings.py | |||||
| EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" # new | |||||
| @ -0,0 +1,26 @@ | |||||
| """ | |||||
| URL configuration for django_project project. | |||||
| The `urlpatterns` list routes URLs to views. For more information please see: | |||||
| https://docs.djangoproject.com/en/5.1/topics/http/urls/ | |||||
| Examples: | |||||
| Function views | |||||
| 1. Add an import: from my_app import views | |||||
| 2. Add a URL to urlpatterns: path('', views.home, name='home') | |||||
| Class-based views | |||||
| 1. Add an import: from other_app.views import Home | |||||
| 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') | |||||
| Including another URLconf | |||||
| 1. Import the include() function: from django.urls import include, path | |||||
| 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) | |||||
| """ | |||||
| from django.contrib import admin | |||||
| from django.urls import path, include | |||||
| from django.views.generic.base import TemplateView # new | |||||
| urlpatterns = [ | |||||
| path('admin/', admin.site.urls), | |||||
| path("accounts/", include("accounts.urls")), # new | |||||
| path("accounts/", include("django.contrib.auth.urls")), # new | |||||
| path("", TemplateView.as_view(template_name="home.html"), name="home"), # new | |||||
| ] | |||||
| @ -0,0 +1,16 @@ | |||||
| """ | |||||
| WSGI config for django_project project. | |||||
| It exposes the WSGI callable as a module-level variable named ``application``. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ | |||||
| """ | |||||
| import os | |||||
| from django.core.wsgi import get_wsgi_application | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') | |||||
| application = get_wsgi_application() | |||||
| @ -0,0 +1,22 @@ | |||||
| #!/usr/bin/env python | |||||
| """Django's command-line utility for administrative tasks.""" | |||||
| import os | |||||
| import sys | |||||
| def main(): | |||||
| """Run administrative tasks.""" | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') | |||||
| try: | |||||
| from django.core.management import execute_from_command_line | |||||
| except ImportError as exc: | |||||
| raise ImportError( | |||||
| "Couldn't import Django. Are you sure it's installed and " | |||||
| "available on your PYTHONPATH environment variable? Did you " | |||||
| "forget to activate a virtual environment?" | |||||
| ) from exc | |||||
| execute_from_command_line(sys.argv) | |||||
| if __name__ == '__main__': | |||||
| main() | |||||
| @ -0,0 +1,17 @@ | |||||
| <!-- templates/base.html --> | |||||
| <!DOCTYPE html> | |||||
| <html> | |||||
| <head> | |||||
| <meta charset="utf-8"> | |||||
| <title>{% block title %}Django Auth Tutorial{% endblock %}</title> | |||||
| </head> | |||||
| <body> | |||||
| <main> | |||||
| {% block content %} | |||||
| {% endblock %} | |||||
| </main> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,19 @@ | |||||
| <!-- templates/home.html --> | |||||
| {% extends "base.html" %} | |||||
| {% block title %}Home{% endblock %} | |||||
| {% block content %} | |||||
| {% if user.is_authenticated %} | |||||
| <p>Hi {{ user.username }}!</p> | |||||
| <p><a href="{% url 'password_change' %}">Password Change</a></p> | |||||
| <form action="{% url 'logout' %}" method="post"> | |||||
| {% csrf_token %} | |||||
| <button type="submit">Log Out</button> | |||||
| </form> | |||||
| {% else %} | |||||
| <p>You are not logged in</p> | |||||
| <p><a href="{% url 'password_reset' %}">Password Reset</a></p> | |||||
| <p><a href="{% url 'login' %}">Log In</a></p> | |||||
| {% endif %} | |||||
| {% endblock %} | |||||
| @ -0,0 +1,14 @@ | |||||
| {% extends "admin/base_site.html" %} | |||||
| {% load i18n %} | |||||
| {% block breadcrumbs %}<div class="breadcrumbs"><a href="{% url 'admin:index' %}">{% translate 'Home' %}</a></div>{% endblock %} | |||||
| {% block nav-sidebar %}{% endblock %} | |||||
| {% block content %} | |||||
| <p>{% translate "Thanks for spending some quality time with the web site today." %}</p> | |||||
| <p><a href="{% url 'admin:index' %}">{% translate 'Log in again' %}</a></p> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,68 @@ | |||||
| {% extends "admin/base_site.html" %} | |||||
| {% load i18n static %} | |||||
| {% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/login.css" %}"> | |||||
| {{ form.media }} | |||||
| {% endblock %} | |||||
| {% block bodyclass %}{{ block.super }} login{% endblock %} | |||||
| {% block usertools %}{% endblock %} | |||||
| {% block nav-global %}{% endblock %} | |||||
| {% block nav-sidebar %}{% endblock %} | |||||
| {% block content_title %}{% endblock %} | |||||
| {% block nav-breadcrumbs %}{% endblock %} | |||||
| {% block content %} | |||||
| {% if form.errors and not form.non_field_errors %} | |||||
| <p class="errornote"> | |||||
| {% blocktranslate count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %} | |||||
| </p> | |||||
| {% endif %} | |||||
| {% if form.non_field_errors %} | |||||
| {% for error in form.non_field_errors %} | |||||
| <p class="errornote"> | |||||
| {{ error }} | |||||
| </p> | |||||
| {% endfor %} | |||||
| {% endif %} | |||||
| <div id="content-main"> | |||||
| {% if user.is_authenticated %} | |||||
| <p class="errornote"> | |||||
| {% blocktranslate trimmed %} | |||||
| You are authenticated as {{ username }}, but are not authorized to | |||||
| access this page. Would you like to login to a different account? | |||||
| {% endblocktranslate %} | |||||
| </p> | |||||
| {% endif %} | |||||
| <form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %} | |||||
| <div class="form-row"> | |||||
| {{ form.username.errors }} | |||||
| {{ form.username.label_tag }} {{ form.username }} | |||||
| </div> | |||||
| <div class="form-row"> | |||||
| {{ form.password.errors }} | |||||
| {{ form.password.label_tag }} {{ form.password }} | |||||
| <input type="hidden" name="next" value="{{ next }}"> | |||||
| </div> | |||||
| {% url 'admin_password_reset' as password_reset_url %} | |||||
| {% if password_reset_url %} | |||||
| <div class="password-reset-link"> | |||||
| <a href="{{ password_reset_url }}">{% translate 'Forgotten your password or username?' %}</a> | |||||
| </div> | |||||
| {% endif %} | |||||
| <div class="submit-row"> | |||||
| <input type="submit" value="{% translate 'Log in' %}"> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,13 @@ | |||||
| <!-- templates/registration/login.html --> | |||||
| {% extends "base.html" %} | |||||
| {% block title %}Login{% endblock %} | |||||
| {% block content %} | |||||
| <h2>Log In</h2> | |||||
| <form method="post"> | |||||
| {% csrf_token %} | |||||
| {{ form }} | |||||
| <button type="submit">Log In</button> | |||||
| </form> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,20 @@ | |||||
| {% extends "admin/base_site.html" %} | |||||
| {% load i18n %} | |||||
| {% block userlinks %} | |||||
| {% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% translate 'Documentation' %}</a> / {% endif %}{% translate 'Change password' %} / | |||||
| <form id="logout-form" method="post" action="{% url 'admin:logout' %}"> | |||||
| {% csrf_token %} | |||||
| <button type="submit">{% translate 'Log out' %}</button> | |||||
| </form> | |||||
| {% include "admin/color_theme_toggle.html" %} | |||||
| {% endblock %} | |||||
| {% block breadcrumbs %} | |||||
| <div class="breadcrumbs"> | |||||
| <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a> | |||||
| › {% translate 'Password change' %} | |||||
| </div> | |||||
| {% endblock %} | |||||
| {% block content %} | |||||
| <p>{% translate 'Your password was changed.' %}</p> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,64 @@ | |||||
| {% extends "admin/base_site.html" %} | |||||
| {% load i18n static %} | |||||
| {% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %} | |||||
| {% block userlinks %} | |||||
| {% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% translate 'Documentation' %}</a> / {% endif %} {% translate 'Change password' %} / | |||||
| <form id="logout-form" method="post" action="{% url 'admin:logout' %}"> | |||||
| {% csrf_token %} | |||||
| <button type="submit">{% translate 'Log out' %}</button> | |||||
| </form> | |||||
| {% include "admin/color_theme_toggle.html" %} | |||||
| {% endblock %} | |||||
| {% block breadcrumbs %} | |||||
| <div class="breadcrumbs"> | |||||
| <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a> | |||||
| › {% translate 'Password change' %} | |||||
| </div> | |||||
| {% endblock %} | |||||
| {% block content %}<div id="content-main"> | |||||
| <form method="post">{% csrf_token %} | |||||
| <div> | |||||
| {% if form.errors %} | |||||
| <p class="errornote"> | |||||
| {% blocktranslate count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %} | |||||
| </p> | |||||
| {% endif %} | |||||
| <p>{% translate 'Please enter your old password, for security’s sake, and then enter your new password twice so we can verify you typed it in correctly.' %}</p> | |||||
| <fieldset class="module aligned wide"> | |||||
| <div class="form-row"> | |||||
| {{ form.old_password.errors }} | |||||
| <div class="flex-container">{{ form.old_password.label_tag }} {{ form.old_password }}</div> | |||||
| </div> | |||||
| <div class="form-row"> | |||||
| {{ form.new_password1.errors }} | |||||
| <div class="flex-container">{{ form.new_password1.label_tag }} {{ form.new_password1 }}</div> | |||||
| {% if form.new_password1.help_text %} | |||||
| <div class="help"{% if form.new_password1.id_for_label %} id="{{ form.new_password1.id_for_label }}_helptext"{% endif %}>{{ form.new_password1.help_text|safe }}</div> | |||||
| {% endif %} | |||||
| </div> | |||||
| <div class="form-row"> | |||||
| {{ form.new_password2.errors }} | |||||
| <div class="flex-container">{{ form.new_password2.label_tag }} {{ form.new_password2 }}</div> | |||||
| {% if form.new_password2.help_text %} | |||||
| <div class="help"{% if form.new_password2.id_for_label %} id="{{ form.new_password2.id_for_label }}_helptext"{% endif %}>{{ form.new_password2.help_text|safe }}</div> | |||||
| {% endif %} | |||||
| </div> | |||||
| </fieldset> | |||||
| <div class="submit-row"> | |||||
| <input type="submit" value="{% translate 'Change my password' %}" class="default"> | |||||
| </div> | |||||
| </div> | |||||
| </form></div> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,17 @@ | |||||
| {% extends "admin/base_site.html" %} | |||||
| {% load i18n %} | |||||
| {% block breadcrumbs %} | |||||
| <div class="breadcrumbs"> | |||||
| <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a> | |||||
| › {% translate 'Password reset' %} | |||||
| </div> | |||||
| {% endblock %} | |||||
| {% block content %} | |||||
| <p>{% translate "Your password has been set. You may go ahead and log in now." %}</p> | |||||
| <p><a href="{{ login_url }}">{% translate 'Log in' %}</a></p> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,47 @@ | |||||
| {% extends "admin/base_site.html" %} | |||||
| {% load i18n static %} | |||||
| {% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %} | |||||
| {% block breadcrumbs %} | |||||
| <div class="breadcrumbs"> | |||||
| <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a> | |||||
| › {% translate 'Password reset confirmation' %} | |||||
| </div> | |||||
| {% endblock %} | |||||
| {% block content %} | |||||
| {% if validlink %} | |||||
| <p>{% translate "Please enter your new password twice so we can verify you typed it in correctly." %}</p> | |||||
| <form method="post">{% csrf_token %} | |||||
| <fieldset class="module aligned"> | |||||
| <input class="hidden" autocomplete="username" value="{{ form.user.get_username }}"> | |||||
| <div class="form-row field-password1"> | |||||
| {{ form.new_password1.errors }} | |||||
| <div class="flex-container"> | |||||
| <label for="id_new_password1">{% translate 'New password:' %}</label> | |||||
| {{ form.new_password1 }} | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row field-password2"> | |||||
| {{ form.new_password2.errors }} | |||||
| <div class="flex-container"> | |||||
| <label for="id_new_password2">{% translate 'Confirm password:' %}</label> | |||||
| {{ form.new_password2 }} | |||||
| </div> | |||||
| </div> | |||||
| </fieldset> | |||||
| <div class="submit-row"> | |||||
| <input type="submit" value="{% translate 'Change my password' %}"> | |||||
| </div> | |||||
| </form> | |||||
| {% else %} | |||||
| <p>{% translate "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p> | |||||
| {% endif %} | |||||
| {% endblock %} | |||||
| @ -0,0 +1,17 @@ | |||||
| {% extends "admin/base_site.html" %} | |||||
| {% load i18n %} | |||||
| {% block breadcrumbs %} | |||||
| <div class="breadcrumbs"> | |||||
| <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a> | |||||
| › {% translate 'Password reset' %} | |||||
| </div> | |||||
| {% endblock %} | |||||
| {% block content %} | |||||
| <p>{% translate 'We’ve emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.' %}</p> | |||||
| <p>{% translate 'If you don’t receive an email, please make sure you’ve entered the address you registered with, and check your spam folder.' %}</p> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,14 @@ | |||||
| {% load i18n %}{% autoescape off %} | |||||
| {% blocktranslate %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktranslate %} | |||||
| {% translate "Please go to the following page and choose a new password:" %} | |||||
| {% block reset_link %} | |||||
| {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} | |||||
| {% endblock %} | |||||
| {% translate 'Your username, in case you’ve forgotten:' %} {{ user.get_username }} | |||||
| {% translate "Thanks for using our site!" %} | |||||
| {% blocktranslate %}The {{ site_name }} team{% endblocktranslate %} | |||||
| {% endautoescape %} | |||||
| @ -0,0 +1,31 @@ | |||||
| {% extends "admin/base_site.html" %} | |||||
| {% load i18n static %} | |||||
| {% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %} | |||||
| {% block breadcrumbs %} | |||||
| <div class="breadcrumbs"> | |||||
| <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a> | |||||
| › {% translate 'Password reset' %} | |||||
| </div> | |||||
| {% endblock %} | |||||
| {% block content %} | |||||
| <p>{% translate 'Forgotten your password? Enter your email address below, and we’ll email instructions for setting a new one.' %}</p> | |||||
| <form method="post">{% csrf_token %} | |||||
| <fieldset class="module aligned"> | |||||
| <div class="form-row field-email"> | |||||
| {{ form.email.errors }} | |||||
| <div class="flex-container"> | |||||
| <label for="id_email">{% translate 'Email address:' %}</label> | |||||
| {{ form.email }} | |||||
| </div> | |||||
| </div> | |||||
| </fieldset> | |||||
| <div class="submit-row"> | |||||
| <input type="submit" value="{% translate 'Reset my password' %}"> | |||||
| </div> | |||||
| </form> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,13 @@ | |||||
| <!-- templates/registration/signup.html --> | |||||
| {% extends "base.html" %} | |||||
| {% block title %}Sign Up{% endblock %} | |||||
| {% block content %} | |||||
| <h2>Sign up</h2> | |||||
| <form method="post"> | |||||
| {% csrf_token %} | |||||
| {{ form }} | |||||
| <button type="submit">Sign Up</button> | |||||
| </form> | |||||
| {% endblock %} | |||||
| @ -1 +1,12 @@ | |||||
| https://learndjango.com/tutorials/django-login-and-logout-tutorial | |||||
| https://learndjango.com/tutorials/django-login-and-logout-tutorial | |||||
| Username: {{ user.username }} | |||||
| User Full name: {{ user.get_full_name }} | |||||
| User Group: {{ user.groups.all.0 }} | |||||
| Email: {{ user.email }} | |||||
| Session Started at: {{ user.last_login }} | |||||
| @ -1,6 +1,6 @@ | |||||
| {% load static %} | {% load static %} | ||||
| <div class="app-branding"> | <div class="app-branding"> | ||||
| <a class="app-logo" href="{% url 'lista_autores' %}"><img class="logo-icon me-2" src="{% static 'images/reymota-logo.svg' %}" alt="logo"><span class="logo-text">LIBROS</span></a> | |||||
| <a class="app-logo" href="{% url 'principal' %}"><img class="logo-icon me-2" src="{% static 'images/reymota-logo.svg' %}" alt="logo"><span class="logo-text">LIBROS</span></a> | |||||
| </div><!--//app-branding--> | </div><!--//app-branding--> | ||||
| @ -0,0 +1,27 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="container-xl"> | |||||
| <h1 class="app-page-title">Introducción</h1> | |||||
| <div class="app-card alert alert-dismissible shadow-sm mb-4 border-left-decoration" role="alert"> | |||||
| <div class="inner"> | |||||
| <div class="app-card-body p-3 p-lg-4"> | |||||
| <h3 class="mb-3">¡Bienvenido a la colección de libros!</h3> | |||||
| <div class="row gx-5 gy-3"> | |||||
| <!-- | |||||
| <div class="col-12 col-lg-9"> | |||||
| <div>Pensado inicialmente para guardar las letras de las canciones de Bruce Springsteen.</div> | |||||
| </div>--><!--//col--> | |||||
| </div><!--//row--> | |||||
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |||||
| </div><!--//app-card-body--> | |||||
| </div><!--//inner--> | |||||
| </div><!--//app-card--> | |||||
| </div><!--//container-fluid--> | |||||
| {% endblock %} | |||||
| @ -1,16 +1,18 @@ | |||||
| from django.urls import path | from django.urls import path | ||||
| from . import views | from . import views | ||||
| urlpatterns = [ | urlpatterns = [ | ||||
| path('autores/', views.lista_autores, name='lista_autores'), | |||||
| path('autores', views.lista_autores, name='lista_autores'), | |||||
| path('autores/nuevo/', views.nuevo_autor, name='nuevo_autor'), | path('autores/nuevo/', views.nuevo_autor, name='nuevo_autor'), | ||||
| path('autores/<int:autor_id>/', views.detalle_autor, name='detalle_autor'), | path('autores/<int:autor_id>/', views.detalle_autor, name='detalle_autor'), | ||||
| path('autores/<int:autor_id>/editar/', views.editar_autor, name='editar_autor'), | path('autores/<int:autor_id>/editar/', views.editar_autor, name='editar_autor'), | ||||
| path('autores/<int:autor_id>/eliminar/', views.eliminar_autor, name='eliminar_autor'), | path('autores/<int:autor_id>/eliminar/', views.eliminar_autor, name='eliminar_autor'), | ||||
| path('libros/', views.lista_libros, name='lista_libros'), | |||||
| path('libros', views.lista_libros, name='lista_libros'), | |||||
| path('libros/nuevo/', views.nuevo_libro, name='nuevo_libro'), | path('libros/nuevo/', views.nuevo_libro, name='nuevo_libro'), | ||||
| path('libros/<int:libro_id>/', views.detalle_libro, name='detalle_libro'), | path('libros/<int:libro_id>/', views.detalle_libro, name='detalle_libro'), | ||||
| path('libros/<int:libro_id>/editar/', views.editar_libro, name='editar_libro'), | path('libros/<int:libro_id>/editar/', views.editar_libro, name='editar_libro'), | ||||
| path('libros/<int:libro_id>/eliminar/', views.eliminar_libro, name='eliminar_libro'), | path('libros/<int:libro_id>/eliminar/', views.eliminar_libro, name='eliminar_libro'), | ||||
| ] | ] | ||||
| @ -0,0 +1,13 @@ | |||||
| [[source]] | |||||
| url = "https://pypi.org/simple" | |||||
| verify_ssl = true | |||||
| name = "pypi" | |||||
| [packages] | |||||
| django = "*" | |||||
| pillow = "*" | |||||
| [dev-packages] | |||||
| [requires] | |||||
| python_version = "3.10" | |||||
| @ -0,0 +1,141 @@ | |||||
| { | |||||
| "_meta": { | |||||
| "hash": { | |||||
| "sha256": "f0a6b10e1349a882b824f490c90a5b0b71905386b17672678bed9a1bfabb658a" | |||||
| }, | |||||
| "pipfile-spec": 6, | |||||
| "requires": { | |||||
| "python_version": "3.10" | |||||
| }, | |||||
| "sources": [ | |||||
| { | |||||
| "name": "pypi", | |||||
| "url": "https://pypi.org/simple", | |||||
| "verify_ssl": true | |||||
| } | |||||
| ] | |||||
| }, | |||||
| "default": { | |||||
| "asgiref": { | |||||
| "hashes": [ | |||||
| "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", | |||||
| "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590" | |||||
| ], | |||||
| "markers": "python_version >= '3.8'", | |||||
| "version": "==3.8.1" | |||||
| }, | |||||
| "django": { | |||||
| "hashes": [ | |||||
| "sha256:848a5980e8efb76eea70872fb0e4bc5e371619c70fffbe48e3e1b50b2c09455d", | |||||
| "sha256:d3b811bf5371a26def053d7ee42a9df1267ef7622323fe70a601936725aa4557" | |||||
| ], | |||||
| "index": "pypi", | |||||
| "markers": "python_version >= '3.10'", | |||||
| "version": "==5.1" | |||||
| }, | |||||
| "pillow": { | |||||
| "hashes": [ | |||||
| "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", | |||||
| "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", | |||||
| "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", | |||||
| "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", | |||||
| "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", | |||||
| "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", | |||||
| "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", | |||||
| "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", | |||||
| "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", | |||||
| "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", | |||||
| "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", | |||||
| "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", | |||||
| "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", | |||||
| "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", | |||||
| "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", | |||||
| "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", | |||||
| "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", | |||||
| "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", | |||||
| "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", | |||||
| "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", | |||||
| "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", | |||||
| "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", | |||||
| "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", | |||||
| "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", | |||||
| "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", | |||||
| "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", | |||||
| "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", | |||||
| "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", | |||||
| "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", | |||||
| "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", | |||||
| "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", | |||||
| "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", | |||||
| "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", | |||||
| "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", | |||||
| "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", | |||||
| "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", | |||||
| "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", | |||||
| "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", | |||||
| "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", | |||||
| "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", | |||||
| "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", | |||||
| "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", | |||||
| "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", | |||||
| "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", | |||||
| "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", | |||||
| "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", | |||||
| "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", | |||||
| "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", | |||||
| "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", | |||||
| "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", | |||||
| "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", | |||||
| "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", | |||||
| "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", | |||||
| "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", | |||||
| "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", | |||||
| "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", | |||||
| "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", | |||||
| "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", | |||||
| "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", | |||||
| "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", | |||||
| "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", | |||||
| "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", | |||||
| "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", | |||||
| "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", | |||||
| "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", | |||||
| "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", | |||||
| "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", | |||||
| "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", | |||||
| "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", | |||||
| "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", | |||||
| "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", | |||||
| "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", | |||||
| "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", | |||||
| "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", | |||||
| "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", | |||||
| "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", | |||||
| "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", | |||||
| "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", | |||||
| "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", | |||||
| "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1" | |||||
| ], | |||||
| "index": "pypi", | |||||
| "markers": "python_version >= '3.8'", | |||||
| "version": "==10.4.0" | |||||
| }, | |||||
| "sqlparse": { | |||||
| "hashes": [ | |||||
| "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4", | |||||
| "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e" | |||||
| ], | |||||
| "markers": "python_version >= '3.8'", | |||||
| "version": "==0.5.1" | |||||
| }, | |||||
| "typing-extensions": { | |||||
| "hashes": [ | |||||
| "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", | |||||
| "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" | |||||
| ], | |||||
| "markers": "python_version < '3.11'", | |||||
| "version": "==4.12.2" | |||||
| } | |||||
| }, | |||||
| "develop": {} | |||||
| } | |||||
| @ -0,0 +1,3 @@ | |||||
| ## Fuente | |||||
| https://testdriven.io/blog/django-custom-user-model/ | |||||
| @ -0,0 +1,16 @@ | |||||
| """ | |||||
| ASGI config for hola_django project. | |||||
| It exposes the ASGI callable as a module-level variable named ``application``. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ | |||||
| """ | |||||
| import os | |||||
| from django.core.asgi import get_asgi_application | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hola_django.settings') | |||||
| application = get_asgi_application() | |||||
| @ -0,0 +1,127 @@ | |||||
| """ | |||||
| Django settings for hola_django project. | |||||
| Generated by 'django-admin startproject' using Django 5.1. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/topics/settings/ | |||||
| For the full list of settings and their values, see | |||||
| https://docs.djangoproject.com/en/5.1/ref/settings/ | |||||
| """ | |||||
| from pathlib import Path | |||||
| # Build paths inside the project like this: BASE_DIR / 'subdir'. | |||||
| BASE_DIR = Path(__file__).resolve().parent.parent | |||||
| # Quick-start development settings - unsuitable for production | |||||
| # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ | |||||
| # SECURITY WARNING: keep the secret key used in production secret! | |||||
| SECRET_KEY = 'django-insecure--q$(z*_2n3!s%5&!g3v7!&w6q_26wmu$+9#p1hj2072f%58fw0' | |||||
| # SECURITY WARNING: don't run with debug turned on in production! | |||||
| DEBUG = True | |||||
| ALLOWED_HOSTS = [] | |||||
| # Application definition | |||||
| INSTALLED_APPS = [ | |||||
| 'django.contrib.admin', | |||||
| 'django.contrib.auth', | |||||
| 'django.contrib.contenttypes', | |||||
| 'django.contrib.sessions', | |||||
| 'django.contrib.messages', | |||||
| 'django.contrib.staticfiles', | |||||
| 'users', | |||||
| ] | |||||
| MIDDLEWARE = [ | |||||
| 'django.middleware.security.SecurityMiddleware', | |||||
| 'django.contrib.sessions.middleware.SessionMiddleware', | |||||
| 'django.middleware.common.CommonMiddleware', | |||||
| 'django.middleware.csrf.CsrfViewMiddleware', | |||||
| 'django.contrib.auth.middleware.AuthenticationMiddleware', | |||||
| 'django.contrib.messages.middleware.MessageMiddleware', | |||||
| 'django.middleware.clickjacking.XFrameOptionsMiddleware', | |||||
| ] | |||||
| ROOT_URLCONF = 'hola_django.urls' | |||||
| TEMPLATES = [ | |||||
| { | |||||
| 'BACKEND': 'django.template.backends.django.DjangoTemplates', | |||||
| 'DIRS': [], | |||||
| 'APP_DIRS': True, | |||||
| 'OPTIONS': { | |||||
| 'context_processors': [ | |||||
| 'django.template.context_processors.debug', | |||||
| 'django.template.context_processors.request', | |||||
| 'django.contrib.auth.context_processors.auth', | |||||
| 'django.contrib.messages.context_processors.messages', | |||||
| ], | |||||
| }, | |||||
| }, | |||||
| ] | |||||
| WSGI_APPLICATION = 'hola_django.wsgi.application' | |||||
| # Database | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#databases | |||||
| DATABASES = { | |||||
| 'default': { | |||||
| 'ENGINE': 'django.db.backends.sqlite3', | |||||
| 'NAME': BASE_DIR / 'customuser.sqlite3', | |||||
| } | |||||
| } | |||||
| # Password validation | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators | |||||
| AUTH_PASSWORD_VALIDATORS = [ | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', | |||||
| }, | |||||
| ] | |||||
| # Internationalization | |||||
| # https://docs.djangoproject.com/en/5.1/topics/i18n/ | |||||
| LANGUAGE_CODE = 'en-us' | |||||
| TIME_ZONE = 'UTC' | |||||
| USE_I18N = True | |||||
| USE_TZ = True | |||||
| # Static files (CSS, JavaScript, Images) | |||||
| # https://docs.djangoproject.com/en/5.1/howto/static-files/ | |||||
| STATIC_URL = 'static/' | |||||
| # Default primary key field type | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field | |||||
| DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' | |||||
| AUTH_USER_MODEL = "users.CustomUser" | |||||
| @ -0,0 +1,22 @@ | |||||
| """ | |||||
| URL configuration for hola_django project. | |||||
| The `urlpatterns` list routes URLs to views. For more information please see: | |||||
| https://docs.djangoproject.com/en/5.1/topics/http/urls/ | |||||
| Examples: | |||||
| Function views | |||||
| 1. Add an import: from my_app import views | |||||
| 2. Add a URL to urlpatterns: path('', views.home, name='home') | |||||
| Class-based views | |||||
| 1. Add an import: from other_app.views import Home | |||||
| 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') | |||||
| Including another URLconf | |||||
| 1. Import the include() function: from django.urls import include, path | |||||
| 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) | |||||
| """ | |||||
| from django.contrib import admin | |||||
| from django.urls import path | |||||
| urlpatterns = [ | |||||
| path('admin/', admin.site.urls), | |||||
| ] | |||||
| @ -0,0 +1,16 @@ | |||||
| """ | |||||
| WSGI config for hola_django project. | |||||
| It exposes the WSGI callable as a module-level variable named ``application``. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ | |||||
| """ | |||||
| import os | |||||
| from django.core.wsgi import get_wsgi_application | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hola_django.settings') | |||||
| application = get_wsgi_application() | |||||
| @ -0,0 +1,22 @@ | |||||
| #!/usr/bin/env python | |||||
| """Django's command-line utility for administrative tasks.""" | |||||
| import os | |||||
| import sys | |||||
| def main(): | |||||
| """Run administrative tasks.""" | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hola_django.settings') | |||||
| try: | |||||
| from django.core.management import execute_from_command_line | |||||
| except ImportError as exc: | |||||
| raise ImportError( | |||||
| "Couldn't import Django. Are you sure it's installed and " | |||||
| "available on your PYTHONPATH environment variable? Did you " | |||||
| "forget to activate a virtual environment?" | |||||
| ) from exc | |||||
| execute_from_command_line(sys.argv) | |||||
| if __name__ == '__main__': | |||||
| main() | |||||
| @ -0,0 +1,38 @@ | |||||
| Migrations for 'users': | |||||
| users/migrations/0001_initial.py | |||||
| + Create model CustomUser | |||||
| Full migrations file '0001_initial.py': | |||||
| # Generated by Django 5.1 on 2024-08-08 13:04 | |||||
| import django.utils.timezone | |||||
| from django.db import migrations, models | |||||
| class Migration(migrations.Migration): | |||||
| initial = True | |||||
| dependencies = [ | |||||
| ('auth', '0012_alter_user_first_name_max_length'), | |||||
| ] | |||||
| operations = [ | |||||
| migrations.CreateModel( | |||||
| name='CustomUser', | |||||
| 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')), | |||||
| ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), | |||||
| ('is_staff', models.BooleanField(default=False)), | |||||
| ('is_active', models.BooleanField(default=True)), | |||||
| ('date_joined', models.DateTimeField(default=django.utils.timezone.now)), | |||||
| ('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={ | |||||
| 'abstract': False, | |||||
| }, | |||||
| ), | |||||
| ] | |||||
| @ -0,0 +1,34 @@ | |||||
| from django.contrib import admin | |||||
| from django.contrib.auth.admin import UserAdmin | |||||
| from .forms import CustomUserCreationForm, CustomUserChangeForm | |||||
| from .models import CustomUser | |||||
| class CustomUserAdmin(UserAdmin): | |||||
| add_form = CustomUserCreationForm | |||||
| form = CustomUserChangeForm | |||||
| model = CustomUser | |||||
| list_display = ("email", "is_staff", "is_active","foto") | |||||
| list_filter = ("email", "is_staff", "is_active",) | |||||
| fieldsets = ( | |||||
| (None, {"fields": ("email", "password")}), | |||||
| ("Permissions", {"fields": ("is_staff", "is_active", "groups", "user_permissions")}), | |||||
| ("Varios", {"fields": ("foto",)}), | |||||
| ) | |||||
| add_fieldsets = ( | |||||
| (None, { | |||||
| "classes": ("wide",), | |||||
| "fields": ( | |||||
| "email", "password1", "password2", "is_staff", | |||||
| "is_active", "groups", "user_permissions" | |||||
| )} | |||||
| ), | |||||
| ("Varios", {"fields": ("foto",)}), | |||||
| ) | |||||
| search_fields = ("email",) | |||||
| ordering = ("email",) | |||||
| admin.site.register(CustomUser, CustomUserAdmin) | |||||
| @ -0,0 +1,6 @@ | |||||
| from django.apps import AppConfig | |||||
| class UsersConfig(AppConfig): | |||||
| default_auto_field = 'django.db.models.BigAutoField' | |||||
| name = 'users' | |||||
| @ -0,0 +1,17 @@ | |||||
| from django.contrib.auth.forms import UserCreationForm, UserChangeForm | |||||
| from .models import CustomUser | |||||
| class CustomUserCreationForm(UserCreationForm): | |||||
| class Meta: | |||||
| model = CustomUser | |||||
| fields = ("email", "foto") | |||||
| class CustomUserChangeForm(UserChangeForm): | |||||
| class Meta: | |||||
| model = CustomUser | |||||
| fields = ("email", "foto") | |||||
| @ -0,0 +1,34 @@ | |||||
| from django.contrib.auth.base_user import BaseUserManager | |||||
| from django.utils.translation import gettext_lazy as _ | |||||
| class CustomUserManager(BaseUserManager): | |||||
| """ | |||||
| Custom user model manager where email is the unique identifiers | |||||
| for authentication instead of usernames. | |||||
| """ | |||||
| def create_user(self, email, password, **extra_fields): | |||||
| """ | |||||
| Create and save a user with the given email and password. | |||||
| """ | |||||
| if not email: | |||||
| raise ValueError(_("The Email must be set")) | |||||
| email = self.normalize_email(email) | |||||
| user = self.model(email=email, **extra_fields) | |||||
| user.set_password(password) | |||||
| user.save() | |||||
| return user | |||||
| def create_superuser(self, email, password, **extra_fields): | |||||
| """ | |||||
| Create and save a SuperUser with the given email and password. | |||||
| """ | |||||
| extra_fields.setdefault("is_staff", True) | |||||
| extra_fields.setdefault("is_superuser", True) | |||||
| extra_fields.setdefault("is_active", True) | |||||
| if extra_fields.get("is_staff") is not True: | |||||
| raise ValueError(_("Superuser must have is_staff=True.")) | |||||
| if extra_fields.get("is_superuser") is not True: | |||||
| raise ValueError(_("Superuser must have is_superuser=True.")) | |||||
| return self.create_user(email, password, **extra_fields) | |||||
| @ -0,0 +1,34 @@ | |||||
| # Generated by Django 5.1 on 2024-08-08 13:05 | |||||
| import django.utils.timezone | |||||
| from django.db import migrations, models | |||||
| class Migration(migrations.Migration): | |||||
| initial = True | |||||
| dependencies = [ | |||||
| ('auth', '0012_alter_user_first_name_max_length'), | |||||
| ] | |||||
| operations = [ | |||||
| migrations.CreateModel( | |||||
| name='CustomUser', | |||||
| 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')), | |||||
| ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), | |||||
| ('is_staff', models.BooleanField(default=False)), | |||||
| ('is_active', models.BooleanField(default=True)), | |||||
| ('date_joined', models.DateTimeField(default=django.utils.timezone.now)), | |||||
| ('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={ | |||||
| 'abstract': False, | |||||
| }, | |||||
| ), | |||||
| ] | |||||
| @ -0,0 +1,18 @@ | |||||
| # Generated by Django 5.1 on 2024-08-08 13:07 | |||||
| from django.db import migrations, models | |||||
| class Migration(migrations.Migration): | |||||
| dependencies = [ | |||||
| ('users', '0001_initial'), | |||||
| ] | |||||
| operations = [ | |||||
| migrations.AddField( | |||||
| model_name='customuser', | |||||
| name='foto', | |||||
| field=models.TextField(default=''), | |||||
| ), | |||||
| ] | |||||
| @ -0,0 +1,18 @@ | |||||
| # Generated by Django 5.1 on 2024-08-08 13:29 | |||||
| from django.db import migrations, models | |||||
| class Migration(migrations.Migration): | |||||
| dependencies = [ | |||||
| ('users', '0002_customuser_foto'), | |||||
| ] | |||||
| operations = [ | |||||
| migrations.AlterField( | |||||
| model_name='customuser', | |||||
| name='foto', | |||||
| field=models.ImageField(blank=True, upload_to='profile_images'), | |||||
| ), | |||||
| ] | |||||
| @ -0,0 +1,22 @@ | |||||
| from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin | |||||
| from django.db import models | |||||
| from django.utils import timezone | |||||
| from django.utils.translation import gettext_lazy as _ | |||||
| from .managers import CustomUserManager | |||||
| class CustomUser(AbstractBaseUser, PermissionsMixin): | |||||
| email = models.EmailField(_("email address"), unique=True) | |||||
| is_staff = models.BooleanField(default=False) | |||||
| is_active = models.BooleanField(default=True) | |||||
| date_joined = models.DateTimeField(default=timezone.now) | |||||
| foto = models.ImageField(upload_to="profile_images", blank=True) | |||||
| USERNAME_FIELD = "email" | |||||
| REQUIRED_FIELDS = [] | |||||
| objects = CustomUserManager() | |||||
| def __str__(self): | |||||
| return self.email | |||||
| @ -0,0 +1,42 @@ | |||||
| from django.contrib.auth import get_user_model | |||||
| from django.test import TestCase | |||||
| class UsersManagersTests(TestCase): | |||||
| def test_create_user(self): | |||||
| User = get_user_model() | |||||
| user = User.objects.create_user(email="normal@user.com", password="foo") | |||||
| self.assertEqual(user.email, "normal@user.com") | |||||
| self.assertTrue(user.is_active) | |||||
| self.assertFalse(user.is_staff) | |||||
| self.assertFalse(user.is_superuser) | |||||
| try: | |||||
| # username is None for the AbstractUser option | |||||
| # username does not exist for the AbstractBaseUser option | |||||
| self.assertIsNone(user.username) | |||||
| except AttributeError: | |||||
| pass | |||||
| with self.assertRaises(TypeError): | |||||
| User.objects.create_user() | |||||
| with self.assertRaises(TypeError): | |||||
| User.objects.create_user(email="") | |||||
| with self.assertRaises(ValueError): | |||||
| User.objects.create_user(email="", password="foo") | |||||
| def test_create_superuser(self): | |||||
| User = get_user_model() | |||||
| admin_user = User.objects.create_superuser(email="super@user.com", password="foo") | |||||
| self.assertEqual(admin_user.email, "super@user.com") | |||||
| self.assertTrue(admin_user.is_active) | |||||
| self.assertTrue(admin_user.is_staff) | |||||
| self.assertTrue(admin_user.is_superuser) | |||||
| try: | |||||
| # username is None for the AbstractUser option | |||||
| # username does not exist for the AbstractBaseUser option | |||||
| self.assertIsNone(admin_user.username) | |||||
| except AttributeError: | |||||
| pass | |||||
| with self.assertRaises(ValueError): | |||||
| User.objects.create_superuser( | |||||
| email="super@user.com", password="foo", is_superuser=False) | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.shortcuts import render | |||||
| # Create your views here. | |||||