Enzo Yair

test-enpoint

1 -from rest_framework import viewsets 1 +import json
  2 +from rest_framework import viewsets,mixins
2 from rest_framework.permissions import IsAuthenticated 3 from rest_framework.permissions import IsAuthenticated
  4 +from rest_framework.decorators import action
  5 +from rest_framework import status
  6 +from rest_framework.response import Response
  7 +from rest_framework_api_key.permissions import HasAPIKey
  8 +
  9 +from datetime import datetime
3 10
4 from .models import Evento 11 from .models import Evento
5 from .serializers import EventoSerializer 12 from .serializers import EventoSerializer
  13 +from .service import GenerarFechas
6 14
7 15
8 -class EventoViewSets(viewsets.ReadOnlyModelViewSet): 16 +class EventoViewSets(
  17 + mixins.CreateModelMixin,
  18 + mixins.ListModelMixin,
  19 + mixins.RetrieveModelMixin,
  20 + viewsets.GenericViewSet,
  21 +):
9 queryset = Evento.objects.all().order_by('id') 22 queryset = Evento.objects.all().order_by('id')
10 serializer_class = EventoSerializer 23 serializer_class = EventoSerializer
11 - permission_classes = [IsAuthenticated,] 24 + permission_classes = [HasAPIKey | IsAuthenticated]
12 lookup_field = 'id' 25 lookup_field = 'id'
13 26
  27 + @action(methods=['POST'], detail=False, url_path='obtener-mes')
  28 + def obtener_mes(self, request):
  29 + mes_query = request.data.get('mes', None)
  30 +
  31 + if mes_query is None:
  32 + return Response(
  33 + data={
  34 + 'error':
  35 + 'El mes ingresado no es válido. '
  36 + 'Por favor, ingrese el mes correspondiente a la fecha actual o posterior.'
  37 + },
  38 + status=status.HTTP_400_BAD_REQUEST
  39 + )
  40 +
  41 + eventos = Evento.objects.filter(fecha_inicio__month=mes_query)
  42 + evento_json = []
  43 + for evento in eventos:
  44 + generar_fechas = GenerarFechas(evento=evento, mes=mes_query)
  45 + generar_fechas.procesar_fechas_evento() # Cambiado a 'procesar_fechas_evento()'
  46 + evento_json.append(generar_fechas.obtener_datos_evento())
  47 +
  48 + return Response(data=evento_json, status=status.HTTP_200_OK)
  49 +
  1 +DIAS_SEMANA = (
  2 + (1, 'Lunes'),
  3 + (2, 'Martes'),
  4 + (3, 'Miércoles'),
  5 + (4, 'Jueves'),
  6 + (5, 'Viernes'),
  7 + (6, 'Sábado'),
  8 + (7, 'Domingo'),
  9 +)
  1 +# Generated by Django 4.2.9 on 2024-10-08 01:39
  2 +
  3 +from django.db import migrations, models
  4 +
  5 +
  6 +class Migration(migrations.Migration):
  7 +
  8 + dependencies = [
  9 + ('evento', '0001_initial'),
  10 + ]
  11 +
  12 + operations = [
  13 + migrations.RemoveField(
  14 + model_name='fechaevento',
  15 + name='dia_evento',
  16 + ),
  17 + migrations.AddField(
  18 + model_name='fechaevento',
  19 + name='dias',
  20 + field=models.DateField(blank=True, editable=False, null=True),
  21 + ),
  22 + migrations.AddField(
  23 + model_name='fechaevento',
  24 + name='duracion_evento',
  25 + field=models.IntegerField(choices=[(1, 'Lunes'), (2, 'Martes'), (3, 'Miércoles'), (4, 'Jueves'), (5, 'Viernes'), (6, 'Sábado'), (7, 'Domingo')], default=1, verbose_name='Días de la semana'),
  26 + preserve_default=False,
  27 + ),
  28 + ]
