소스 검색

Adaptación para correr en kubernetes

Celestino Rey 5 달 전
부모
커밋
3b44daf940
75개의 변경된 파일833개의 추가작업 그리고 0개의 파일을 삭제
  1. 30 0
      Dockerfile
  2. 49 0
      K8S/Makefile
  3. 2 0
      K8S/creaCredencialesRegistry.sh
  4. 17 0
      K8S/env-prod-configmap.yaml
  5. 7 0
      K8S/namespace.yaml
  6. 13 0
      K8S/pvc-static.yaml
  7. 63 0
      K8S/pvc-trgenerator.yaml
  8. 8 0
      K8S/reg-secret.yaml
  9. 10 0
      K8S/trgenerator-db.yaml
  10. 123 0
      K8S/trgenerator-deployment.yaml
  11. 31 0
      K8S/trgenerator-ingress.yaml
  12. 15 0
      K8S/trgenerator-service-lb.yaml
  13. 15 0
      K8S/trgenerator-service.yaml
  14. 6 0
      Makefile
  15. 31 0
      builder.yaml
  16. BIN
      doc_generator/__pycache__/__init__.cpython-313.pyc
  17. BIN
      doc_generator/__pycache__/settings.cpython-313.pyc
  18. BIN
      doc_generator/__pycache__/urls.cpython-313.pyc
  19. BIN
      doc_generator/__pycache__/wsgi.cpython-313.pyc
  20. 43 0
      nginx/default.conf
  21. 1 0
      src/accounts/.gitignore
  22. 0 0
      src/accounts/__init__.py
  23. 0 0
      src/accounts/admin.py
  24. 6 0
      src/accounts/apps.py
  25. 3 0
      src/accounts/models.py
  26. 0 0
      src/accounts/tests.py
  27. 14 0
      src/accounts/urls.py
  28. 12 0
      src/accounts/views.py
  29. 37 0
      src/config/entrypoint.sh
  30. 0 0
      src/doc_generator/__init__.py
  31. 0 0
      src/doc_generator/asgi.py
  32. 1 0
      src/doc_generator/settings.py
  33. 0 0
      src/doc_generator/urls.py
  34. 0 0
      src/doc_generator/wsgi.py
  35. 0 0
      src/manage.py
  36. 0 0
      src/requirements.txt
  37. 0 0
      src/siemensusers/__init__.py
  38. 64 0
      src/siemensusers/admin.py
  39. 6 0
      src/siemensusers/apps.py
  40. 19 0
      src/siemensusers/forms.py
  41. 0 0
      src/siemensusers/management/__init__.py
  42. 0 0
      src/siemensusers/management/commands/__init__.py
  43. 46 0
      src/siemensusers/management/commands/importar_usuarios.py
  44. 34 0
      src/siemensusers/managers.py
  45. 1 0
      src/siemensusers/migrations/noborrar
  46. 24 0
      src/siemensusers/models.py
  47. 16 0
      src/siemensusers/serializers.py
  48. 42 0
      src/siemensusers/tests.py
  49. 9 0
      src/siemensusers/urls.py
  50. 29 0
      src/siemensusers/views.py
  51. 0 0
      src/templates/textdocs/base.html
  52. 0 0
      src/templates/textdocs/document_confirm_delete.html
  53. 0 0
      src/templates/textdocs/document_detail.html
  54. 0 0
      src/templates/textdocs/document_form.html
  55. 0 0
      src/templates/textdocs/document_list.html
  56. 0 0
      src/templates/textdocs/document_pdf.html
  57. 0 0
      src/textdocs/__init__.py
  58. 3 0
      src/textdocs/admin.py
  59. 0 0
      src/textdocs/apps.py
  60. 0 0
      src/textdocs/forms.py
  61. 0 0
      src/textdocs/migrations/0001_initial.py
  62. 0 0
      src/textdocs/migrations/__init__.py
  63. 0 0
      src/textdocs/models.py
  64. 3 0
      src/textdocs/tests.py
  65. 0 0
      src/textdocs/urls.py
  66. 0 0
      src/textdocs/views.py
  67. BIN
      textdocs/__pycache__/__init__.cpython-313.pyc
  68. BIN
      textdocs/__pycache__/admin.cpython-313.pyc
  69. BIN
      textdocs/__pycache__/apps.cpython-313.pyc
  70. BIN
      textdocs/__pycache__/forms.cpython-313.pyc
  71. BIN
      textdocs/__pycache__/models.cpython-313.pyc
  72. BIN
      textdocs/__pycache__/urls.cpython-313.pyc
  73. BIN
      textdocs/__pycache__/views.cpython-313.pyc
  74. BIN
      textdocs/migrations/__pycache__/0001_initial.cpython-313.pyc
  75. BIN
      textdocs/migrations/__pycache__/__init__.cpython-313.pyc

