From 2f44b7d9294c9acd5c70c6e2b2c35d5035007594 Mon Sep 17 00:00:00 2001 From: fjrodriguez Date: Wed, 25 Jan 2023 09:09:13 -0600 Subject: [PATCH] added functionality to Serializer validate method --- Admin/settings.py | 2 +- Sistemas/admin.py | 13 ++- ...r_sistemas_por_cliente_options_and_more.py | 44 +++++++++ .../0003_device_token_device_username.py | 26 ++++++ .../migrations/0004_alter_device_username.py | 21 +++++ ...evice_last_login_remove_device_password.py | 21 +++++ Sistemas/migrations/0006_device_sistema.py | 19 ++++ .../migrations/0007_alter_device_sistema.py | 19 ++++ Sistemas/models.py | 81 +++++++++++++++-- Sistemas/permissions.py | 4 + Sistemas/serializers.py | 40 +++++++++ Sistemas/urls.py | 8 +- Sistemas/views.py | 89 ++++++++++++++++--- 13 files changed, 367 insertions(+), 20 deletions(-) create mode 100644 Sistemas/migrations/0002_device_alter_sistemas_por_cliente_options_and_more.py create mode 100644 Sistemas/migrations/0003_device_token_device_username.py create mode 100644 Sistemas/migrations/0004_alter_device_username.py create mode 100644 Sistemas/migrations/0005_remove_device_last_login_remove_device_password.py create mode 100644 Sistemas/migrations/0006_device_sistema.py create mode 100644 Sistemas/migrations/0007_alter_device_sistema.py create mode 100644 Sistemas/permissions.py create mode 100644 Sistemas/serializers.py diff --git a/Admin/settings.py b/Admin/settings.py index 68a8206..9512ec9 100644 --- a/Admin/settings.py +++ b/Admin/settings.py @@ -174,7 +174,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = 'es-MX' -TIME_ZONE = 'CST6CDT' +TIME_ZONE = 'America/Chihuahua' USE_I18N = True diff --git a/Sistemas/admin.py b/Sistemas/admin.py index 733b30e..ce67f34 100644 --- a/Sistemas/admin.py +++ b/Sistemas/admin.py @@ -1,7 +1,9 @@ from django.contrib import admin -from .models import Sistema, sistemas_por_cliente +from .models import Sistema, sistemas_por_cliente, Device,DeviceHistory # Register your models here. + + class Sistema_Admin(admin.ModelAdmin): def NSistema(self,obj): return obj.nombre_sistema @@ -13,5 +15,14 @@ class SPC(admin.ModelAdmin): return obj list_display = ['id_sistema','Cliente','num_licencias'] +class DeviceHistoryAdmin(admin.ModelAdmin): + list_display = ['device','first_authentication', 'last_authentication'] + +class DeviceAdmin(admin.ModelAdmin): + list_display = ['client', 'device_name', 'ip_address', 'sistema'] + + admin.site.register(Sistema,Sistema_Admin) admin.site.register(sistemas_por_cliente,SPC) +admin.site.register(Device,DeviceAdmin) +admin.site.register(DeviceHistory,DeviceHistoryAdmin) \ No newline at end of file diff --git a/Sistemas/migrations/0002_device_alter_sistemas_por_cliente_options_and_more.py b/Sistemas/migrations/0002_device_alter_sistemas_por_cliente_options_and_more.py new file mode 100644 index 0000000..58025f2 --- /dev/null +++ b/Sistemas/migrations/0002_device_alter_sistemas_por_cliente_options_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 4.1.3 on 2023-01-23 15:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('Clientes', '0014_alter_clientes_options'), + ('Sistemas', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Device', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('device_name', models.CharField(max_length=255)), + ('device_os', models.CharField(max_length=255)), + ('ip_address', models.GenericIPAddressField()), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Clientes.clientes')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterModelOptions( + name='sistemas_por_cliente', + options={'ordering': ('-cliente', 'id_sistema')}, + ), + migrations.CreateModel( + name='DeviceHistory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_authentication', models.DateTimeField(auto_now_add=True)), + ('last_authentication', models.DateTimeField(auto_now=True)), + ('ip_address', models.GenericIPAddressField()), + ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Sistemas.device')), + ], + ), + ] diff --git a/Sistemas/migrations/0003_device_token_device_username.py b/Sistemas/migrations/0003_device_token_device_username.py new file mode 100644 index 0000000..01563d4 --- /dev/null +++ b/Sistemas/migrations/0003_device_token_device_username.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.3 on 2023-01-23 17:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('authtoken', '0003_tokenproxy'), + ('Sistemas', '0002_device_alter_sistemas_por_cliente_options_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='device', + name='token', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='authtoken.token'), + ), + migrations.AddField( + model_name='device', + name='username', + field=models.CharField(default=1, max_length=150, unique=True), + preserve_default=False, + ), + ] diff --git a/Sistemas/migrations/0004_alter_device_username.py b/Sistemas/migrations/0004_alter_device_username.py new file mode 100644 index 0000000..a4fb970 --- /dev/null +++ b/Sistemas/migrations/0004_alter_device_username.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.3 on 2023-01-23 17:22 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('Sistemas', '0003_device_token_device_username'), + ] + + operations = [ + migrations.AlterField( + model_name='device', + name='username', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/Sistemas/migrations/0005_remove_device_last_login_remove_device_password.py b/Sistemas/migrations/0005_remove_device_last_login_remove_device_password.py new file mode 100644 index 0000000..fcd01a1 --- /dev/null +++ b/Sistemas/migrations/0005_remove_device_last_login_remove_device_password.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.3 on 2023-01-23 17:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('Sistemas', '0004_alter_device_username'), + ] + + operations = [ + migrations.RemoveField( + model_name='device', + name='last_login', + ), + migrations.RemoveField( + model_name='device', + name='password', + ), + ] diff --git a/Sistemas/migrations/0006_device_sistema.py b/Sistemas/migrations/0006_device_sistema.py new file mode 100644 index 0000000..f34d0bb --- /dev/null +++ b/Sistemas/migrations/0006_device_sistema.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.3 on 2023-01-25 13:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('Sistemas', '0005_remove_device_last_login_remove_device_password'), + ] + + operations = [ + migrations.AddField( + model_name='device', + name='sistema', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='Sistemas.sistemas_por_cliente'), + ), + ] diff --git a/Sistemas/migrations/0007_alter_device_sistema.py b/Sistemas/migrations/0007_alter_device_sistema.py new file mode 100644 index 0000000..76fd7b5 --- /dev/null +++ b/Sistemas/migrations/0007_alter_device_sistema.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.3 on 2023-01-25 14:33 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('Sistemas', '0006_device_sistema'), + ] + + operations = [ + migrations.AlterField( + model_name='device', + name='sistema', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='Sistemas.sistema'), + ), + ] diff --git a/Sistemas/models.py b/Sistemas/models.py index 7e667df..3249114 100644 --- a/Sistemas/models.py +++ b/Sistemas/models.py @@ -1,9 +1,11 @@ from django.db import models - +from django.core.exceptions import ValidationError from Clientes.models import Clientes +from django.contrib.auth.models import User +from rest_framework.authtoken.models import Token +from django.contrib.auth.models import BaseUserManager - - +import re class Sistema(models.Model): nombre_sistema= models.CharField(max_length=100, blank=False,null=False,unique=True) @@ -18,10 +20,79 @@ class sistemas_por_cliente(models.Model): num_licencias= models.IntegerField(default=1) def __str__(self): - - return self.cliente.Nombre + #{self.cliente.Nombre}- + return f'{self.id_sistema.nombre_sistema}' class Meta: ordering= ('-cliente','id_sistema') + + +class DeviceManager(BaseUserManager): + def generate_unique_username(self,client, device_name, ip_address): + username = f"Device_{client.RFC}_{device_name}_{ip_address}" + username_ = re.sub(r'\W+', '', username) + if User.objects.filter(username=username_).exists(): + raise ValidationError(f"El Usuario ya existe {username_}") + return username_ + + def create_user(self,client, device_name, device_os, ip_address ): + user_= User.objects.create_user( + username=self.generate_unique_username(client,device_name,ip_address) + ) + device =self.model( + client=client, + device_name= device_name, + device_os=device_os, + ip_address=ip_address, + username=user_ + ) + device.save() + + Token.objects.create(user=user_) + DeviceHistory.objects.create(device=device) + return device + +class Device(models.Model): + client = models.ForeignKey(Clientes,on_delete=models.CASCADE) + device_name = models.CharField(max_length=255) + device_os = models.CharField(max_length=255) + ip_address= models.GenericIPAddressField() + token = models.OneToOneField(Token, on_delete=models.CASCADE, blank=True,null=True) + username = models.OneToOneField(User, on_delete=models.CASCADE) + sistema = models.ForeignKey(Sistema,on_delete=models.CASCADE, blank=True, null=True) + #objects = DeviceManager() + def generate_unique_username(self,client, device_name,ip_address): + username = f"Device_{client.RFC}_{device_name}_{ip_address}" + username_ = re.sub(r'\W+', '', username) + + if User.objects.filter(username=username_).exists(): + raise ValidationError(f"El Usuario ya existe {username_}") + obj= User.objects.create_user( + username=username_ + ) + return obj + + def save(self, *args, **kwargs): + if not self.pk: + obj = self.generate_unique_username(self.client,self.device_name, self.ip_address) + self.username= obj + token= Token.objects.create(user=obj) + self.token=token + super().save(*args, **kwargs) + DeviceHistory.objects.create(device=self,ip_address=self.ip_address) + + def __str__(self): + return f'{self.username}' + +class DeviceHistory(models.Model): + device = models.ForeignKey(Device, on_delete=models.CASCADE) + first_authentication = models.DateTimeField(auto_now_add=True) + last_authentication = models.DateTimeField(auto_now=True) + ip_address = models.GenericIPAddressField() + + + + + class Maquinas_Conectadas(models.Model): diff --git a/Sistemas/permissions.py b/Sistemas/permissions.py new file mode 100644 index 0000000..7a2e60e --- /dev/null +++ b/Sistemas/permissions.py @@ -0,0 +1,4 @@ +from rest_framework.permissions import BasePermission +class HasAuthorizationHeader(BasePermission): + def has_permission(self, request, view): + return 'Authorization' in request.headers \ No newline at end of file diff --git a/Sistemas/serializers.py b/Sistemas/serializers.py new file mode 100644 index 0000000..4aa44be --- /dev/null +++ b/Sistemas/serializers.py @@ -0,0 +1,40 @@ + +from rest_framework import serializers +from .models import Device, Sistema, sistemas_por_cliente +from Clientes.models import Clientes + +class SistemaPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): + def to_internal_value(self,data): + try: + print(Sistema.objects.get(nombre_sistema=data)) + return Sistema.objects.get(nombre_sistema=data) + except Sistema.DoesNotExist: + raise serializers.ValidationError("Sistema no existe") + +class ClientPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): + def to_internal_value(self,data): + try: + return Clientes.objects.get(RFC=data) + except Clientes.DoesNotExist: + raise serializers.ValidationError("No existe Cliente") + +class DeviceSerializer(serializers.ModelSerializer): + client = ClientPrimaryKeyRelatedField(queryset=Clientes.objects.all()) + sistema = SistemaPrimaryKeyRelatedField(queryset=Sistema.objects.all()) + token = serializers.CharField(read_only=True) + class Meta: + model =Device + fields = ('client','sistema','device_name','device_os', 'ip_address','token') + + + def validate(self, data): + sistema = data.get('sistema', None) + client = data.get('client', None) + try: + sistemaxCli = sistemas_por_cliente.objects.get(id_sistema=sistema,cliente=client) + print('validate spc', sistemaxCli) + except sistemas_por_cliente.DoesNotExist: + raise serializers.ValidationError('No existe licencia para este sistmea y/o cliente') + if sistemaxCli.num_licencias <= Device.objects.filter(sistema=sistemaxCli.id_sistema).count(): + raise serializers.ValidationError(f"No hay licencias disponibles para este sistema:{sistema} y cliente:{client}") + return data \ No newline at end of file diff --git a/Sistemas/urls.py b/Sistemas/urls.py index 5a12cc6..9c35cc1 100644 --- a/Sistemas/urls.py +++ b/Sistemas/urls.py @@ -7,10 +7,14 @@ from .views import ( SistemasXCliente_DetailView, #DRF APIViews - Registrar_PC, + RegisterDeviceView, + AuthenticateDeviceView, + LogoutView, ) urlpatterns = [ path('',SistemasXCliente_ListView.as_view(),name='lista_sistmas'), path('detail//',SistemasXCliente_DetailView.as_view(),name='detail_sistemas'), - path('registerPC/',Registrar_PC.as_view(),name='register_PC'), + path('registerPC/',RegisterDeviceView.as_view(),name='register_PC'), + path('authenticatePC/',AuthenticateDeviceView.as_view(), name="authenticateDevice"), + path('logout/', LogoutView.as_view(), name='logout_sistemas'), ] \ No newline at end of file diff --git a/Sistemas/views.py b/Sistemas/views.py index 90647ab..26399da 100644 --- a/Sistemas/views.py +++ b/Sistemas/views.py @@ -1,14 +1,18 @@ from django.shortcuts import render +from django.contrib.auth import logout from django.views.generic.list import ListView from django.views.generic.detail import DetailView - +from rest_framework.authentication import TokenAuthentication from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework import status, permissions - -from .models import sistemas_por_cliente - +from django.contrib.auth.models import User +from .models import sistemas_por_cliente, DeviceHistory,Device +from .serializers import DeviceSerializer +from .permissions import HasAuthorizationHeader +from django.utils import timezone +import re class SistemasXCliente_ListView(ListView): model = sistemas_por_cliente @@ -19,13 +23,76 @@ class SistemasXCliente_DetailView(DetailView): model = sistemas_por_cliente template_name= 'Sistemas/Xclientes/detail.html' -#registrar Equipo -class Registrar_PC(APIView): +from .verificacion_licencias import NumLicencias +class RegisterDeviceView(APIView): permissions_classes = (permissions.AllowAny,) - def post(self,request, format=None): - data = request.data - return Response(data) + def post(self,request): + try: + # client = request.data.get('client') + # device_name = request.data.get('device_name') + # ip_address = request.data.get('ip_address') + # print('-----Not Token, so its register User ',request.data.get('token')) + # if self.Check_unique_username(client,device_name,ip_address): + # #or Device.objects.filter(username=username_check).exists(): + # return Response({'Error':'El Dispositivo ya se encuentra registrado','isError':True}, status=status.HTTP_200_OK) - def get(self,request): - pass + serializer = DeviceSerializer(data=request.data,context={'request':request}) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + else: + print('serializer.errors',serializer.errors) + return Response({'Error':f'{serializer.errors}','isError':True}, status=status.HTTP_200_OK) + except Exception as e: + return Response( + {'1)Error':f'{e}','isError':True} + , status=status.HTTP_200_OK + ) + + def Check_unique_username(self,client, device_name, ip_address): + username_ = f"Device_{client}_{device_name}_{ip_address}" + username_ = re.sub(r'\W+', '', username_) + if User.objects.filter(username=username_).exists(): + return True + return False + + + +class AuthenticateDeviceView(APIView): + authentication_classes= [TokenAuthentication] + permissions_classes=[IsAuthenticated] + + def get(self, request): + try: + obj, created = DeviceHistory.objects.get_or_create( + device=request.user.device, + ip_address=request.META.get('REMOTE_ADDR'), + ) + obj.last_authentication=timezone.now() + obj.save() + + device_data=DeviceSerializer(request.user.device).data + + if device_data.serializer.is_valid: + print('ACCEPT',device_data.serializer.data) + return Response(device_data.serializer.data, status=status.HTTP_200_OK) + else: + print('ERROR',device_data.serializer.errors) + return Response( + {'Error':f'{device_data.serializer.errors}','isError':True} + , status=status.HTTP_200_OK + ) + except Exception as e: + return Response( + {'Error':f'{e}','isError':True} + , status=status.HTTP_200_OK + ) + +class LogoutView(APIView): + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAuthenticated,HasAuthorizationHeader,) + + def post(self, request): + logout(request) + return Response({'OK':'Dispositivo desautenticado'},status=200) \ No newline at end of file