From 3c743216a5f56d4d77a01cfee9b6b8ad96dcf4a6 Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Tue, 25 Apr 2023 15:37:40 -0300 Subject: [PATCH] Validation in admin with inlines --- djangochildformtest/child/child/__init__.py | 0 djangochildformtest/child/child/asgi.py | 16 +++ djangochildformtest/child/child/settings.py | 124 ++++++++++++++++++ djangochildformtest/child/child/urls.py | 22 ++++ djangochildformtest/child/child/wsgi.py | 16 +++ djangochildformtest/child/db.sqlite3 | Bin 0 -> 143360 bytes djangochildformtest/child/manage.py | 22 ++++ .../child/validate/__init__.py | 0 djangochildformtest/child/validate/admin.py | 32 +++++ djangochildformtest/child/validate/apps.py | 6 + .../child/validate/migrations/0001_initial.py | 51 +++++++ .../child/validate/migrations/__init__.py | 0 djangochildformtest/child/validate/models.py | 11 ++ djangochildformtest/child/validate/tests.py | 3 + djangochildformtest/child/validate/views.py | 3 + 15 files changed, 306 insertions(+) create mode 100644 djangochildformtest/child/child/__init__.py create mode 100644 djangochildformtest/child/child/asgi.py create mode 100644 djangochildformtest/child/child/settings.py create mode 100644 djangochildformtest/child/child/urls.py create mode 100644 djangochildformtest/child/child/wsgi.py create mode 100644 djangochildformtest/child/db.sqlite3 create mode 100755 djangochildformtest/child/manage.py create mode 100644 djangochildformtest/child/validate/__init__.py create mode 100644 djangochildformtest/child/validate/admin.py create mode 100644 djangochildformtest/child/validate/apps.py create mode 100644 djangochildformtest/child/validate/migrations/0001_initial.py create mode 100644 djangochildformtest/child/validate/migrations/__init__.py create mode 100644 djangochildformtest/child/validate/models.py create mode 100644 djangochildformtest/child/validate/tests.py create mode 100644 djangochildformtest/child/validate/views.py diff --git a/djangochildformtest/child/child/__init__.py b/djangochildformtest/child/child/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djangochildformtest/child/child/asgi.py b/djangochildformtest/child/child/asgi.py new file mode 100644 index 0000000..dc6397a --- /dev/null +++ b/djangochildformtest/child/child/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for child 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/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "child.settings") + +application = get_asgi_application() diff --git a/djangochildformtest/child/child/settings.py b/djangochildformtest/child/child/settings.py new file mode 100644 index 0000000..70b9dbc --- /dev/null +++ b/djangochildformtest/child/child/settings.py @@ -0,0 +1,124 @@ +""" +Django settings for child project. + +Generated by 'django-admin startproject' using Django 4.2. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/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/4.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-b%&^(izms=udbncurdg-k75dui-414*-$y^z@f7huacb@ibs3l" + +# 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", + "validate" +] + +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 = "child.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 = "child.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.2/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/4.2/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/4.2/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/djangochildformtest/child/child/urls.py b/djangochildformtest/child/child/urls.py new file mode 100644 index 0000000..c025ae3 --- /dev/null +++ b/djangochildformtest/child/child/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for child project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.2/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), +] diff --git a/djangochildformtest/child/child/wsgi.py b/djangochildformtest/child/child/wsgi.py new file mode 100644 index 0000000..1594fed --- /dev/null +++ b/djangochildformtest/child/child/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for child 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/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "child.settings") + +application = get_wsgi_application() diff --git a/djangochildformtest/child/db.sqlite3 b/djangochildformtest/child/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..d69aa07179f003a0dfbb0c490400d4ab74a88680 GIT binary patch literal 143360 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 literal 0 HcmV?d00001 diff --git a/djangochildformtest/child/manage.py b/djangochildformtest/child/manage.py new file mode 100755 index 0000000..02eeb0d --- /dev/null +++ b/djangochildformtest/child/manage.py @@ -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", "child.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() diff --git a/djangochildformtest/child/validate/__init__.py b/djangochildformtest/child/validate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djangochildformtest/child/validate/admin.py b/djangochildformtest/child/validate/admin.py new file mode 100644 index 0000000..0c47342 --- /dev/null +++ b/djangochildformtest/child/validate/admin.py @@ -0,0 +1,32 @@ +from django import forms +from django.contrib import admin +from django.core.exceptions import ValidationError + +from .models import Child +from .models import Parent + + +class ChildInlineFormset(forms.models.BaseInlineFormSet): + def clean(self): + sum = 0 + for form in self.forms: + if ( + form.is_valid() + and "proportion" in form.cleaned_data + and not form.cleaned_data["DELETE"] + ): + sum += form.cleaned_data["proportion"] + print(f">>> total proportion: {sum}") + + if sum > 100: + raise ValidationError("Invalid proportion") + + +class ChildAdmin(admin.TabularInline): + model = Child + formset = ChildInlineFormset + + +@admin.register(Parent) +class ParentAdmin(admin.ModelAdmin): + inlines = [ChildAdmin] diff --git a/djangochildformtest/child/validate/apps.py b/djangochildformtest/child/validate/apps.py new file mode 100644 index 0000000..4984cc1 --- /dev/null +++ b/djangochildformtest/child/validate/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ValidateConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "validate" diff --git a/djangochildformtest/child/validate/migrations/0001_initial.py b/djangochildformtest/child/validate/migrations/0001_initial.py new file mode 100644 index 0000000..8452abc --- /dev/null +++ b/djangochildformtest/child/validate/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# Generated by Django 4.2 on 2023-04-25 18:14 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Parent", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=256)), + ], + ), + migrations.CreateModel( + name="Child", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=256)), + ("proportion", models.FloatField()), + ( + "parent", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="validate.parent", + ), + ), + ], + ), + ] diff --git a/djangochildformtest/child/validate/migrations/__init__.py b/djangochildformtest/child/validate/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djangochildformtest/child/validate/models.py b/djangochildformtest/child/validate/models.py new file mode 100644 index 0000000..3206936 --- /dev/null +++ b/djangochildformtest/child/validate/models.py @@ -0,0 +1,11 @@ +from django.db import models + + +class Parent(models.Model): + name = models.CharField(max_length=256) + + +class Child(models.Model): + name = models.CharField(max_length=256) + parent = models.ForeignKey(Parent, on_delete=models.CASCADE) + proportion = models.FloatField() diff --git a/djangochildformtest/child/validate/tests.py b/djangochildformtest/child/validate/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/djangochildformtest/child/validate/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/djangochildformtest/child/validate/views.py b/djangochildformtest/child/validate/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/djangochildformtest/child/validate/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.