+ 30 - 0
Dockerfile

@@ -0,0 +1,30 @@
+FROM python:3.14.2-alpine3.23
+
+# install nginx y otras cosas
+RUN apk add nginx netcat-openbsd curl vim jq rsync pango pango-dev cairo postgresql-client bash glib fontconfig font-noto
+
+COPY ./src/requirements.txt requirements.txt
+RUN python3 -m pip install --upgrade pip
+RUN pip install --user --no-cache-dir -r requirements.txt
+
+COPY ./nginx/default.conf /etc/nginx/http.d/nginx.conf
+
+# link nginx logs to container stdout
+RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
+    ln -sf /dev/stderr /var/log/nginx/error.log && \
+    chgrp -R 0 /var/lib/nginx && \
+    chmod -R g=u /var/lib/nginx
+
+ENV PATH="/root/.local/bin:$PATH"
+
+# copy the django code
+COPY ./src ./app
+
+RUN chgrp -R 0 ./app && \
+    chmod -R g=u ./app
+
+# change our working directory to the django projcet roo
+WORKDIR /app
+
+# execute our entrypoint.sh file
+CMD ["/app/config/entrypoint.sh"]

+ 49 - 0
K8S/Makefile

@@ -0,0 +1,49 @@
+export REGISTRO=harbor.reymota.es\/trgenerator
+export IMG_VERSION = alpine-1
+export NAMESPACE = trgenerator
+
+# limpia todo
+all: imagen clean install
+
+imagen:
+	cd ../; make
+
+ns:
+	-cat namespace.yaml | sed -e "s/<NS>/${NAMESPACE}/" |kubectl create -f -
+
+install:
+	-cat reg-secret.yaml| sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+	-cat env-prod-configmap.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+	-cat secreto-db.yaml| sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+
+	-cat trgenerator-deployment.yaml | sed -e "s/<NS>/${NAMESPACE}/" | sed -e "s/<TAG>/${IMG_VERSION}/" | sed -e "s/<REGISTRO>/${REGISTRO}/" | kubectl create -f -
+	-cat trgenerator-ingress.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+
+clean:
+	-cat trgenerator-ingress.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
+	-cat trgenerator-deployment.yaml | sed -e "s/<NS>/${NAMESPACE}/" | sed -e "s/<TAG>/${IMG_VERSION}/" | sed -e "s/<REGISTRO>/${REGISTRO}/" | kubectl delete -f -
+
+	-cat secreto-db.yaml| sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
+	-cat env-prod-configmap.yaml  | sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
+	-cat reg-secret.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
+
+service:
+	-cat trgenerator-service.yaml| sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+
+servicelb:
+	-cat trgenerator-service-lb.yaml| sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+
+borraclaims:
+	-cat pvc-trgenerator.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
+	-cat pvc-static.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
+
+claims:
+	-cat pvc-trgenerator.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+	-cat pvc-static.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+
+app:
+	-cat trgenerator-deployment.yaml | sed -e "s/<NS>/${NAMESPACE}/" | sed -e "s/<TAG>/${IMG_VERSION}/" | sed -e "s/<REGISTRO>/${REGISTRO}/" | kubectl delete -f -
+	-cat trgenerator-deployment.yaml | sed -e "s/<NS>/${NAMESPACE}/" | sed -e "s/<TAG>/${IMG_VERSION}/" | sed -e "s/<REGISTRO>/${REGISTRO}/" | kubectl create -f -
+
+db:
+	-kubectl create -f trgenerator-db.yaml

