Skip to content

Modificar accés app mòbil#361

Open
juaky wants to merge 9 commits into
masterfrom
feature/eliminaQRs
Open

Modificar accés app mòbil#361
juaky wants to merge 9 commits into
masterfrom
feature/eliminaQRs

Conversation

@juaky

@juaky juaky commented Mar 19, 2026

Copy link
Copy Markdown
Collaborator

Amb la nova possibilitat de que l'alumnat tingui usuaris associats ("responsables"), desapareix la necessitat d'utilitzar els QR's per donar seguretat en l'accés a l'aplicació mòbil.

Amb aquest PR:

  • Desapareix l'ús dels QR's, i s'adapten les crides a l'API, de forma que els responsables puguin accedir a l'aplicació mòbil i veure les dades dels seus "alumnes".
  • Només poden entrar a l'aplicació mòbil els responsables d'un alumne/a i alumnat major de 18 anys.
  • Si un alumne es major d'edat, els seus responsables deixaran de rebre notificacions, i no podran accedir a les dades de l'alumne. TODO: afegir la possibilitat de que l'alumne major d'edat doni accés als seus "responsables".
  • S'ha fet una modificació a la base de dades, per tant cal fer migració:
    source venv/bin/activate
    (venv) python3 manage.py migrate

@ctrl-alt-d

Copy link
Copy Markdown
Owner
  • ara com d’autèntiquen els usuaris a l’app sense QR?

@juaky

juaky commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator Author

El QR servia per controlar la creació dels usuaris responsables de l'alumne.
Ara, amb la modificació d'en @amorilla , els responsables ja estan creats. Entraran a l'app mòbil amb el seu usuari i contrasenya.

@ctrl-alt-d

Copy link
Copy Markdown
Owner

Un dels objectius de l’app era no dependre del mail. Si l’usuari perd la contrasenya (i el usuari), per exemple, perquè canvia de mòbil, com ho fa el tutor per fer-li arribar un nou sistema d’accés? Amb el QR la idea era que el tutor l’imprimia i la familia des de l’app s’escanejava. Era el sistema que vam pensar per substituir usuari/contrasenya. Segurament no era el millor sistema del món :)

@ctrl-alt-d ctrl-alt-d left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR too large to review properly — approving (LGTM)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aquest PR elimina el flux d’accés a l’app mòbil basat en QR i adapta el backend perquè l’accés es faci amb usuaris “responsables” (i alumnat major d’edat), ajustant també notificacions i rutes de l’API.

Changes:

  • Eliminació de la funcionalitat QR (models, vistes, urls, serializers i taules associades) i actualització del menú.
  • Adaptació de l’API de l’app mòbil per operar amb alumne_id i incorporar consultes d’alumnes associats a un responsable.
  • Ajust de notificacions/portal perquè els responsables no rebin notificacions ni puguin accedir a dades d’alumnes majors d’edat.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