@@ -3,6 +3,7 @@ from django.db import models @@ -3,6 +3,7 @@ from django.db import models
3 from django.core.validators import FileExtensionValidator 3 from django.core.validators import FileExtensionValidator
4 4
5 from organismo.models import Organismo, Dependencia 5 from organismo.models import Organismo, Dependencia
  6 +from .constant import DIAS_SEMANA
6 7
7 # Create your models here. 8 # Create your models here.
8 9
@@ -62,8 +63,8 @@ class FechaEvento(models.Model): @@ -62,8 +63,8 @@ class FechaEvento(models.Model):
62 class Meta: 63 class Meta:
63 verbose_name = 'Fecha del Eventos' 64 verbose_name = 'Fecha del Eventos'
64 verbose_name_plural = 'Fechas del Eventos' 65 verbose_name_plural = 'Fechas del Eventos'
65 -  
66 - dia_evento = models.DateField(verbose_name='Días del evento') 66 + duracion_evento = models.IntegerField(choices=DIAS_SEMANA, blank=False, verbose_name='Días de la semana')
  67 + dias = models.DateField(editable=False, blank=True, null=True)
67 68
68 def __str__(self): 69 def __str__(self):
69 - return f'{self.dia_evento}'  
  70 + return f'{self.duracion_evento}'
  1 +import json
  2 +import calendar
  3 +import pytz
  4 +
  5 +from django.core.serializers.json import DjangoJSONEncoder
  6 +from datetime import datetime, timedelta, time
  7 +
  8 +from .models import Evento, FechaEvento
  9 +
  10 +
  11 +class GenerarFechas:
  12 + def __init__(self, evento: Evento, mes: int):
  13 + self.fechas_generadas = {}
  14 + self.evento = evento
  15 + self.mes = mes
  16 +
  17 + def obtener_rango_del_mes(self):
  18 + """Obtiene el rango de fechas para el mes específico."""
  19 + year = datetime.now().year
  20 + self.mes = int(self.mes)
  21 + primer_dia, ultimo_dia = calendar.monthrange(year, self.mes)
  22 + fecha_inicio_mes = datetime(year, self.mes, 1)
  23 + fecha_fin_mes = datetime(year, self.mes, ultimo_dia)
  24 + return fecha_inicio_mes, fecha_fin_mes
  25 +
  26 + def ajustar_fechas_a_rango_mes(self):
  27 + """Ajusta las fechas del evento al rango del mes especificado."""
  28 + zona_horaria = pytz.timezone('America/Argentina/Buenos_Aires')
  29 +
  30 + fecha_inicio_evento = self.combinar_fecha_hora(self.evento.fecha_inicio, self.evento.hora_inicio, zona_horaria)
  31 + fecha_fin_evento = self.combinar_fecha_hora(self.evento.fecha_final, self.evento.hora_fin, zona_horaria)
  32 +
  33 + fecha_inicio_mes, fecha_fin_mes = self.obtener_rango_del_mes()
  34 + fecha_inicio_evento = max(fecha_inicio_evento, zona_horaria.localize(fecha_inicio_mes))
  35 + fecha_fin_evento = min(fecha_fin_evento, zona_horaria.localize(fecha_fin_mes))
  36 +
  37 + return fecha_inicio_evento, fecha_fin_evento
  38 +
  39 + @staticmethod
  40 + def combinar_fecha_hora(fecha, hora, zona_horaria):
  41 + """Combina la fecha y hora en un solo objeto datetime."""
  42 + if fecha is None or hora is None:
  43 + return None
  44 + fecha_hora = datetime.combine(fecha, hora)
  45 + return zona_horaria.localize(fecha_hora) if fecha_hora.tzinfo is None else fecha_hora
  46 +
  47 + def obtener_dias_del_evento(self):
  48 + """Obtiene los días de la semana en los que se realiza el evento (en formato 1 a 7)."""
  49 + dias_evento = []
  50 + for fecha in self.evento.fechas.all():
  51 + if fecha.duracion_evento is not None: # Solo considerar fechas con duracion_evento definido
  52 + dias_evento.append(int(fecha.duracion_evento)) # Convertir a entero y agregar a la lista
  53 + return dias_evento
  54 +
  55 + def procesar_fechas_evento(self):
  56 + """Genera las fechas del evento ajustadas al mes especificado."""
  57 + fecha_inicio, fecha_fin = self.ajustar_fechas_a_rango_mes()
  58 + dias_evento = self.obtener_dias_del_evento()
  59 + fechas_null = self.evento.fechas.filter(dias__isnull=True)
  60 +
  61 + self.asignar_fechas_nulas(fechas_null, dias_evento, fecha_inicio)
  62 + self.generar_fechas_para_dias_especificos(fecha_inicio, fecha_fin, dias_evento)
  63 +
  64 + return self.fechas_generadas
  65 +
  66 + def asignar_fechas_nulas(self, fechas_null, dias_evento, fecha_inicio):
  67 + """Asigna fechas a las entradas que no tienen días especificados."""
  68 + for fecha_obj in fechas_null:
  69 + dia_semana = fecha_inicio.weekday() + 1 # Ajustar de 0-6 a 1-7
  70 + if dia_semana in dias_evento:
  71 + fecha_obj.dias = fecha_inicio.date()
  72 + fecha_obj.save(update_fields=['dias'])
  73 + self.fechas_generadas[fecha_inicio.strftime('%Y-%m-%d')] = fecha_obj.pk
  74 +
  75 + def generar_fechas_para_dias_especificos(self, fecha_inicio, fecha_fin, dias_evento):
  76 + """Genera fechas adicionales dentro del rango si coinciden con los días seleccionados."""
  77 + fecha_actual = fecha_inicio
  78 + while fecha_actual <= fecha_fin:
  79 + dia_semana = fecha_actual.weekday() + 1 # Ajustar de 0-6 a 1-7
  80 + if dia_semana in dias_evento:
  81 + self.agregar_fecha(fecha_actual, dia_semana)
  82 + fecha_actual += timedelta(days=1)
  83 +
  84 + def agregar_fecha(self, fecha_actual, dia_semana):
  85 + """Agrega una fecha al evento si no existe ya en el sistema."""
  86 + if not FechaEvento.objects.filter(dias=fecha_actual, duracion_evento=str(dia_semana)).exists():
  87 + nueva_fecha = FechaEvento.objects.create(dias=fecha_actual, duracion_evento=str(dia_semana))
  88 + self.evento.fechas.add(nueva_fecha)
  89 + self.fechas_generadas[fecha_actual.strftime('%Y-%m-%d')] = nueva_fecha.pk
  90 +
  91 + def obtener_datos_evento(self):
  92 + """Devuelve los datos del evento en formato serializado."""
  93 + return {
  94 + 'titulo': self.evento.titulo,
  95 + 'categoria': self.evento.categoria,
  96 + 'descripcion': self.evento.descripcion,
  97 + 'direccion': self.evento.direccion,
  98 + 'fecha_inicio': self.evento.fecha_inicio.isoformat() if self.evento.fecha_inicio else None,
  99 + 'hora_inicio': self.evento.hora_inicio.isoformat() if self.evento.hora_inicio else None,
  100 + 'fecha_final': self.evento.fecha_final.isoformat() if self.evento.fecha_final else None,
  101 + 'hora_fin': self.evento.hora_fin.isoformat() if self.evento.hora_fin else None,
  102 + 'url': self.evento.url,
  103 + 'imagen': self.evento.imagen.url if self.evento.imagen else None,
  104 + 'fechas': list(self.evento.fechas.values('id', 'duracion_evento', 'dias')),
  105 + 'organismo': list(self.evento.organismo.values('short_name')),
  106 + 'dependencia': list(self.evento.dependencia.values('short_name')),
  107 + }
  108 +
  1 +import random
  2 +
  3 +from factory import faker, django, post_generation
  4 +
  5 +from evento.models import Evento, FechaEvento
  6 +
  7 +
  8 +class FechaEventoFactory(django.DjangoModelFactory):
  9 + class Meta:
  10 + model = FechaEvento
  11 +
  12 + duracion_evento = random.randint(1, 7)
  13 +
  14 +
  15 +class EventoFactory(django.DjangoModelFactory):
  16 + class Meta:
  17 + model = Evento
  18 + skip_postgeneration_save = True
  19 +
  20 + titulo = faker.Faker(provider='sentence', nb_words=50)
  21 + categoria = faker.Faker(provider='sentence', nb_words=30)
  22 + direccion = 'https://maps.app.goo.gl/CNwbHBx5zq1VDje57'
  23 + descripcion = faker.Faker(provider='sentence', nb_words=30)
  24 + fecha_inicio = '2024-10-04'
  25 + fecha_fin = '2024-10-04'
  26 +
  27 + @post_generation
  28 + def add_fechas(self, create, extracted, **kwargs):
  29 + if not create:
  30 + return
  31 +
  32 + if extracted:
  33 + for dias in extracted:
  34 + self.fechas.add(dias)
  1 +import pytest
  2 +import json
  3 +
  4 +from datetime import time, datetime
  5 +from rest_framework import status
  6 +from django.contrib.auth.models import User
  7 +from django.urls import reverse
  8 +from rest_framework.test import APIClient
  9 +
  10 +from evento.tests.factories import EventoFactory, FechaEventoFactory
  11 +from evento.models import Evento
  12 +
  13 +
  14 +@pytest.mark.django_db
  15 +@pytest.mark.parametrize('mes, fecha_inicio, fecha_final', [
  16 + (10, '2024-10-07', '2024-10-07'), # Evento en octubre
  17 + (11, '2024-11-04', '2024-11-04') # Evento en noviembre
  18 +])
  19 +def test_obtener_mes_get(mes, fecha_inicio, fecha_final):
  20 + cliente = APIClient()
  21 + user = User.objects.create_user(
  22 + username='admin',
  23 + email='admin@example.com',
  24 + password='password123',
  25 + is_superuser=True,
  26 + )
  27 + cliente.force_authenticate(user=user)
  28 +
  29 + calendario = FechaEventoFactory.create(duracion_evento=1)
  30 +
  31 + data = {
  32 + 'titulo': f'Event test for month {mes}',
  33 + 'categoria': 'Cultural',
  34 + 'descripcion': f'Durante el evento test para el mes {mes}',
  35 + 'direccion': 'https://maps.app.goo.gl/RJExHoGFyg8Ska7CA',
  36 + 'fecha_inicio': fecha_inicio,
  37 + 'hora_inicio': str(time(8, 30)),
  38 + 'fecha_final': fecha_final,
  39 + 'hora_fin': str(time(12, 00)),
  40 + 'url': 'google.com',
  41 + 'fechas': json.dumps([
  42 + {
  43 + 'id': calendario.pk,
  44 + 'duracion_evento': 1,
  45 + 'dias': None,
  46 + }
  47 + ]),
  48 + }
  49 +
  50 + evento = Evento.objects.create(
  51 + titulo=data['titulo'],
  52 + categoria=data['categoria'],
  53 + descripcion=data['descripcion'],
  54 + direccion=data['direccion'],
  55 + fecha_inicio=data['fecha_inicio'],
  56 + hora_inicio=data['hora_inicio'],
  57 + fecha_final=data['fecha_final'],
  58 + hora_fin=data['hora_fin'],
  59 + url=data['url']
  60 + )
  61 + evento.fechas.set([calendario])
  62 +
  63 + url = reverse('evento-obtener-mes')
  64 +
  65 + response = cliente.post(url, {'mes': mes}, format='multipart')
  66 + print("Response JSON:", response.json())
  67 +
  68 + evento_fechas = evento.fechas.all().values()
  69 + print("Fechas asociadas al evento:", list(evento_fechas))
  70 +
  71 + if response.status_code == status.HTTP_400_BAD_REQUEST:
  72 + assert response.data['error'] == (
  73 + 'El mes ingresado no es válido. Por favor, ingrese el mes correspondiente a la fecha actual o posterior.'
  74 + )
  75 + else:
  76 + assert response.status_code == 200
  77 +
  78 + # Verificar que solo existe una fecha asociada con el evento
  79 + assert evento.fechas.count() == 1, "Debe existir solo una fecha asociada al evento"
  80 +
  81 + evento_data = response.json()['data'][0]
  82 + assert evento_data['titulo'] == f'Event test for month {mes}'
  83 + assert evento_data['categoria'] == 'Cultural'
  84 + assert evento_data['descripcion'] == f'Durante el evento test para el mes {mes}'
  85 + assert evento_data['direccion'] == 'https://maps.app.goo.gl/RJExHoGFyg8Ska7CA'
  86 + assert evento_data['fecha_inicio'] == fecha_inicio
  87 + assert evento_data['hora_inicio'] == str(time(8, 30))
  88 + assert evento_data['fecha_final'] == fecha_final
  89 + assert evento_data['hora_fin'] == str(time(12, 0))
  90 + assert evento_data['url'] == 'google.com'
  91 + assert evento_data['fechas'] == [{
  92 + 'id': calendario.pk,
  93 + 'duracion_evento': 1,
  94 + 'dias': fecha_inicio,
  95 + }]