+ 2 - 0
K8S/creaCredencialesRegistry.sh

@@ -0,0 +1,2 @@
+# Añadir lo que salga a la directiva .dockerconfigjson en el fichero de secretos reg-secret.yaml
+cat /home/kubeadmin/.docker/config.json |base64 -w 0

+ 17 - 0
K8S/env-prod-configmap.yaml

@@ -0,0 +1,17 @@
+apiVersion: v1
+data:
+  DEBUG: "False"
+  APP_VERSION: 16.0.6
+  SQL_DATABASE: reymota
+  SQL_ENGINE: django.db.backends.postgresql
+  SQL_HOST: postgres-cluster-rw.reymota-pg-cluster.svc.cluster.local
+  SQL_PASSWORD: Dsa-0213
+  SQL_PORT: "5432"
+  SQL_USER: creylopez
+  DATABASE: postgres
+kind: ConfigMap
+metadata:
+  labels:
+    io.kompose.service: web-env-prod
+  name: env-prod
+  namespace: <NS>

+ 7 - 0
K8S/namespace.yaml

@@ -0,0 +1,7 @@
+###################################################
+# Namespace trgenerator
+###################################################
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: <NS>

+ 13 - 0
K8S/pvc-static.yaml

@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  labels:
+    io.kompose.service: static-volume
+  name: static-volume
+  namespace: <NS>
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 70Mi

+ 63 - 0
K8S/pvc-trgenerator.yaml

@@ -0,0 +1,63 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  creationTimestamp: null
+  labels:
+    io.kompose.service: trgenerator-media
+  name: trgenerator-media
+  namespace: <NS>
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 10Mi
+status: {}
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  creationTimestamp: null
+  labels:
+    io.kompose.service: trgenerator-textdocs-migrations
+  name: trgenerator-textdocs-migrations
+  namespace: <NS>
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 50Mi
+status: {}
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  creationTimestamp: null
+  labels:
+    io.kompose.service: trgenerator-gestionreservas-migrations
+  name: trgenerator-gestionreservas-migrations
+  namespace: <NS>
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 50Mi
+status: {}
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  creationTimestamp: null
+  labels:
+    io.kompose.service: trgenerator-siemensusers-migrations
+  name: trgenerator-siemensusers-migrations
+  namespace: <NS>
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 50Mi
+status: {}

+ 8 - 0
K8S/reg-secret.yaml

@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: myregistrykey
+  namespace: <NS>
+data:
+  .dockerconfigjson: ewoJImF1dGhzIjogewoJCSJnaXRzZXJ2ZXIucmV5bW90YS5lcyI6IHsKCQkJImF1dGgiOiAiWTNKbGVXeHZjR1Y2T2xKbGVTMHhNVGMyIgoJCX0sCgkJImhhcmJvci5yZXltb3RhLmVzIjogewoJCQkiYXV0aCI6ICJZM0psZVd4dmNHVjZPbmhsZG5wdmJpMTRkWEJvYjNvdE1WSjFkM052IgoJCX0sCgkJInJlZ2lzdHJ5LnJleW1vdGEuZXMiOiB7CgkJCSJhdXRoIjogIlkzSmxlV3h2Y0dWNk9sSmxlUzB4TVRjMiIKCQl9Cgl9Cn0=
+type: kubernetes.io/dockerconfigjson

+ 10 - 0
K8S/trgenerator-db.yaml

@@ -0,0 +1,10 @@
+apiVersion: postgresql.cnpg.io/v1
+kind: Database
+metadata:
+  name: trgenerator-db
+  namespace: reymota-pg-cluster
+spec:
+  name: trgenerator
+  owner: creylopez
+  cluster:
+    name: postgres-cluster

+ 123 - 0
K8S/trgenerator-deployment.yaml

