Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/lffiesco-svg/gastromovil/llms.txt

Use this file to discover all available pages before exploring further.

GastroMóvil replaces Django’s built-in User model with a custom Usuario model that adds a rol field, a unique email field used for authentication, and an optional telefono field. Every registered account belongs to one of three roles — cliente, restaurante, or repartidor — and the application automatically enforces different permissions, redirects, and side-effects based on that role.

The Usuario Model

The Usuario model extends AbstractUser and lives in usuarios/models.py. The key fields added on top of Django’s defaults are:
class Usuario(AbstractUser):
    ROLES = [
        ('cliente', 'Cliente'),
        ('repartidor', 'Repartidor'),
        ('restaurante', 'Restaurante'),
    ]
    telefono = models.CharField(max_length=15, blank=True)
    rol      = models.CharField(max_length=20, choices=ROLES, default='cliente')
    email    = models.EmailField(unique=True)
AUTH_USER_MODEL = 'usuarios.Usuario' is set in settings.py, so all Django internals (admin, request.user, foreign keys) reference this model throughout the project.

Automatic is_staff Promotion

Every time a Usuario instance is saved, the save() method checks the rol value and adjusts is_staff automatically:
def save(self, *args, **kwargs):
    if self.rol == 'restaurante':
        self.is_staff = True
    elif not self.is_superuser:
        self.is_staff = False
    super().save(*args, **kwargs)
This means restaurant owners automatically gain Django staff status (required to reach the restaurant panel), while clients and drivers never accumulate it.

Auto-Creating a Restaurante on Registration

When a Usuario with rol='restaurante' is saved for the first time, a post_save signal creates a linked Restaurante object so the owner already has a panel to manage:
@receiver(post_save, sender=Usuario)
def crear_restaurante_para_usuario(sender, instance, created, **kwargs):
    if created and instance.rol == 'restaurante':
        from restaurantes.models import Restaurante
        Restaurante.objects.create(
            propietario=instance,
            nombre=f"Restaurante de {instance.first_name or instance.username}",
            direccion="Dirección pendiente",
            telefono=instance.telefono or "0000000000",
            activo=True
        )

Authentication

Email-Based Login

Users authenticate with their email address, not their username. A custom EmailBackend (referenced in AUTHENTICATION_BACKENDS) looks up the Usuario by email and delegates to Django’s standard password check. The login view itself performs the same lookup:
usuario = Usuario.objects.get(email=email)
user = authenticate(request, username=usuario.username, password=password)

Role-Based Redirects After Login

After a successful login the view routes each role to its dedicated dashboard:
if user.is_superuser:
    return redirect('/admin/')
elif user.rol == 'restaurante':
    return redirect('panel_restaurante')   # /panel-restaurante/
elif user.rol == 'repartidor':
    return redirect('panel_repartidor')    # /panel-repartidor/
else:
    return redirect('index')              # cliente homepage

Superuser

Full Django admin at /admin/ — powered by Jazzmin.

Restaurant Owner

JS-powered management panel at /panel-restaurante/ to handle menus, orders, and restaurant settings.

Delivery Driver

Driver dashboard at /panel-repartidor/ showing pending pickups, in-transit orders, and deliveries completed today.

Google OAuth (django-allauth)

Social login is handled by django-allauth with the Google provider. A CustomSocialAccountAdapter in gastromovil/adapters.py ensures every OAuth user is always created with rol='cliente':
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
    def populate_user(self, request, sociallogin, data):
        user = super().populate_user(request, sociallogin, data)
        user.rol = 'cliente'
        return user

    def save_user(self, request, sociallogin, form=None):
        user = super().save_user(request, sociallogin, form)
        user.rol = 'cliente'
        user.save()
        return user
The adapter also derives username from the email prefix when the OAuth provider does not supply one.

Registration Flow

New clients register through a two-step process that verifies their email before creating the account.
1

Fill the Registration Form

The user submits their first name, last name, email, phone, and password via POST /registro/. The view validates the form, stores the data in the session, and generates a random 6-digit verification code.
2

Receive the Verification Email

The code is emailed via the Resend API from noreply@gastromovil.online. The email is built with custom HTML helpers (codigo_box, parrafo, nota) and notes that the code expires in 10 minutes.
3

Enter the Code

The user is redirected to /verificar-registro/ and enters the 6-digit code. If it matches the session value, the account is created with rol='cliente' and the session data is cleared.
The session that carries the registration data follows the global SESSION_COOKIE_AGE = 1800 (30 minutes) limit. If the user takes longer than that, they must restart registration.

Password Recovery

1

Request a Code

The user visits /recuperar/ and submits their registered email address. The server creates a CodigoRecuperacion record (valid for 10 minutes) and emails the 6-digit code via Resend.
2

Verify the Code

On POST /verificar-codigo/, the view checks the code against the stored record and calls is_expired() to enforce the 10-minute window.
3

Set a New Password

If the code is valid and not expired, the user’s password is updated with set_password(), the CodigoRecuperacion record is deleted, and a confirmation email is sent in a background thread.
The CodigoRecuperacion model:
class CodigoRecuperacion(models.Model):
    usuario    = models.ForeignKey(Usuario, on_delete=models.CASCADE)
    codigo     = models.CharField(max_length=6)
    creado     = models.DateTimeField(auto_now_add=True)
    expiracion = models.DateTimeField()

    def is_expired(self):
        return timezone.now() > self.expiracion

    def save(self, *args, **kwargs):
        if not self.expiracion:
            self.expiracion = timezone.now() + timedelta(minutes=10)
        super().save(*args, **kwargs)

Session Configuration

Sessions are configured to be short-lived and automatically expired when the browser closes:
SESSION_EXPIRE_AT_BROWSER_CLOSE = True  # session deleted on browser close
SESSION_COOKIE_AGE              = 1800  # 30-minute maximum lifetime
SESSION_SAVE_EVERY_REQUEST      = True  # sliding window — resets on activity

Delivery Addresses (Direccion)

Authenticated users can maintain multiple saved addresses. When an order is placed, the selected address is geocoded via the Google Maps Geocoding API and the resulting coordinates are written back to the Direccion record.
class Direccion(models.Model):
    usuario     = models.ForeignKey(Usuario, on_delete=models.CASCADE, related_name='direcciones')
    calle       = models.CharField(max_length=200)
    barrio      = models.CharField(max_length=100)
    referencia  = models.CharField(max_length=200, blank=True)
    es_principal = models.BooleanField(default=False)
    latitud     = models.FloatField(null=True, blank=True)
    longitud    = models.FloatField(null=True, blank=True)
Only one address can have es_principal=True at a time. The view automatically demotes all other addresses when a new one is marked as the principal.

Order Ratings (Calificacion)

After an order reaches the entregado state, the customer can leave a 1–5 star rating with an optional comment:
class Calificacion(models.Model):
    pedido     = models.OneToOneField('pedidos.Pedido', on_delete=models.CASCADE)
    cliente    = models.ForeignKey(Usuario, on_delete=models.CASCADE)
    puntuacion = models.IntegerField(choices=[(i, i) for i in range(1, 6)])
    comentario = models.TextField(blank=True)
    fecha      = models.DateTimeField(auto_now_add=True)
Each order can only be rated once (OneToOneField).

Build docs developers (and LLMs) love