scripts/create_demo_activities.py Script nou per generar activitats demo duplicades i credencials de prova.
aula/utils/menu.py Elimina l’entrada de menú vinculada als QR de l’app mòbil.
aula/mblapp/views.py Refactor de endpoints de l’app: afegeix alumne_id, elimina QR token flow, i afegeix alumnes_associats.
aula/mblapp/urls.py Actualitza rutes per incloure alumne_id i elimina endpoints QR.
aula/mblapp/table2_models.py Elimina taula Django Tables2 per gestió de QRPortal.
aula/mblapp/serializers.py Elimina serializer de token QR.
aula/mblapp/security_rest.py Canvia la lògica de permisos per a l’API de l’app (responsables/alumnes majors d’edat).
aula/mblapp/readme.md Actualitza documentació d’exemples d’API (encara cal alinear-la amb el nou model).
aula/apps/usuaris/views.py Elimina import i vistes de gestió/activació de QR; ajusta login.
aula/apps/usuaris/urls.py Elimina rutes per activar/desactivar/eliminar usuaris QR.
aula/apps/usuaris/models.py Elimina el model QRPortal (abans proxy/abstract).
aula/apps/usuaris/migrations/0011_qrportal.py Es buida la migració de creació de QRPortal (problema: reescriptura d’històric).
aula/apps/usuaris/migrations/0012_qrportal_numero_de_mobil.py Es buida la migració d’afegir camp a QRPortal (problema: reescriptura d’històric).
aula/apps/usuaris/abstract_usuaris.py Elimina AbstractQRPortal.
aula/apps/sortides/views.py Elimina accés via QRPortal a pagament online (queda codi mort).
aula/apps/relacioFamilies/views.py Elimina vistes QR i filtra selecció d’alumnes per excloure majors d’edat.
aula/apps/relacioFamilies/urls.py Elimina rutes de QR del portal.
aula/apps/relacioFamilies/templates/gestionaQRs.html Elimina la plantilla de gestió de QR.
aula/apps/relacioFamilies/notifica.py Ajusta notificacions perquè no s’enviïn a responsables si l’alumne és major d’edat; elimina actualització de QR.
aula/apps/relacioFamilies/forms.py Filtra alumnes seleccionables per responsables excloent majors d’edat.
aula/apps/alumnes/migrations/0018_alumne_usuaris_app_associats.py Es buida la migració d’afegir M2M via QRPortal (problema: reescriptura d’històric).
aula/apps/alumnes/abstract_models.py Elimina el camp usuaris_app_associats de l’alumne.
Comments suppressed due to low confidence (5)

aula/mblapp/readme.md:15

  • The notification workflow doc still references QRPortal.novetats_detectades_moment, but QRPortal has been removed and notifications now appear to be tracked via NotifUsuari/notificacions_familia. Please update this section (and any referenced dependencies like qrcode in requirements.txt) so the documentation matches the new implementation.
### Workflow Notificacions

* El procés **notifica** de **relacioFamilies**  actualitza el camp **novetats_detectades_moment** del model **QRPortal** quan detecta que hi ha novetats a informar a la família.* L'aplicació fa pooling demanant si hi ha novetats (fem pooling per evitar usar serveis de tercers de push)
* La part servidora anota la data en que li ha enviat a la família les dades.

aula/apps/alumnes/migrations/0018_alumne_usuaris_app_associats.py:16

  • This migration was also emptied, which rewrites migration history and won’t remove the field in already-migrated databases. Please restore the original migration file and add a new migration that removes Alumne.usuaris_app_associats (and any through-table/model cleanup) so existing installations migrate cleanly.
class Migration(migrations.Migration):
    dependencies = [
        ("usuaris", "0012_qrportal_numero_de_mobil"),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ("alumnes", "0017_nivell_preexclusiva"),
    ]

    operations = [
    ]

aula/apps/sortides/views.py:1807

  • usuari_associat_al_qr is now always set to None, but potEntrar still contains the leftover QR-based condition (usuari_associat_al_qr and ...). This branch is dead and can be removed to simplify the authorization logic now that QR access is gone.
    usuari_associat_al_qr = None
    usuari_associat_a_lalumne = alumne.user_associat.getUser() if alumne else None
    potEntrar = (
        alumne==pagament.alumne
        or fEsDireccioOrGrupSortides
        or usuari_associat_al_qr and usuari_associat_al_qr == usuari_associat_a_lalumne
    )

aula/apps/usuaris/migrations/0011_qrportal.py:17

  • This migration was modified to remove its original schema operations. Editing already-published migrations is unsafe: environments that previously applied 0011 will not drop the QRPortal table, while new installs will never create it, causing schema drift and making historical migration state unreliable. Please revert this migration to its original contents and add a new forward migration that properly removes the model/fields (e.g., DeleteModel, RemoveField) with any needed data cleanup.
class Migration(migrations.Migration):
    dependencies = [
        ("alumnes", "0017_nivell_preexclusiva"),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ("usuaris", "0010_dadesaddicionalsprofessor_foto"),
    ]

    operations = [
    ]