@@ -0,0 +1,123 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: trgenerator
+  namespace: <NS>
+  labels:
+    app: trgenerator
+    app.kubernetes.io/part-of: trgenerator
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: trgenerator
+  strategy:
+    type: Recreate
+  template:
+    metadata:
+      labels:
+        app: trgenerator
+    spec:
+      containers:
+      - name: trgenerator
+        image: <REGISTRO>/trgenerator:<TAG>
+        env:
+        - name: IMG_VERSION
+          value: "<TAG>"
+        - name: DEBUG
+          valueFrom:
+            configMapKeyRef:
+              key: DEBUG
+              name: env-prod
+        - name: APP_VERSION
+          valueFrom:
+            configMapKeyRef:
+              key: APP_VERSION
+              name: env-prod
+        - name: MES_COMIENZO_TEMPORADA
+          valueFrom:
+            configMapKeyRef:
+              key: MES_COMIENZO_TEMPORADA
+              name: env-prod
+        - name: DATABASE
+          valueFrom:
+            configMapKeyRef:
+              key: DATABASE
+              name: env-prod
+        - name: SQL_HOST
+          valueFrom:
+            configMapKeyRef:
+              key: SQL_HOST
+              name: env-prod
+        - name: SQL_PORT
+          valueFrom:
+            configMapKeyRef:
+              key: SQL_PORT
+              name: env-prod
+        - name: SQL_ENGINE
+          valueFrom:
+            configMapKeyRef:
+              key: SQL_ENGINE
+              name: env-prod
+        - name: SQL_DATABASE
+          valueFrom:
+            configMapKeyRef:
+              key: SQL_DATABASE
+              name: env-prod
+        - name: SQL_USER
+          valueFrom:
+            configMapKeyRef:
+              key: SQL_USER
+              name: env-prod
+        - name: SQL_PASSWORD
+          valueFrom:
+            configMapKeyRef:
+              key: SQL_PASSWORD
+              name: env-prod        
+        - name: ADMIN_EMAIL
+          valueFrom:
+            configMapKeyRef:
+              key: ADMIN_EMAIL
+              name: env-prod
+        ports:
+        - containerPort: 8000
+          protocol: TCP
+        volumeMounts:
+        - mountPath: /app/mediafiles
+          name: trgenerator-media
+
+        - mountPath: /app/textdocs/migrations
+          name: trgenerator-textdocs-migrations
+
+        - mountPath: /app/doc_generator/migrations
+          name: trgenerator-gestionreservas-migrations
+
+        - mountPath: /app/siemensusers/migrations
+          name: trgenerator-siemensusers-migrations
+
+        - mountPath: /app/staticfiles
+          name: static-volume
+      imagePullSecrets:
+      - name: myregistrykey
+      restartPolicy: Always
+      volumes:
+      - name: trgenerator-media
+        persistentVolumeClaim:
+          claimName: trgenerator-media
+
+      - name: trgenerator-textdocs-migrations
+        persistentVolumeClaim:
+          claimName: trgenerator-textdocs-migrations
+
+      - name: trgenerator-gestionreservas-migrations
+        persistentVolumeClaim:
+          claimName: trgenerator-gestionreservas-migrations
+          
+      - name: trgenerator-siemensusers-migrations
+        persistentVolumeClaim:
+          claimName: trgenerator-siemensusers-migrations
+
+      - name: static-volume
+        persistentVolumeClaim:
+          claimName: static-volume
+status: {}

+ 31 - 0
K8S/trgenerator-ingress.yaml

@@ -0,0 +1,31 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  generation: 1
+  managedFields:
+    - apiVersion: networking.k8s.io/v1
+      fieldsType: FieldsV1
+      fieldsV1:
+        f:spec:
+          f:defaultBackend:
+            .: {}
+            f:service:
+              .: {}
+              f:name: {}
+              f:port: {}
+          f:rules: {}
+      manager: rancher
+      operation: Update
+  name: trgenerator
+  namespace: <NS>
+spec:
+  defaultBackend:
+    service:
+      name: trgenerator
+      port:
+        number: 8080
+  ingressClassName: nginx
+  rules:
+    - host: jalp.ser7.lab
+status:
+  loadBalancer: {}

+ 15 - 0
K8S/trgenerator-service-lb.yaml

@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: trgenerator-lb
+  namespace: <NS>
+  annotations:
+    metallb.io/loadBalancerIPs: 192.168.1.203
+spec:
+  type: LoadBalancer
+  ports:
+    - name: "80"
+      port: 80
+      targetPort: 8080
+  selector:
+    app: trgenerator

