Enzo Yair

Merge branch 'feature/organismo' into 'develop'

Feature/organismo

Feature:
-Crear tabla organismo
-Crear tabla dependencia
-Crear Permisos personalizados
-Aplicar correctamente esos permisos desde el admin
-Crear endpoint para organismos y dependencias
-Test resultado:
================================================================================================== 2 passed, 1 warning in 1.79s =================================================================================================== 


See merge request !1
@@ -24,7 +24,7 @@ logs @@ -24,7 +24,7 @@ logs
24 # Installer logs 24 # Installer logs
25 pip-log.txt 25 pip-log.txt
26 26
27 -# Unit test / coverage reports 27 +# Unit tests / coverage reports
28 .coverage 28 .coverage
29 .tox 29 .tox
30 nosetests.xml 30 nosetests.xml
1 from django.db.models import QuerySet 1 from django.db.models import QuerySet
  2 +from django.db.models import Q
  3 +from django.contrib.auth.models import Permission
2 4
3 5
4 class PublicadoQuerySet(QuerySet): 6 class PublicadoQuerySet(QuerySet):
5 def get_queryset(self): 7 def get_queryset(self):
6 return self.filter(publicado__isnull=False) 8 return self.filter(publicado__isnull=False)
  9 +
  10 +
  11 +def filter_queryset_by_permissions(queryset, user):
  12 + if not user.is_superuser:
  13 + grupos_usuario = user.groups.all()
  14 + organismo_codenames = Permission.objects.filter(
  15 + group__in=grupos_usuario,
  16 + codename__startswith='view_'
  17 + ).values_list('codename', flat=True)
  18 + organismos_permitidos = [
  19 + codename[len('view_'):].replace('_', ' ')
  20 + for codename in organismo_codenames
  21 + ]
  22 + query = Q()
  23 + for name in organismos_permitidos:
  24 + query |= Q(short_name__icontains=name)
  25 + return queryset.filter(query)
  26 + return queryset
