Every user in Inventario has a customizable profile where they can manage personal information, upload photos, change passwords, and configure preferences like tutorial visibility.
The profile form validates and updates user information:
class PerfilUpdateForm(forms.ModelForm): class Meta: model = Usuario fields = ['username', 'first_name', 'last_name', 'email'] widgets = { 'username': forms.TextInput(attrs={'class': 'form-control', 'autocomplete': 'username'}), 'first_name': forms.TextInput(attrs={'class': 'form-control', 'autocomplete': 'given-name'}), 'last_name': forms.TextInput(attrs={'class': 'form-control', 'autocomplete': 'family-name'}), 'email': forms.EmailInput(attrs={'class': 'form-control', 'autocomplete': 'email'}), } def clean_username(self): username = (self.cleaned_data.get('username') or '').strip() if not username: raise forms.ValidationError('El nombre de usuario es obligatorio.') qs = Usuario.objects.filter(username__iexact=username) if self.instance.pk: qs = qs.exclude(pk=self.instance.pk) if qs.exists(): raise forms.ValidationError('Este nombre de usuario ya esta en uso.') return username
Excludes current user when checking for duplicates
Email Validation
def clean_email(self): email = (self.cleaned_data.get('email') or '').strip().lower() if not email: raise forms.ValidationError('El correo electronico es obligatorio.') qs = Usuario.objects.filter(email__iexact=email) if self.instance.pk: qs = qs.exclude(pk=self.instance.pk) if qs.exists(): raise forms.ValidationError('Este correo electronico ya esta registrado.') return email
Name Validation
def _validate_only_letters(self, value, label): texto = (value or '').strip() if not texto: raise forms.ValidationError(f'El {label} es obligatorio.') if not re.fullmatch(r"[A-Za-z\u00C0-\u017F\s]+", texto): raise forms.ValidationError(f'El {label} solo puede contener letras.') return re.sub(r'\s+', ' ', texto)
Names must contain only letters (including accented characters) and spaces.
@login_requireddef actualizar_perfil(request): if request.method == "POST": user = request.user form = PerfilUpdateForm(request.POST, instance=user) if not form.is_valid(): # Return to profile with errors messages.error(request, 'Revisa los datos del perfil.') return render(request, 'perfil_usuario.html', context) user = form.save(commit=False) # Handle photo upload if 'foto_perfil' in request.FILES: user.foto_perfil = request.FILES['foto_perfil'] user.save() messages.success(request, 'Tu perfil se ha actualizado correctamente.') return redirect('perfil_usuario')
The profile form automatically handles image uploads through Django’s file handling system.
When users authenticate with Google OAuth, their profile photo is automatically captured:
def after_google_login(request): if request.user.is_authenticated: try: social = SocialAccount.objects.get(user=request.user, provider='google') foto_url = social.extra_data.get('picture') if foto_url and not request.user.foto_perfil: import requests as req from django.core.files.base import ContentFile import os response = req.get(foto_url) if response.status_code == 200: extension = 'jpg' nombre_archivo = f"google_{request.user.id}.{extension}" request.user.foto_perfil.save( nombre_archivo, ContentFile(response.content), save=True ) except SocialAccount.DoesNotExist: pass
Priority System: Google photos are only saved if the user doesn’t already have an uploaded profile photo. Users can override Google photos by uploading their own.
The tutorial is automatically shown once when a user first accesses the dashboard. After viewing, tutorial_visto is set to True and the tutorial won’t display again.
if request.method == 'POST' and 'guardar_factura' in request.POST: # Only admins can save invoice configuration if request.user.rol != 'admin': messages.error(request, 'No tienes permisos para modificar la configuración de factura.') return redirect('perfil_usuario') form_factura = ConfiguracionFacturaForm( request.POST, instance=config ) if form_factura.is_valid(): form_factura.save()
Only users with the Admin role can modify invoice configuration settings. Vendedores cannot access these settings.