+ 15 - 0
K8S/trgenerator-service.yaml

@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: trgenerator
+  namespace: <NS>
+spec:
+  type: NodePort
+  ports:
+    - name: "8080"
+      port: 8080
+      nodePort: 30343
+      targetPort: 8080
+  selector:
+    app: trgenerator
+---

+ 6 - 0
Makefile

@@ -0,0 +1,6 @@
+install:
+
+	echo "Creando imagen con version '${IMG_VERSION}' en el registry '${REGISTRO}'"
+
+	docker build --no-cache -t ${REGISTRO}/trgenerator:${IMG_VERSION} -f Dockerfile .
+	docker push ${REGISTRO}/trgenerator:${IMG_VERSION}

+ 31 - 0
builder.yaml

@@ -0,0 +1,31 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: kaniko
+  namespace: jenkins
+spec:
+  hostAliases:
+  - ip: "192.168.1.116"
+    hostnames:
+    - "api.ocp-cluster.reymota.lab"
+  containers:
+  - name: kubectl
+    image: joshendriks/alpine-k8s
+    command:
+    - /bin/cat
+    tty: true    
+  - name: kaniko
+    image: gcr.io/kaniko-project/executor:debug
+    command:
+    - /busybox/cat
+    tty: true
+    volumeMounts:
+      - name: kaniko-secret
+        mountPath: /kaniko/.docker
+  volumes:
+    - name: kaniko-secret
+      secret:
+        secretName: regcred
+        items:
+          - key: .dockerconfigjson
+            path: config.json

BIN
doc_generator/__pycache__/__init__.cpython-313.pyc


BIN
doc_generator/__pycache__/settings.cpython-313.pyc


BIN
doc_generator/__pycache__/urls.cpython-313.pyc


BIN
doc_generator/__pycache__/wsgi.cpython-313.pyc


+ 43 - 0
nginx/default.conf

@@ -0,0 +1,43 @@
+upstream django_project {
+    server localhost:8000;
+}
+
+error_log /var/log/nginx/error.log;
+
+server {
+    listen       8080;
+
+    access_log /var/log/nginx/access.log;
+
+    location / {
+        proxy_pass http://django_project;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+	proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header Host $host;
+        proxy_redirect off;
+        client_max_body_size 100M;
+    }
+
+    location /static/ {
+        alias /app/staticfiles/;
+    }
+
+    location /media/ {
+        alias /app/mediafiles/;
+	expires 1y;
+	add_header Cache-Control "public, max-age=31536000, immutable";
+
+	# Optimize for small files, combine small packets, and send directly
+	tcp_nodelay on;
+	tcp_nopush on;
+	sendfile on;
+
+	# Logging off for high-traffic image serving
+	access_log off;
+    }
+
+    error_page   500 502 503 504  /50x.html;
+    location = /50x.html {
+        root   /usr/share/nginx/html;
+    }
+}

+ 1 - 0
src/accounts/.gitignore

@@ -0,0 +1 @@
+migrations/

+ 0 - 0
doc_generator/__init__.py → src/accounts/__init__.py


+ 0 - 0
textdocs/admin.py → src/accounts/admin.py


+ 6 - 0
src/accounts/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AccountsConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'accounts'

+ 3 - 0
src/accounts/models.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 0 - 0
textdocs/tests.py → src/accounts/tests.py


+ 14 - 0
src/accounts/urls.py

@@ -0,0 +1,14 @@
+# accounts/urls.py
+from django.urls import path
+from django.contrib.auth import views as auth_views
+from .views import SignUpView
+
+urlpatterns = [
+    path('login/', auth_views.LoginView.as_view(), name='login'),
+    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
+    path("signup/", SignUpView.as_view(), name="signup"),
+    path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
+    path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
+    path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
+    path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
+]

+ 12 - 0
src/accounts/views.py

@@ -0,0 +1,12 @@
+# accounts/views.py
+# from django.contrib.auth.forms import UserCreationForm
+from django.urls import reverse_lazy
+from django.views.generic import CreateView
+
+from siemensusers.forms import SiemensUserCreationForm
+
+
+class SignUpView(CreateView):
+    form_class = SiemensUserCreationForm
+    success_url = reverse_lazy("login")
+    template_name = "registration/signup.html"