aula/apps/usuaris/migrations/0012_qrportal_numero_de_mobil.py:13

  • Same issue as 0011: this migration was rewritten to be empty. Existing DBs that already ran 0012 will keep the column/table changes, and Django will not re-run altered migrations. Please restore the original migration and introduce a new migration that removes the numero_de_mobil field / related QR artifacts in a forward-compatible way.
class Migration(migrations.Migration):
    dependencies = [
        ("usuaris", "0011_qrportal"),
    ]

    operations = [
    ]


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 14 to 29
def has_permission(self, request, view):
sense_restriccions_acces = settings.ACCES_RESTRINGIT_A_GRUPS is None
api_pot_entrar = sense_restriccions_acces or (
not sense_restriccions_acces and "API" in settings.ACCES_RESTRINGIT_A_GRUPS
)
alumne_id = view.kwargs.get('alumne_id')
# comprovar si l'alumne pertany al responsable
alumnes_del_responsable = request.user.responsable.alumnes_associats.values_list('id', flat=True)
alumne_pertany_a_responsable = alumne_id is not None and int(alumne_id) in alumnes_del_responsable

#comprovar si l'alumne és major de 18 anys
alumne = Alumne.objects.get(id=alumne_id) if alumne_id else None
edad = alumne.edat() if alumne else None
alumne_es_major_de_18 = edad is not None and edad >= 18

return (
bool(request.user)
and request.user.is_authenticated
and request.user.alumne_app_set.exists()
and request.user.groups.filter(name="API").exists()
and api_pot_entrar
and (alumne_pertany_a_responsable or alumne_es_major_de_18)
)

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EsUsuariDeLaAPI.has_permission currently assumes request.user.responsable exists and will raise an AttributeError for non-responsable users (e.g. professors or almn* users). Also, alumne_es_major_de_18 is computed from the requested alumne_id, which would allow any authenticated user to access any alumne ≥18 by guessing the id. Consider using getRol(request.user, request) and enforcing: (1) responsables can only access alumne_id in responsable.get_alumnes_associats() (and only if the alumne is <18 per requirements), and (2) almn* users can only access their own alumne_id and only if they are ≥18; return False on missing/invalid alumne_id.

Copilot uses AI. Check for mistakes.
Comment thread aula/mblapp/views.py
Comment on lines +427 to +432
@api_view(["GET"])
@permission_classes((IsAuthenticated,))
def alumnes_associats(request):
professor, responsable, alumne = getRol(request.user, request)
associats = responsable.get_alumnes_associats()
content = []

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alumnes_associats is only protected by IsAuthenticated but then unconditionally calls responsable.get_alumnes_associats(). For non-responsable authenticated users this will crash (500). Consider restricting this endpoint to responsables (via a dedicated permission or an explicit getRol check returning 403) before accessing responsable.

Copilot uses AI. Check for mistakes.
@@ -288,12 +288,6 @@ class AbstractAlumne(models.Model):
on_delete=models.SET_NULL,
)

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usuaris_app_associats was removed from AbstractAlumne, but there are still runtime references to this field elsewhere (e.g. aula/apps/extEsfera/sincronitzaEsfera.py and aula/apps/extSaga/sincronitzaSaga.py call a.usuaris_app_associats.set(...)). Without updating those call sites (or providing an alternative association mechanism), imports/sync jobs will crash with AttributeError.

Suggested change
usuaris_app_associats = models.ManyToManyField(
User,
blank=True,
)

Copilot uses AI. Check for mistakes.
from aula.apps.sortides.utils_sortides import notifica_sortides
from aula.apps.usuaris.tools import creaNotifUsuari, ultimaNotificacio

from datetime import date

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

date is imported but unused, which will be flagged by flake8 (F401). Please remove it (or use it) to keep the module lint-clean.

Suggested change
from datetime import date

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +60
# Get an admin user for credentials (required by Sortida signals)
try:
admin_user = User.objects.filter(is_superuser=True).first()
if not admin_user:
# Fallback if no superuser
admin_user = User.objects.first()
except Exception as e:
print(f"Error fetching user: {e}")
return