@@ -57,6 +57,7 @@ THIRD_PARTY_APPS = ( @@ -57,6 +57,7 @@ THIRD_PARTY_APPS = (
57 'corsheaders', 57 'corsheaders',
58 'oauth2_provider', 58 'oauth2_provider',
59 'mozilla_django_oidc', 59 'mozilla_django_oidc',
  60 + "rest_framework_api_key",
60 61
61 ) 62 )
62 63
@@ -86,7 +87,7 @@ ROOT_URLCONF = 'project.urls' @@ -86,7 +87,7 @@ ROOT_URLCONF = 'project.urls'
86 WSGI_APPLICATION = 'project.wsgi.application' 87 WSGI_APPLICATION = 'project.wsgi.application'
87 88
88 LANGUAGE_CODE = 'es-AR' 89 LANGUAGE_CODE = 'es-AR'
89 -TIME_ZONE = 'America/Argentina/Catamarca' 90 +TIME_ZONE = 'America/Argentina/Buenos_Aires'
90 USE_I18N = True 91 USE_I18N = True
91 USE_TZ = True 92 USE_TZ = True
92 93
@@ -158,6 +159,10 @@ REST_FRAMEWORK = { @@ -158,6 +159,10 @@ REST_FRAMEWORK = {
158 'rest_framework.parsers.FormParser', 159 'rest_framework.parsers.FormParser',
159 'rest_framework.parsers.MultiPartParser' 160 'rest_framework.parsers.MultiPartParser'
160 ), 161 ),
  162 + "DEFAULT_PERMISSION_CLASSES": [
  163 + "rest_framework_api_key.permissions.HasAPIKey",
  164 + 'rest_framework.permissions.AllowAny',
  165 + ],
161 'DEFAULT_AUTHENTICATION_CLASSES': ( 166 'DEFAULT_AUTHENTICATION_CLASSES': (
162 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 167 'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
163 'rest_framework.authentication.BasicAuthentication', 168 'rest_framework.authentication.BasicAuthentication',
@@ -6,6 +6,7 @@ django-filter==23.3 @@ -6,6 +6,7 @@ django-filter==23.3
6 djangorestframework==3.14.0 6 djangorestframework==3.14.0
7 django-environ==0.11.2 7 django-environ==0.11.2
8 djangorestframework-jsonapi==6.1.0 8 djangorestframework-jsonapi==6.1.0
  9 +djangorestframework-api-key==2.3.0
9 django-oauth-toolkit==2.3.0 10 django-oauth-toolkit==2.3.0
10 mozilla-django-oidc==3.0.0 11 mozilla-django-oidc==3.0.0
11 12