+ 37 - 0
src/config/entrypoint.sh

@@ -0,0 +1,37 @@
+#!/bin/bash
+RUN_PORT="8000"
+
+if [ "$DATABASE" = "postgres" ]
+then
+    echo "Esperando a postgres..."
+
+    while ! nc -z $SQL_HOST $SQL_PORT; do
+      sleep 0.1
+    done
+
+    echo "PostgreSQL ha arrancado"
+
+	python manage.py migrate --no-input
+	python manage.py collectstatic --no-input
+
+  # Start nginx as a daemon so it doesn't block, then run gunicorn in
+  # foreground (PID 1) so logs go to stdout/stderr and are visible to kubectl logs
+  nginx
+
+  # Determine log level
+  if [ "$DEBUG" = "True" ] || [ "$DEBUG" = "1" ]; then
+    LOG_LEVEL=debug
+  else
+    LOG_LEVEL=info
+  fi
+
+  echo "Lanzando gunicorn con log level: $LOG_LEVEL"
+
+  exec gunicorn doc_generator.wsgi:application \
+    --bind "0.0.0.0:${RUN_PORT}" \
+    --access-logfile - \
+    --error-logfile - \
+    --log-level "$LOG_LEVEL"
+else
+    echo "la base de datos no es postgres: '$DATABASE'"
+fi

+ 0 - 0
textdocs/__init__.py → src/doc_generator/__init__.py


+ 0 - 0
doc_generator/asgi.py → src/doc_generator/asgi.py


+ 1 - 0
doc_generator/settings.py → src/doc_generator/settings.py

@@ -40,6 +40,7 @@ INSTALLED_APPS = [
     
     'tinymce',  # For rich text editing
     'textdocs',
+    'siemensusers',
 ]
 
 MIDDLEWARE = [

+ 0 - 0
doc_generator/urls.py → src/doc_generator/urls.py


+ 0 - 0
doc_generator/wsgi.py → src/doc_generator/wsgi.py


+ 0 - 0
manage.py → src/manage.py


+ 0 - 0
requirements.txt → src/requirements.txt


+ 0 - 0
textdocs/migrations/__init__.py → src/siemensusers/__init__.py


+ 64 - 0
src/siemensusers/admin.py

@@ -0,0 +1,64 @@
+from django.contrib import admin
+from django.core.mail import send_mail
+from django.contrib import messages
+
+from django.contrib.auth.admin import UserAdmin
+
+# Register your models here.
+
+from siemensusers.models import SiemensUser
+from siemensusers.forms import SiemensUserCreationForm, SiemensUserChangeForm
+
+
+def enviar_email_prueba(modeladmin, request, queryset):
+    for user in queryset:
+        if user.email:
+            send_mail(
+                'Correo de Prueba',
+                'Este es un correo de prueba desde el panel de administración.',
+                'jugaralpadelentreamigos@gmail.com',  # Remitente
+                [user.email],  # Destinatario
+                fail_silently=False,
+            )
+            messages.success(request, f'Se ha enviado un correo de prueba a {user.email}')
+        else:
+            messages.warning(request, f'El usuario {user.username} no tiene un correo registrado.')
+
+
+enviar_email_prueba.short_description = 'Enviar correo de prueba a usuario(s) seleccionado(s)'
+
+
+class SiemensUserAdmin(UserAdmin):
+    add_form = SiemensUserCreationForm
+    form = SiemensUserChangeForm
+    model = SiemensUser
+    list_display = ("email", "nombre", "is_staff", "is_active", "foto")
+    list_filter = ("email", "nombre", "is_staff", "is_active",)
+    fieldsets = (
+        (None, {"fields": ("email", "password")}),
+        ("Personal", {"fields": ("nombre",)}),
+        ("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"
+                )
+            }
+        ),
+
+        ("Personal", {"fields": ("nombre",)}),
+        ("Varios", {"fields": ("foto",)}),
+    )
+    search_fields = ("email", "nombre",)
+    ordering = ("email",)
+    actions = [enviar_email_prueba]  # Añadir la acción a la lista de acciones
+
+
+admin.site.register(SiemensUser, SiemensUserAdmin)