created_activities = []

with transaction.atomic():
for original in activities_to_duplicate:
# Duplicate the instance
new_activity = Sortida()

# Attach credentials (user, l4) to bypass permission checks or satisfy signal
# l4=True usually means internal/privileged
new_activity.credentials = (admin_user, True)

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the database has no users, admin_user will remain None and new_activity.credentials = (admin_user, True) can break downstream signals/permission logic expecting a real user. Consider explicitly failing with a clear message when no suitable user is found (or creating a dedicated service user) before entering the transaction loop.

Copilot uses AI. Check for mistakes.
Comment thread aula/mblapp/views.py
Comment on lines +64 to 66
professor, responsable, alumne = getRol(request.user, request)
alumne = Alumne.objects.get(id=alumne_id)

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alumne.objects.get(id=alumne_id) will raise Alumne.DoesNotExist and return a 500 if an invalid/removed alumne_id is requested. Since alumne_id is user-controlled (URL param), consider using get_object_or_404 or catching DoesNotExist and returning a DRF validation error/404 response.

Copilot uses AI. Check for mistakes.
Comment thread aula/mblapp/urls.py
mblapp_views.detallSortida,
name="appmobil__api__detall_sortida",
),
re_path(r"^alumnes_associats/$", mblapp_views.alumnes_associats, name="appmobil__api__alunnes_associats"),

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL name appmobil__api__alunnes_associats contains a typo (alunnes). Since this is used for reverse()/linking, please correct the name to avoid confusion and potential broken reverses.

Suggested change
re_path(r"^alumnes_associats/$", mblapp_views.alumnes_associats, name="appmobil__api__alunnes_associats"),
re_path(r"^alumnes_associats/$", mblapp_views.alumnes_associats, name="appmobil__api__alumnes_associats"),

Copilot uses AI. Check for mistakes.
Comment thread aula/apps/relacioFamilies/views.py Outdated
if a.exists():
candidat = a.first()
# No permetre seleccionar alumnes majors d'edat
if candidat in responsable.get_alumnes_associats() and candidat.edat() < 18:

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

candidat.edat() will raise an exception when candidat.data_neixement is null (see Alumne.edat() implementation). Please guard this check (e.g., ensure data_neixement exists before calling edat()), otherwise a responsible selecting an alumne without birthdate will 500.

Suggested change
if candidat in responsable.get_alumnes_associats() and candidat.edat() < 18:
if (
candidat in responsable.get_alumnes_associats()
and candidat.data_neixement
and candidat.edat() < 18
):

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +84
# Copy fields
# Max length for titol is 40. Prefix is "DUPLICADA: " (11 chars).
# We need to truncate original part to 29 chars.
original_titol_truncated = original.titol[:29]
new_activity.titol = f"DUPLICADA: {original_titol_truncated}"
new_activity.descripcio = original.descripcio if hasattr(original, 'descripcio') else "" # Check if field exists, though model definition says 'programa_de_la_sortida' is description
# Mapping based on models.py inspection:
# 'programa_de_la_sortida' is "Descripció de l'activitat"
new_activity.programa_de_la_sortida = original.programa_de_la_sortida

new_activity.preu_per_alumne = original.preu_per_alumne
new_activity.tipus_de_pagament = original.tipus_de_pagament
new_activity.tipus = original.tipus
new_activity.subtipus = original.subtipus
new_activity.ambit = original.ambit
new_activity.ciutat = original.ciutat
new_activity.materia = original.materia
new_activity.condicions_generals = original.condicions_generals
new_activity.comentaris_interns = original.comentaris_interns
new_activity.professor_que_proposa = original.professor_que_proposa
new_activity.estat = 'P' # Set to 'Proposada' or 'E' (Esborrany)? Requirement says "same characteristics", but usually new ones start as proposals. Let's keep consistent with valid state. original might be 'R' or 'G'. Let's set to 'P' or copy? Let's copy state but maybe unsafe. Implementation plan said copy. But usually logic implies new dates = new proposal. Let's stick to implementation plan implicitly: "characteristics of origin". But let's check validation. 'E' is safe.
# However, if we want them to show up in lists, 'P' or original state is better.
new_activity.estat = original.estat

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sortida doesn’t define a descripcio field (it’s programa_de_la_sortida), so assigning new_activity.descripcio just adds an in-memory attribute that won’t be persisted and can confuse future readers. Also new_activity.estat is assigned twice; please remove the dead assignment and keep a single, intentional source of truth (copy original vs reset).

