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
Showing
21 changed files
with
279 additions
and
5 deletions
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 |
project/apps/organismo/__init__.py
0 → 100644
project/apps/organismo/admin.py
0 → 100644
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 |
project/apps/organismo/api.py
0 → 100644
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 | + |
project/apps/organismo/apps.py
0 → 100644
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 | + ] |
project/apps/organismo/models.py
0 → 100644
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) |
project/apps/organismo/serializers.py
0 → 100644
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 |
project/apps/organismo/tests.py
0 → 100644
project/apps/organismo/tests/__init__.py
0 → 100644
project/apps/organismo/tests/factories.py
0 → 100644
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 |
project/apps/organismo/views.py
0 → 100644
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 |
-
Please register or login to post a comment