+ 6 - 0
src/siemensusers/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ReymotausersConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'siemensusers'

+ 19 - 0
src/siemensusers/forms.py

@@ -0,0 +1,19 @@
+from django import forms
+from django.contrib.auth.forms import UserCreationForm, UserChangeForm
+
+from .models import SiemensUser
+
+
+class SiemensUserCreationForm(UserCreationForm):
+
+    class Meta:
+        model = SiemensUser
+        fields = ("email", "nombre", "foto")
+        labels = {'email': 'Dirección de correo'}
+
+
+class SiemensUserChangeForm(UserChangeForm):
+
+    class Meta:
+        model = SiemensUser
+        fields = ("email", "foto")

+ 0 - 0
src/siemensusers/management/__init__.py


+ 0 - 0
src/siemensusers/management/commands/__init__.py


+ 46 - 0
src/siemensusers/management/commands/importar_usuarios.py

@@ -0,0 +1,46 @@
+import json
+from django.core.management.base import BaseCommand
+from siemensusers.models import SiemensUser
+
+
+class Command(BaseCommand):
+    help = "Importa usuarios desde un archivo JSON"
+
+    def add_arguments(self, parser):
+        parser.add_argument('archivo_json', type=str, help="Ruta del archivo JSON")
+
+    def handle(self, *args, **kwargs):
+        archivo_json = kwargs['archivo_json']
+
+        try:
+            with open(archivo_json, 'r', encoding='utf-8') as file:
+                datos = json.load(file)
+
+            self.stdout.write(self.style.WARNING(f"\nSe encontraron {len(datos)} usuarios en el archivo '{archivo_json}'."))
+            confirmar = input("¿Deseas continuar con la importación? (s/n): ").strip().lower()
+
+            if confirmar != 's':
+                self.stdout.write(self.style.ERROR("Importación cancelada."))
+                return
+
+            usuarios_creados = 0
+            for usuario_data in datos:
+                creado = SiemensUser.objects.create(
+                    foto=usuario_data['foto'],
+                    password=usuario_data['password'],
+                    is_superuser=usuario_data['is_superuser'],
+                    is_staff=usuario_data['is_staff'],
+                    is_active=usuario_data['is_active'],
+                    nombre=usuario_data['nombre'],
+                    email=usuario_data['email'],
+                    last_login=usuario_data['last_login']
+                )
+                if creado:
+                    usuarios_creados += 1
+
+            self.stdout.write(self.style.SUCCESS(f'Se importaron {usuarios_creados} usuarios correctamente.'))
+
+        except FileNotFoundError:
+            self.stderr.write(self.style.ERROR(f"El archivo {archivo_json} no se encontró."))
+        except json.JSONDecodeError:
+            self.stderr.write(self.style.ERROR("Error al leer el archivo JSON. Asegúrate de que el formato sea correcto."))

+ 34 - 0
src/siemensusers/managers.py

@@ -0,0 +1,34 @@
+from django.contrib.auth.base_user import BaseUserManager
+from django.utils.translation import gettext_lazy as _
+
+
+class SiemensUserManager(BaseUserManager):
+    """
+    ReyMota 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)

+ 1 - 0
src/siemensusers/migrations/noborrar

@@ -0,0 +1 @@
+no borrar para que el directorio migrations se cree

+ 24 - 0
src/siemensusers/models.py

@@ -0,0 +1,24 @@
+from django.db import models
+
+# Create your models here.
+from django.utils.translation import gettext_lazy as _
+from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
+from .managers import SiemensUserManager
+
+# Create your models here.
+
+
+class SiemensUser(AbstractBaseUser, PermissionsMixin):
+    email = models.EmailField(_("email address"), unique=True)
+    foto = models.ImageField(upload_to="profile_images", default="profile_images/default.jpg", blank=True)
+    is_staff = models.BooleanField(default=False)
+    is_active = models.BooleanField(default=True)
+    nombre = models.CharField(max_length=200, blank=True, null=True)
+
+    USERNAME_FIELD = "email"
+    REQUIRED_FIELDS = []
+
+    objects = SiemensUserManager()
+
+    def __str__(self):
+        return self.email

+ 16 - 0
src/siemensusers/serializers.py

@@ -0,0 +1,16 @@
+import os
+
+from rest_framework import serializers
+from .models import SiemensUser
+
+
+class SiemensUserSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = SiemensUser
+        fields = ['email', 'password', 'foto', 'is_superuser', 'is_staff', 'is_active', 'nombre', 'last_login']
+
+    def to_representation(self, instance):
+        ret = super().to_representation(instance)
+        ret['foto'] = "profile_images/" + os.path.basename(ret['foto'])
+
+        return ret

+ 42 - 0
src/siemensusers/tests.py

@@ -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)

+ 9 - 0
src/siemensusers/urls.py

@@ -0,0 +1,9 @@
+from django.urls import path
+
+from . import views
+from .views import api_lista_usuarios, api_detalle_usuario
+
+urlpatterns = [
+    path('api/usuarios/', api_lista_usuarios, name='api_lista_usuarios'),
+    path('api/usuarios/<int:usuario_id>/', api_detalle_usuario, name='api_detalle_usuario'),
+]

+ 29 - 0
src/siemensusers/views.py

@@ -0,0 +1,29 @@
+from django.shortcuts import render
+
+from rest_framework.response import Response
+from rest_framework.decorators import api_view
+
+from .serializers import SiemensUserSerializer
+
+from .models import SiemensUser
+
+# Create your views here.
+
+
+@api_view(['GET'])
+def api_lista_usuarios(request):
+    """Devuelve la lista de todos los usuarios."""
+    usuarios = SiemensUser.objects.all()
+    serializer = SiemensUserSerializer(usuarios, many=True)
+    return Response(serializer.data)
+
+
+@api_view(['GET'])
+def api_detalle_usuario(request, usuario_id):
+    """Devuelve los detalles de un usuario específico."""
+    try:
+        usuario = SiemensUser.objects.get(id=usuario_id)
+        serializer = SiemensUserSerializer(usuario)
+        return Response(serializer.data)
+    except SiemensUser.DoesNotExist:
+        return Response({'error': 'Canción no encontrada'}, status=404)

+ 0 - 0
templates/textdocs/base.html → src/templates/textdocs/base.html


+ 0 - 0
templates/textdocs/document_confirm_delete.html → src/templates/textdocs/document_confirm_delete.html


+ 0 - 0
templates/textdocs/document_detail.html → src/templates/textdocs/document_detail.html


+ 0 - 0
templates/textdocs/document_form.html → src/templates/textdocs/document_form.html


+ 0 - 0
templates/textdocs/document_list.html → src/templates/textdocs/document_list.html


+ 0 - 0
templates/textdocs/document_pdf.html → src/templates/textdocs/document_pdf.html


+ 0 - 0
src/textdocs/__init__.py


+ 3 - 0
src/textdocs/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 0 - 0
textdocs/apps.py → src/textdocs/apps.py


+ 0 - 0
textdocs/forms.py → src/textdocs/forms.py


+ 0 - 0
textdocs/migrations/0001_initial.py → src/textdocs/migrations/0001_initial.py


+ 0 - 0
src/textdocs/migrations/__init__.py


+ 0 - 0
textdocs/models.py → src/textdocs/models.py


+ 3 - 0
src/textdocs/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 0 - 0
textdocs/urls.py → src/textdocs/urls.py


+ 0 - 0
textdocs/views.py → src/textdocs/views.py


BIN
textdocs/__pycache__/__init__.cpython-313.pyc


BIN
textdocs/__pycache__/admin.cpython-313.pyc


BIN
textdocs/__pycache__/apps.cpython-313.pyc


BIN
textdocs/__pycache__/forms.cpython-313.pyc


BIN
textdocs/__pycache__/models.cpython-313.pyc


BIN
textdocs/__pycache__/urls.cpython-313.pyc


BIN
textdocs/__pycache__/views.cpython-313.pyc


BIN
textdocs/migrations/__pycache__/0001_initial.cpython-313.pyc


BIN
textdocs/migrations/__pycache__/__init__.cpython-313.pyc