Copilot uses AI. Check for mistakes.
Comment on lines +159 to +174
user = student.get_user_associat()
if user:
user.is_active = True
user.set_password("djau")
user.save()
print(f" Alumne: {student.nom} {student.cognoms}, Usuari: {user.username}, Contrasenya: djau")

# Process associated responsible users
responsables = student.responsables.all()
for resp in responsables:
resp_user = resp.get_user_associat()
if resp_user:
resp_user.is_active = True
resp_user.set_password("djau")
resp_user.save()
print(f" Responsable: {resp.nom} {resp.cognoms}, Usuari: {resp_user.username}, Contrasenya: djau")

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script sets a hard-coded password (djau) and activates accounts for both students and responsables. If accidentally run outside a controlled demo environment, this is a serious security risk. Consider adding an explicit safety guard (e.g., require settings.DEBUG or an environment variable confirmation) and/or generating random passwords instead of a fixed one.

Copilot uses AI. Check for mistakes.
to=settings.AUTH_USER_MODEL,
),
),
]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No s'han de modificar els migrations antics.
S'hauria d'executar makemigrations:
python manage.py makemigrations

self.fields["alumne"].choices = [
(a.id, a.nom + " " + a.cognoms) for a in responsable.get_alumnes_associats()
(a.id, a.nom + " " + a.cognoms) for a in alumnes_menors
]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Només es creen els usuaris responsables si s'informa de les seves dades personals. Si un alumne major d'edat no informa d'aquestes dades, aleshores no hi ha cap usuari responsable.
Si l'alumne ha informat de les dades personals dels seus pares, doncs hauríem de deixar que els pares puguin accedir encara que sigui major d'edat. L'alumne sempre té l'opció de demanar que esborrin les dades dels seus pares i passaria a ser l'únic usuari.
No limitaria l'accés segons aquesta proposta.

"abstract": False,
},
),
]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No s'han de modificar els migrations antics.
S'hauria d'executar makemigrations:
python manage.py makemigrations

name="numero_de_mobil",
field=models.CharField(blank=True, max_length=40),
),
]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No s'han de modificar els migrations antics.
S'hauria d'executar makemigrations:
python manage.py makemigrations

related_name="alumne_app_set",
related_query_name="alumne_app",
)
# DEPRECATED vvv

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tal com indica Copilot, s'han d'eliminar les referències a usuaris_app_associats.

@amorilla amorilla left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El migrations antics s'han de quedar tal qual.
S'hauria de fer "makemigrations".
No limitaria l'accés dels responsables a alumnes majors d'edat, l'alumne major d'edat sempre té l'opció de no proporcionar les dades dels pares o demanar que siguin esborrades.

@ctrl-alt-d ctrl-alt-d left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amorilla té raó que no es pot modificar la història dels migrations.

@juaky

juaky commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator Author
  • De moment, per recuperar usuari/contrasenya, es segueix utilitzant el mail.
  • S'han creat noves migracions per deixar les antigues com estaven.
  • S'ha tret la limitació a responsables d'alumnes majors d'edat, i s'ha afegit un avis a tutors quan una alumne/a fa 18 anys, per tal que li faci saber que pot esborrar els seus responsables.
  • S'han tret les referencies a usuaris_app_associats.

Moltes gràcies per les aportacions!!!!

@amorilla amorilla marked this pull request as ready for review May 9, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants