Modificar accés app mòbil#361
Conversation
|
|
El QR servia per controlar la creació dels usuaris responsables de l'alumne. |
|
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
left a comment
There was a problem hiding this comment.
PR too large to review properly — approving (LGTM)
There was a problem hiding this comment.
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_idi 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 viaNotifUsuari/notificacions_familia. Please update this section (and any referenced dependencies likeqrcodeinrequirements.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_qris now always set toNone, butpotEntrarstill 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_mobilfield / 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.
| 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) | ||
| ) |
There was a problem hiding this comment.
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.
| @api_view(["GET"]) | ||
| @permission_classes((IsAuthenticated,)) | ||
| def alumnes_associats(request): | ||
| professor, responsable, alumne = getRol(request.user, request) | ||
| associats = responsable.get_alumnes_associats() | ||
| content = [] |
There was a problem hiding this comment.
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.
| @@ -288,12 +288,6 @@ class AbstractAlumne(models.Model): | |||
| on_delete=models.SET_NULL, | |||
| ) | |||
|
|
|||
There was a problem hiding this comment.
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.
| usuaris_app_associats = models.ManyToManyField( | |
| User, | |
| blank=True, | |
| ) |
| from aula.apps.sortides.utils_sortides import notifica_sortides | ||
| from aula.apps.usuaris.tools import creaNotifUsuari, ultimaNotificacio | ||
|
|
||
| from datetime import date |
There was a problem hiding this comment.
date is imported but unused, which will be flagged by flake8 (F401). Please remove it (or use it) to keep the module lint-clean.
| from datetime import date |
| # 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) | ||
|
|
There was a problem hiding this comment.
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.
| professor, responsable, alumne = getRol(request.user, request) | ||
| alumne = Alumne.objects.get(id=alumne_id) | ||
|
|
There was a problem hiding this comment.
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.
| mblapp_views.detallSortida, | ||
| name="appmobil__api__detall_sortida", | ||
| ), | ||
| re_path(r"^alumnes_associats/$", mblapp_views.alumnes_associats, name="appmobil__api__alunnes_associats"), |
There was a problem hiding this comment.
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.
| 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"), |
| if a.exists(): | ||
| candidat = a.first() | ||
| # No permetre seleccionar alumnes majors d'edat | ||
| if candidat in responsable.get_alumnes_associats() and candidat.edat() < 18: |
There was a problem hiding this comment.
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.
| 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 | |
| ): |
| # 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 | ||
|
|
There was a problem hiding this comment.
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).
| 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") |
There was a problem hiding this comment.
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.
| to=settings.AUTH_USER_MODEL, | ||
| ), | ||
| ), | ||
| ] |
There was a problem hiding this comment.
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 | ||
| ] |
There was a problem hiding this comment.
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, | ||
| }, | ||
| ), | ||
| ] |
There was a problem hiding this comment.
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), | ||
| ), | ||
| ] |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Tal com indica Copilot, s'han d'eliminar les referències a usuaris_app_associats.
amorilla
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
@amorilla té raó que no es pot modificar la història dels migrations.
Moltes gràcies per les aportacions!!!! |
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:
source venv/bin/activate
(venv) python3 manage.py migrate