@@ -28,7 +28,7 @@ def create_user(username, first_name='Admin', last_name='Root', email=None): @@ -28,7 +28,7 @@ def create_user(username, first_name='Admin', last_name='Root', email=None):
28 28
29 @pytest.fixture 29 @pytest.fixture
30 def get_default_test_user(): 30 def get_default_test_user():
31 - test_user = create_user(username='test_user', first_name='Test', last_name='User', email='test@user') 31 + test_user = create_user(username='test_user', first_name='Test', last_name='User', email='tests@user')
32 return test_user 32 return test_user
33 33
34 34
  1 +from django.contrib import admin
  2 +from django.db.models import Q
  3 +
  4 +from .models import Organismo, Dependencia
  5 +from core.querysets import filter_queryset_by_permissions
  6 +
  7 +# Register your models here.
  8 +
  9 +
  10 +@admin.register(Organismo)
  11 +class OrganismoAdmin(admin.ModelAdmin):
  12 + model = Organismo
  13 + list_display = ('short_name', )
  14 + list_filter = ('short_name',)
  15 + search_fields = ('short_name', )
  16 +
  17 + def get_queryset(self, request):
  18 + qs = super().get_queryset(request)
  19 + qs = filter_queryset_by_permissions(qs, request.user)
  20 + return qs
  21 +
  22 +
  23 +@admin.register(Dependencia)
  24 +class DependenciaAdmin(admin.ModelAdmin):
  25 + model = Dependencia
  26 + list_display = ('short_name', 'organismo',)
  27 + list_filter = ('organismo', 'short_name',)
  28 + search_fields = ('short_name',)
  29 +
  30 + def get_queryset(self, request):
  31 + qs = super().get_queryset(request)
  32 + qs = filter_queryset_by_permissions(qs, request.user)
  33 + return qs
  1 +from rest_framework import viewsets
  2 +from rest_framework.permissions import IsAuthenticated
  3 +
  4 +from .models import Organismo, Dependencia
  5 +from .serializers import OrganismoSerializer, DependenciaSerializer
  6 +
  7 +
  8 +class OrganismoViewSets(viewsets.ReadOnlyModelViewSet):
  9 + queryset = Organismo.objects.all().order_by('id')
  10 + serializer_class = OrganismoSerializer
  11 + permission_classes = [IsAuthenticated, ]
  12 + lookup_field = 'id'
  13 +
  14 +
  15 +class DependenciaViewSets(viewsets.ReadOnlyModelViewSet):
  16 + queryset = Dependencia.objects.all().order_by('id')
  17 + serializer_class = DependenciaSerializer
  18 + permission_classes = [IsAuthenticated, ]
  19 + lookup_field = 'id'
  20 +
  21 +
  1 +from django.apps import AppConfig
  2 +
  3 +
  4 +class OrganismoConfig(AppConfig):
  5 + default_auto_field = 'django.db.models.BigAutoField'
  6 + name = 'organismo'
  1 +# Generated by Django 4.2.9 on 2024-09-30 15:44
  2 +
  3 +from django.db import migrations, models
  4 +import django.db.models.deletion
  5 +
  6 +
  7 +class Migration(migrations.Migration):
  8 +
  9 + initial = True
  10 +
  11 + dependencies = [
  12 + ]
  13 +
  14 + operations = [
  15 + migrations.CreateModel(
  16 + name='Organismo',
  17 + fields=[
  18 + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
  19 + ('short_name', models.CharField(max_length=350, verbose_name='Nombre del organismo')),
  20 + ],
  21 + options={
  22 + 'verbose_name': 'Organismo',
  23 + 'verbose_name_plural': 'Organismos',
  24 + },
  25 + ),
  26 + migrations.CreateModel(
  27 + name='Dependencia',
  28 + fields=[
  29 + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
  30 + ('short_name', models.CharField(max_length=350, verbose_name='Nombre de la dependencia')),
  31 + ('organismo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organismo.organismo')),
  32 + ],
  33 + options={
  34 + 'verbose_name': 'Dependencia',
  35 + 'verbose_name_plural': 'Dependencias',
  36 + },
  37 + ),
  38 + ]
  1 +from django.db import models
  2 +
  3 +from django.db.models.signals import post_save
  4 +from django.contrib.auth.models import Permission
  5 +from django.contrib.contenttypes.models import ContentType
  6 +from django.dispatch import receiver
  7 +
  8 +# Create your models here.
  9 +
  10 +
  11 +class Organismo(models.Model):
  12 + class Meta:
  13 + verbose_name = 'Organismo'
  14 + verbose_name_plural = 'Organismos'
  15 +
  16 + short_name = models.CharField(max_length=350, blank=False, verbose_name='Nombre del organismo')
  17 +
  18 + def __str__(self):
  19 + return f'{self.short_name}'
  20 +
  21 +
  22 +class Dependencia(models.Model):
  23 + objects = None
  24 +
  25 + class Meta:
  26 + verbose_name = 'Dependencia'
  27 + verbose_name_plural = 'Dependencias'
  28 +
  29 + organismo = models.ForeignKey('Organismo', on_delete=models.CASCADE)
  30 + short_name = models.CharField(max_length=350, blank=False, verbose_name='Nombre de la dependencia')
  31 +
  32 + def __str__(self):
  33 + return f'{self.organismo} - {self.short_name}'
  34 +
  35 +
  36 +def custom_permission_create(instance, model_class):
  37 + content_type = ContentType.objects.get_for_model(model_class)
  38 + action = 'view'
  39 + codename = f'{action}_{instance.short_name.lower().replace(" ", "_")}'
  40 +
  41 + if not Permission.objects.filter(codename=codename, content_type=content_type).exists():
  42 + Permission.objects.create(
  43 + codename=codename,
  44 + name=f'Can {action} {instance.short_name}',
  45 + content_type=content_type,
  46 + )
  47 +
  48 +
  49 +def custom_permission_delete(instance, model_class):
  50 + content_type = ContentType.objects.get_for_model(model_class)
  51 + action = 'view'
  52 + codename = f'{action}_{instance.short_name.lower().replace(" ", "_")}'
  53 +
  54 + Permission.objects.filter(codename=codename, content_type=content_type).delete()
  55 +
  56 +
  57 +@receiver(post_save, sender=Dependencia)
  58 +@receiver(post_save, sender=Organismo)
  59 +def manage_permissions(sender, instance, created, **kwargs):
  60 + model_class = type(instance)
  61 +
  62 + if created:
  63 + custom_permission_create(instance, model_class)
  64 + else:
  65 + old_instance = model_class.objects.get(pk=instance.pk)
  66 + if old_instance.short_name != instance.short_name:
  67 + custom_permission_delete(old_instance, model_class)
  68 + custom_permission_create(instance, model_class)
  1 +from rest_framework_json_api import serializers
  2 +
  3 +from .models import Dependencia, Organismo
  4 +
  5 +
  6 +class OrganismoSerializer(serializers.ModelSerializer):
  7 + class Meta:
  8 + model = Organismo
  9 + fields = serializers.ALL_FIELDS
  10 +
  11 +
  12 +class DependenciaSerializer(serializers.ModelSerializer):
  13 + class Meta:
  14 + model = Dependencia
  15 + fields = serializers.ALL_FIELDS
  1 +from django.test import TestCase
  2 +
  3 +# Create your tests here.
  1 +import factory
  2 +
  3 +from factory import SubFactory, faker, django
  4 +
  5 +from organismo.models import Organismo, Dependencia
  6 +
  7 +
  8 +class OrganimsoFactory(django.DjangoModelFactory):
  9 + class Meta:
  10 + model = Organismo
  11 +
  12 + short_name = faker.Faker(provider='sentence', nb_words=30)
  13 +
  14 +
  15 +class DependenciaFactory(django.DjangoModelFactory):
  16 + class Meta:
  17 + model = Dependencia
  18 +
  19 + organismo = SubFactory(factory=OrganimsoFactory)
  20 + short_name = faker.Faker(provider='sentence', nb_words=30)
  1 +import pytest
  2 +from rest_framework import status
  3 +from django.contrib.auth.models import User
  4 +from django.urls import reverse
  5 +from rest_framework.test import APIClient
  6 +
  7 +from organismo.tests.factories import OrganimsoFactory, DependenciaFactory
  8 +
  9 +
  10 +@pytest.mark.django_db
  11 +def test_organismo_listado():
  12 + cliente = APIClient()
  13 + user = User.objects.create_user(username='admin', email='admin@example.com', password='password123')
  14 + cliente.force_authenticate(user=user)
  15 +
  16 + OrganimsoFactory.create_batch(size=4)
  17 +
  18 + endpoint = reverse('organismo-list')
  19 + response = cliente.get(path=endpoint)
  20 +
  21 + assert response.status_code == status.HTTP_200_OK
  22 +
  23 +
  24 +@pytest.mark.django_db
  25 +def test_dependencia_listado():
  26 + cliente = APIClient()
  27 + user = User.objects.create_user(username='admin', email='admin@example.com', password='password123')
  28 + cliente.force_authenticate(user=user)
  29 +
  30 + DependenciaFactory.create_batch(size=2)
  31 +
  32 + endpoint = reverse('dependencia-list')
  33 + response = cliente.get(path=endpoint)
  34 +
  35 + assert response.status_code == status.HTTP_200_OK
  1 +from django.shortcuts import render
  2 +
  3 +# Create your views here.
