diff --git a/becas_cozcyt/settings.py b/becas_cozcyt/settings.py index ce6d9a67e908bac9d905233320fffe98f7475f06..277b7433eb9f75862968d3b47bba3dbf938fe531 100644 --- a/becas_cozcyt/settings.py +++ b/becas_cozcyt/settings.py @@ -62,6 +62,12 @@ MIDDLEWARE = [ "django_htmx.middleware.HtmxMiddleware", ] +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + } +} + ROOT_URLCONF = 'becas_cozcyt.urls' TEMPLATES = [ @@ -75,6 +81,7 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'usuarios.context_processors.cache_version', ], }, }, diff --git a/solicitudes/viewsAdmin.py b/solicitudes/viewsAdmin.py index f77ce534d1fee6cac9c0b02a052a6fbe748093f5..b5df93ab5e64eeb007078329c7b0962f5c26501a 100644 --- a/solicitudes/viewsAdmin.py +++ b/solicitudes/viewsAdmin.py @@ -99,8 +99,7 @@ ESTADISTICAS_SOLICITUD_CHOICES = [ ] ESTADISTICAS_SOLICITUD_CHOICES_EXCLUDE_USER = [ - 'puntaje', - 'estado solicitud', + 'puntaje', ] TODOS_STR = 'Todas' @@ -121,7 +120,7 @@ MAPEO_SOLICITUDES_CHOICES_ADMIN = { MAPEO_SOLICITUDES_CHOICES_USER = { 'modalidad': 'modalidad__nombre', 'ciclo': 'ciclo__nombre', - #'estado solicitud': 'estado', + 'estado solicitud': 'estado', 'tipo': 'tipo', 'genero': 'solicitante__genero', 'instituciones': 'solicitante__carrera__institucion__nombre', diff --git a/testMigracion.py b/testMigracion.py new file mode 100644 index 0000000000000000000000000000000000000000..07f2ba9c227d35f1845c11b58603feac450a8426 --- /dev/null +++ b/testMigracion.py @@ -0,0 +1,12 @@ +import os +import django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'becas_cozcyt.settings') +django.setup() +from django.contrib.contenttypes.models import ContentType +from usuarios.models import Solicitante, Usuario, SiteColor +from django.conf import settings +from django.contrib.auth.models import Permission, Group, User + + + +load_colors_from_css() \ No newline at end of file diff --git a/usuarios/admin.py b/usuarios/admin.py index e9c9f6b4b692539da8feda0308a5144ade8e2c3e..5c0d08f942caff1600dfc153177f5172b4713150 100644 --- a/usuarios/admin.py +++ b/usuarios/admin.py @@ -65,4 +65,6 @@ admin.site.register(Carrera) admin.site.register(Institucion) admin.site.register(Municipio) admin.site.register(Estado) +admin.site.register(SiteColor) +admin.site.register(CacheVersion) diff --git a/usuarios/context_processors.py b/usuarios/context_processors.py new file mode 100644 index 0000000000000000000000000000000000000000..6aadeeee59a537b0083c9118314540a70d1ebe76 --- /dev/null +++ b/usuarios/context_processors.py @@ -0,0 +1,5 @@ +from .models import CacheVersion + +def cache_version(request): + version = CacheVersion.get_object() + return {'cache_version': version.version if version else ''} \ No newline at end of file diff --git a/usuarios/forms.py b/usuarios/forms.py index 0f3fe38da749cc0b8509a67204074f4e5cd0b61a..72220b8a027befbb98d9102482768ec76efdcf80 100644 --- a/usuarios/forms.py +++ b/usuarios/forms.py @@ -340,4 +340,25 @@ class AgregarAdminForm(UserCreationForm): } password1 = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control form-control-lg my-3', 'placeholder': 'Contraseña'})) - password2 = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control form-control-lg my-3', 'placeholder': 'Confirme su contraseña'})) \ No newline at end of file + password2 = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control form-control-lg my-3', 'placeholder': 'Confirme su contraseña'})) + + +class SiteColorForm(forms.ModelForm): + class Meta: + model = SiteColor + fields = ['color'] + labels = { + 'color': 'Color', + } + widgets = { + #'nombre': forms.TextInput(attrs={'disabled': 'disabled'}), + 'color': forms.TextInput(attrs={'class': 'jscolor', 'data-jscolor': '{}', }), + } + + def clean_value(self): + value = self.cleaned_data.get('value') + if not SiteColor.is_valid_color(value): + raise forms.ValidationError("Formato de color invalido. Usa #000000, rgb(0,0,0) o rgba(0,0,0).") + return value + +SiteColorFormSet = modelformset_factory(SiteColor, form=SiteColorForm, extra=0, can_delete=False) \ No newline at end of file diff --git a/usuarios/migrations/0002_creacion_SiteColor.py b/usuarios/migrations/0002_creacion_SiteColor.py new file mode 100644 index 0000000000000000000000000000000000000000..5ad7046ebf21d9c7f9d1fe4ad841258fa11895ba --- /dev/null +++ b/usuarios/migrations/0002_creacion_SiteColor.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.13 on 2024-05-16 19:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='SiteColor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, unique=True)), + ('color', models.CharField(max_length=20)), + ], + ), + ] diff --git a/usuarios/migrations/0003_alter_sitecolor_options_alter_sitecolor_color.py b/usuarios/migrations/0003_alter_sitecolor_options_alter_sitecolor_color.py new file mode 100644 index 0000000000000000000000000000000000000000..e8f25792f8a24949393899f0ef986447ac620b24 --- /dev/null +++ b/usuarios/migrations/0003_alter_sitecolor_options_alter_sitecolor_color.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.13 on 2024-05-16 21:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0002_creacion_SiteColor'), + ] + + operations = [ + migrations.AlterModelOptions( + name='sitecolor', + options={'ordering': ['id']}, + ), + migrations.AlterField( + model_name='sitecolor', + name='color', + field=models.CharField(max_length=30), + ), + ] diff --git a/usuarios/migrations/0004_crear_cache_version.py b/usuarios/migrations/0004_crear_cache_version.py new file mode 100644 index 0000000000000000000000000000000000000000..8246c49c6f7d123442843b6596c36832a877c3a1 --- /dev/null +++ b/usuarios/migrations/0004_crear_cache_version.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.13 on 2024-05-17 18:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0003_alter_sitecolor_options_alter_sitecolor_color'), + ] + + operations = [ + migrations.CreateModel( + name='CacheVersion', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('version', models.PositiveIntegerField(default=0, verbose_name='Version')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/usuarios/migrations/0005_aztializar_siteColors.py b/usuarios/migrations/0005_aztializar_siteColors.py new file mode 100644 index 0000000000000000000000000000000000000000..1f0c1364cfa8e6670fb944a37b7303ed130b984b --- /dev/null +++ b/usuarios/migrations/0005_aztializar_siteColors.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-05-17 19:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0004_crear_cache_version'), + ] + + operations = [ + migrations.RenameField( + model_name='sitecolor', + old_name='name', + new_name='nombre', + ), + ] diff --git a/usuarios/models.py b/usuarios/models.py index a210971ac47c9c0c8c375cf1c6e7ee7719ab2900..4b702b2a17e773d84d1bbb01ccaa52e9f161fdb6 100644 --- a/usuarios/models.py +++ b/usuarios/models.py @@ -6,7 +6,8 @@ from django.core.validators import MinLengthValidator, MaxLengthValidator from django.core.exceptions import ValidationError from django.db.models import Q from django.core.validators import MaxValueValidator, MinValueValidator -from modalidades.models import Ciclo +from modalidades.models import Ciclo, SingletonModel +import re class Estado(models.Model): @@ -276,4 +277,36 @@ class PuntajeMunicipio(models.Model): puntos = models.IntegerField(default=0) def __str__(self): - return f"PuntajeMun de {self.municipio} - {self.puntos} puntos" \ No newline at end of file + return f"PuntajeMun de {self.municipio} - {self.puntos} puntos" + +class SiteColor(models.Model): + nombre = models.CharField(max_length=50, unique=True) + color = models.CharField(max_length=30) + + class Meta: + ordering = ['id'] + + def __str__(self): + return f"{self.nombre}: {self.color}" + + def save(self, *args, **kwargs): + if not self.is_valid_color(self.color): + raise ValueError("Invalid color format") + if self.pk is not None: + original = SiteColor.objects.get(pk=self.pk) + self.nombre = original.nombre + super().save(*args, **kwargs) + + @staticmethod + def is_valid_color(value): + hex_pattern = re.compile(r'^#(?:[0-9a-fA-F]{3}){1,2}$') + rgb_pattern = re.compile(r'^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$') + rgba_pattern = re.compile(r'^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(0|1|0?\.\d+)\)$') + return bool(hex_pattern.match(value) or rgb_pattern.match(value) or rgba_pattern.match(value)) + + +class CacheVersion(SingletonModel): + version = models.PositiveIntegerField(verbose_name="Version", default=0) + + def __str__(self): + return self.version \ No newline at end of file diff --git a/usuarios/templates/admin/configColores.html b/usuarios/templates/admin/configColores.html new file mode 100644 index 0000000000000000000000000000000000000000..d71f4166218ec4ee6710b6a34cd600e60931a76c --- /dev/null +++ b/usuarios/templates/admin/configColores.html @@ -0,0 +1,99 @@ +{% extends 'base.html' %} + +{% block titulo %} +Configuración colores +{% endblock titulo %} + +{% block body %} + {% include 'admin/config_nav.html' %} + + +
+
+
+

Configuración de color del sitio

+
+
+
+
+
+
+ {% csrf_token %} + {{ formset.management_form }} + +
+
+
Color
+
Valor
+
+ +
+
+ {% for form in formset %} +
+
+
+ {{ form.instance.nombre }} +
+ {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} +
+
+ {{ form.color }} + {{ form.color.errors }} +
+
+ {% if forloop.counter|divisibleby:"10" and not forloop.first %} +
+
+ {% endif %} + {% endfor %} +
+
+
+ + +
+
+{% endblock body %} \ No newline at end of file diff --git a/usuarios/templates/admin/config_nav.html b/usuarios/templates/admin/config_nav.html index 7ca58f149c1801cad200b688e731cc2a06b0cec2..24f7868b56a6cba7b524cf33d4a82285d39438a0 100644 --- a/usuarios/templates/admin/config_nav.html +++ b/usuarios/templates/admin/config_nav.html @@ -21,7 +21,7 @@ Estudio socioeconómico {% endwith %} diff --git a/usuarios/templates/base_head.html b/usuarios/templates/base_head.html index c4cc4c55b1553379ecfb134a324133a3875e58d6..2b812e627247ced098001ea950122c0e791889d9 100644 --- a/usuarios/templates/base_head.html +++ b/usuarios/templates/base_head.html @@ -21,6 +21,9 @@ + + + diff --git a/usuarios/urls.py b/usuarios/urls.py index d358b6d225792eadf76607b38a0791296f488287..2a3086d6d1f0a8f98c7a1b048de781f995d97917 100644 --- a/usuarios/urls.py +++ b/usuarios/urls.py @@ -51,6 +51,7 @@ urlpatterns = [ path('perfil/',views.perfil, name='perfil'), path('mensajes/',views.sMensajes, name='mensajes'), + path('colores/', viewsAdmin.cargarColoresCSS, name='colores'), @@ -69,4 +70,11 @@ urlpatterns = [ path('administracion/editarUsuario//', viewsAdmin.editarUsuario, name='AEditarUsuario'), path('administracion/eliminarUsuario//', viewsAdmin.eliminarUsuario, name='AEliminarUsuario'), path('administracion/reEnviarConfirmaciones/', viewsAdmin.reEnviarConfirmaciones, name='AReEnviarConfirmaciones'), + + path('administracion/configuracion/colores/', viewsAdmin.configColores, name='AConfigColores'), + path('administracion/recargarColoresDefault/', viewsAdmin.load_colors_from_css, name='AReCargarColoresDefault'), + path('administracion/reset_cache/colores', viewsAdmin.reset_cache_and_new_version, + kwargs={'view': 'usuarios:colores',}, + name='AResetCacheColors'), + ] \ No newline at end of file diff --git a/usuarios/viewsAdmin.py b/usuarios/viewsAdmin.py index 60679bad6cb5b1c09b45d879d469ee9067f24a08..2e798c938d4b5b171a9338cba2572cad5140d23c 100644 --- a/usuarios/viewsAdmin.py +++ b/usuarios/viewsAdmin.py @@ -25,6 +25,7 @@ from solicitudes.models import * #decoradores from .decorators import user_passes_test, user_passes_test_httpresponse, usuarioEsAdmin +from django.views.decorators.cache import cache_page @login_required @@ -475,4 +476,87 @@ def agregarAdmin(request, pk=None): 'postUrl': postUrl, 'modalForm': form, } - return render(request, 'modal_base.html', context) \ No newline at end of file + return render(request, 'modal_base.html', context) + + +@login_required +@user_passes_test(usuarioEsAdmin) +def load_colors_from_css(request): + css_file_path = os.path.join(settings.BASE_DIR, 'static', 'css', 'colores.css') + print(css_file_path) + print(os.path.exists(css_file_path)) + if os.path.exists(css_file_path): + SiteColor.objects.all().delete() + with open(css_file_path, 'r') as f: + lines = f.readlines() + for line in lines: + if line.strip().startswith('--'): + name, value = line.strip().split(':') + name = name.strip()[2:] # Remove the leading '--' + value = value.strip().rstrip(';') # Remove the trailing ';' + if SiteColor.is_valid_color(value): + print(f'{name} - {value}') + SiteColor.objects.create(nombre=name, color=value) + else : + print(f'No se pudo crear el SiteColor: {name} with value {value}') + messages.success(request,'colores.css importado con exito') + else: + messages.error(request,'No se pudo encontrar colores.css') + return redirect('usuarios:AConfigColores') + + +@cache_page(None) # won't expire, ever +def cargarColoresCSS(request): + colors = SiteColor.objects.all() + css = ":root {\n" + for color in colors: + css += f" --{color.nombre}: {color.color};\n" + css += "}\n" + return HttpResponse(css, content_type='text/css') + +@login_required +@user_passes_test(usuarioEsAdmin) +def reset_cache_and_new_version(request, view='usuarios:colores', args=None): + from django.core.cache import cache + from django.utils.cache import get_cache_key + + if args is None: + path = reverse(view) + else: + path = reverse(view, args=args) + + request.path = path + key = get_cache_key(request) + if key in cache: + print(f'existe un cache de "{key}": eliminando') + cache.delete(key) + else: + print(f'no se encontro un cache de "{key}": no se pudo eliminar') + + #crear nueva vercion de cache + version = CacheVersion.get_object() + version.version = version.version + 1 + version.save() + + return redirect('usuarios:AConfigColores') + + + +@login_required +@user_passes_test(usuarioEsAdmin) +def configColores(request): + if request.method == 'POST': + formset = SiteColorFormSet(request.POST, queryset=SiteColor.objects.all()) + if formset.is_valid(): + formset.save() + messages.success(request, 'Nueva vercion del esquema de colores del sitio guardada con exito.') + else: + messages.warning(request, 'No se pudo guardar el esquema de colores.') + messages.error(request, formset.errors) + else: + formset = SiteColorFormSet(queryset=SiteColor.objects.all()) + context = { + 'formset':formset, + } + return render(request, 'admin/configColores.html', context) +