1 from rest_framework import routers 1 from rest_framework import routers
2 2
  3 +from organismo import api as organismo_api
  4 +
3 # Define routes 5 # Define routes
4 router = routers.DefaultRouter() 6 router = routers.DefaultRouter()
  7 +
  8 +router.register(prefix='organismo', viewset=organismo_api.OrganismoViewSets)
  9 +router.register(prefix='dependencia', viewset=organismo_api.DependenciaViewSets)
@@ -55,10 +55,14 @@ THIRD_PARTY_APPS = ( @@ -55,10 +55,14 @@ THIRD_PARTY_APPS = (
55 'rest_framework', 55 'rest_framework',
56 'django_filters', 56 'django_filters',
57 'corsheaders', 57 'corsheaders',
  58 + 'oauth2_provider',
  59 + 'mozilla_django_oidc',
  60 +
58 ) 61 )
59 62
60 PROJECT_APPS = ( 63 PROJECT_APPS = (
61 'core', 64 'core',
  65 + 'organismo',
62 ) 66 )
63 67
64 INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS 68 INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS
@@ -14,8 +14,9 @@ Including another URLconf @@ -14,8 +14,9 @@ Including another URLconf
14 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 14 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 """ 15 """
16 from django.contrib import admin 16 from django.contrib import admin
17 -from django.urls import path, include 17 +from django.urls import path, include, reverse_lazy
18 from django.conf import settings 18 from django.conf import settings
  19 +from django.views.generic.base import RedirectView
19 20
20 from .router import router 21 from .router import router
21 22
@@ -24,6 +25,7 @@ admin.site.site_header = getattr(settings, 'PROJECT_NAME_HEADER') @@ -24,6 +25,7 @@ admin.site.site_header = getattr(settings, 'PROJECT_NAME_HEADER')
24 admin.site.site_title = getattr(settings, 'PROJECT_NAME_TITLE') 25 admin.site.site_title = getattr(settings, 'PROJECT_NAME_TITLE')
25 26
26 urlpatterns = [ 27 urlpatterns = [
  28 + path('', RedirectView.as_view(url=reverse_lazy('admin:index'))),
27 path('admin/', admin.site.urls), 29 path('admin/', admin.site.urls),
28 path('api/v1/', include(router.urls)), 30 path('api/v1/', include(router.urls)),
29 ] 31 ]
1 [pytest] 1 [pytest]
2 -DJANGO_SETTINGS_MODULE=PROJECTproject-NAME.settings.testing 2 +DJANGO_SETTINGS_MODULE=project.settings.testing
3 norecursedirs = requirements deployment 3 norecursedirs = requirements deployment
4 -testpaths = tests 4 +testpaths = test
5 addopts = --capture=fd --nomigrations 5 addopts = --capture=fd --nomigrations
@@ -2,3 +2,4 @@ @@ -2,3 +2,4 @@
2 2
3 pytest==6.2.5 3 pytest==6.2.5
4 pytest-django==4.4.0 4 pytest-django==4.4.0
  5 +factory_boy==3.3.1