mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 18:08:21 +02:00
Compare commits
26 Commits
6f67d2c629
...
faster_ci
Author | SHA1 | Date | |
---|---|---|---|
3eed93e346 | |||
4da523a1ba | |||
e74ff54468 | |||
2e49c9ffbd | |||
d20a1038a8 | |||
f6b711bb1b | |||
893d87a9e1 | |||
9f3323c73e | |||
c57f81b920 | |||
0636d84286 | |||
ed06901fae | |||
28932f316b | |||
9b50ba722c | |||
3e3e61d23f | |||
1129815ca3 | |||
c13172d3ff | |||
fcc4121225 | |||
a06f355559 | |||
08df5fcccd | |||
b6c0f9758d | |||
a23093851f | |||
d803ab5ec2 | |||
d7a537b6b5 | |||
0941ee954d | |||
fd11d96d95 | |||
4bfc057454 |
3
.ansible-lint
Normal file
3
.ansible-lint
Normal file
@ -0,0 +1,3 @@
|
||||
skip_list:
|
||||
- command-instead-of-shell # Use shell only when shell functionality is required
|
||||
- experimental # all rules tagged as experimental
|
@ -10,6 +10,7 @@ DJANGO_SECRET_KEY=CHANGE_ME
|
||||
DJANGO_SETTINGS_MODULE=note_kfet.settings
|
||||
CONTACT_EMAIL=tresorerie.bde@localhost
|
||||
NOTE_URL=localhost
|
||||
DOMAIN=localhost
|
||||
|
||||
# Config for mails. Only used in production
|
||||
NOTE_MAIL=notekfet@localhost
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -42,13 +42,11 @@ map.json
|
||||
backups/
|
||||
/static/
|
||||
/media/
|
||||
/tmp/
|
||||
|
||||
# Virtualenv
|
||||
env/
|
||||
venv/
|
||||
db.sqlite3
|
||||
shell.nix
|
||||
|
||||
# ansibles customs host
|
||||
ansible/host_vars/*.yaml
|
||||
|
@ -8,55 +8,27 @@ variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# Debian Buster
|
||||
# py37-django22:
|
||||
# stage: test
|
||||
# image: debian:buster-backports
|
||||
# before_script:
|
||||
# - >
|
||||
# apt-get update &&
|
||||
# apt-get install --no-install-recommends -t buster-backports -y
|
||||
# python3-django python3-django-crispy-forms
|
||||
# python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||
# python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||
# python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||
# python3-bs4 python3-setuptools tox texlive-xetex
|
||||
# script: tox -e py37-django22
|
||||
py37-django22:
|
||||
stage: test
|
||||
image: otthorn/nk20_ci_37
|
||||
script: tox -e py37-django22
|
||||
|
||||
# Ubuntu 20.04
|
||||
py38-django22:
|
||||
stage: test
|
||||
image: ubuntu:20.04
|
||||
before_script:
|
||||
# Fix tzdata prompt
|
||||
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
||||
- >
|
||||
apt-get update &&
|
||||
apt-get install --no-install-recommends -y
|
||||
python3-django python3-django-crispy-forms
|
||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
image: otthorn/nk20_ci_38
|
||||
script: tox -e py38-django22
|
||||
|
||||
# Debian Bullseye
|
||||
py39-django22:
|
||||
stage: test
|
||||
image: debian:bullseye
|
||||
before_script:
|
||||
- >
|
||||
apt-get update &&
|
||||
apt-get install --no-install-recommends -y
|
||||
python3-django python3-django-crispy-forms
|
||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
image: otthorn/nk20_ci_39
|
||||
script: tox -e py39-django22
|
||||
|
||||
# Tox linter
|
||||
linters:
|
||||
stage: quality-assurance
|
||||
image: debian:bullseye
|
||||
image: debian:buster-backports
|
||||
before_script:
|
||||
- apt-get update && apt-get install -y tox
|
||||
script: tox -e linters
|
||||
@ -64,6 +36,20 @@ linters:
|
||||
# Be nice to new contributors, but please use `tox`
|
||||
allow_failure: true
|
||||
|
||||
# Ansible linter
|
||||
ansible-linter:
|
||||
stage: quality-assurance
|
||||
image: otthorn/nk20_ci_ansiblelint
|
||||
script: ansible-lint ansible/
|
||||
|
||||
# Docker linter
|
||||
docker-linter:
|
||||
stage: quality-assurance
|
||||
image: hadolint/hadolint
|
||||
script:
|
||||
- hadolint -c .hadolint Dockerfile
|
||||
- hadolint -c .hadolint docker_ci/Dockerfile.*
|
||||
|
||||
# Compile documentation
|
||||
documentation:
|
||||
stage: docs
|
||||
|
4
.hadolint
Normal file
4
.hadolint
Normal file
@ -0,0 +1,4 @@
|
||||
ignored:
|
||||
- DL3008 # Do not force to pin version in apt (Debian)
|
||||
- DL3013 # Do not force to pin version in pip (PyPI)
|
||||
- DL3018 # Do not force to pin version in apk (Alpine)
|
@ -1,8 +1,8 @@
|
||||
# NoteKfet 2020
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
[](https://gitlab.crans.org/bde/nk20/commits/main)
|
||||
[](https://gitlab.crans.org/bde/nk20/commits/main)
|
||||
[](https://gitlab.crans.org/bde/nk20/commits/master)
|
||||
[](https://gitlab.crans.org/bde/nk20/commits/master)
|
||||
|
||||
## Table des matières
|
||||
|
||||
@ -279,8 +279,7 @@ Le cahier des charges initial est disponible [sur le Wiki Crans](https://wiki.cr
|
||||
La documentation des classes et fonctions est directement dans le code et est explorable à partir de la partie documentation de l'interface d'administration de Django.
|
||||
**Commentez votre code !**
|
||||
|
||||
La documentation plus haut niveau sur le développement et sur l'utilisation
|
||||
est disponible sur <https://note.crans.org/doc> et également dans le dossier `docs`.
|
||||
La documentation plus haut niveau sur le développement est disponible sur [le Wiki associé au dépôt Git](https://gitlab.crans.org/bde/nk20/-/wikis/home).
|
||||
|
||||
## FAQ
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
prompt: "Password of the database (leave it blank to skip database init)"
|
||||
private: yes
|
||||
vars:
|
||||
mirror: eclats.crans.org
|
||||
mirror: mirror.crans.org
|
||||
roles:
|
||||
- 1-apt-basic
|
||||
- 2-nk20
|
||||
|
6
ansible/host_vars/bde-nk20-beta.adh.crans.org.yml
Normal file
6
ansible/host_vars/bde-nk20-beta.adh.crans.org.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
note:
|
||||
server_name: note-beta.crans.org
|
||||
git_branch: beta
|
||||
cron_enabled: false
|
||||
email: notekfet2020@lists.crans.org
|
@ -2,6 +2,5 @@
|
||||
note:
|
||||
server_name: note-dev.crans.org
|
||||
git_branch: beta
|
||||
serve_static: false
|
||||
cron_enabled: false
|
||||
email: notekfet2020@lists.crans.org
|
||||
|
@ -1,7 +1,6 @@
|
||||
---
|
||||
note:
|
||||
server_name: note.crans.org
|
||||
git_branch: main
|
||||
serve_static: true
|
||||
git_branch: master
|
||||
cron_enabled: true
|
||||
email: notekfet2020@lists.crans.org
|
||||
|
@ -1,5 +1,6 @@
|
||||
[dev]
|
||||
bde-note-dev.adh.crans.org
|
||||
bde-nk20-beta.adh.crans.org
|
||||
|
||||
[prod]
|
||||
bde-note.adh.crans.org
|
||||
|
@ -1,15 +1,14 @@
|
||||
---
|
||||
- name: Add buster-backports to apt sources if needed
|
||||
- name: Add buster-backports to apt sources
|
||||
apt_repository:
|
||||
repo: deb http://{{ mirror }}/debian buster-backports main
|
||||
state: present
|
||||
when:
|
||||
- ansible_distribution == "Debian"
|
||||
- ansible_distribution_major_version | int == 10
|
||||
when: ansible_facts['distribution'] == "Debian"
|
||||
|
||||
- name: Install note_kfet APT dependencies
|
||||
apt:
|
||||
update_cache: true
|
||||
default_release: "{{ 'buster-backports' if ansible_facts['distribution'] == 'Debian' }}"
|
||||
install_recommends: false
|
||||
name:
|
||||
# Common tools
|
||||
|
@ -41,7 +41,6 @@ server {
|
||||
# max upload size
|
||||
client_max_body_size 75M; # adjust to taste
|
||||
|
||||
{% if note.serve_static %}
|
||||
# Django media
|
||||
location /media {
|
||||
alias /var/www/note_kfet/media; # your Django project's media files - amend as required
|
||||
@ -51,7 +50,6 @@ server {
|
||||
alias /var/www/note_kfet/static; # your Django project's static files - amend as required
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
location /doc {
|
||||
alias /var/www/documentation; # The documentation of the project
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'activity.apps.ActivityConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -6,7 +6,7 @@
|
||||
"name": "Pot",
|
||||
"manage_entries": true,
|
||||
"can_invite": true,
|
||||
"guest_entry_fee": 1000
|
||||
"guest_entry_fee": 500
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -28,25 +28,5 @@
|
||||
"can_invite": false,
|
||||
"guest_entry_fee": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "activity.activitytype",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "Soir\u00e9e avec entrées",
|
||||
"manage_entries": true,
|
||||
"can_invite": false,
|
||||
"guest_entry_fee": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "activity.activitytype",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"name": "Soir\u00e9e avec invitations",
|
||||
"manage_entries": true,
|
||||
"can_invite": true,
|
||||
"guest_entry_fee": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import timedelta
|
||||
@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from member.models import Club
|
||||
from note.models import Note, NoteUser
|
||||
from note_kfet.inputs import Autocomplete, DateTimePickerInput
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from note_kfet.middlewares import get_current_authenticated_user
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .models import Activity, Guest
|
||||
@ -24,16 +24,10 @@ class ActivityForm(forms.ModelForm):
|
||||
self.fields["attendees_club"].initial = Club.objects.get(name="Kfet")
|
||||
self.fields["attendees_club"].widget.attrs["placeholder"] = "Kfet"
|
||||
clubs = list(Club.objects.filter(PermissionBackend
|
||||
.filter_queryset(get_current_request(), Club, "view")).all())
|
||||
.filter_queryset(get_current_authenticated_user(), Club, "view")).all())
|
||||
shuffle(clubs)
|
||||
self.fields["organizer"].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..."
|
||||
|
||||
def clean_organizer(self):
|
||||
organizer = self.cleaned_data['organizer']
|
||||
if not organizer.note.is_active:
|
||||
self.add_error('organiser', _('The note of this club is inactive.'))
|
||||
return organizer
|
||||
|
||||
def clean_date_end(self):
|
||||
date_end = self.cleaned_data["date_end"]
|
||||
date_start = self.cleaned_data["date_start"]
|
||||
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.28 on 2024-03-23 13:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('activity', '0002_auto_20200904_2341'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='activity',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, default='', verbose_name='description'),
|
||||
),
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import os
|
||||
@ -66,8 +66,6 @@ class Activity(models.Model):
|
||||
|
||||
description = models.TextField(
|
||||
verbose_name=_('description'),
|
||||
blank=True,
|
||||
default="",
|
||||
)
|
||||
|
||||
location = models.CharField(
|
||||
@ -125,14 +123,6 @@ class Activity(models.Model):
|
||||
verbose_name=_('open'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("activity")
|
||||
verbose_name_plural = _("activities")
|
||||
unique_together = ("name", "date_start", "date_end",)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
@ -154,6 +144,14 @@ class Activity(models.Model):
|
||||
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
|
||||
return ret
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("activity")
|
||||
verbose_name_plural = _("activities")
|
||||
unique_together = ("name", "date_start", "date_end",)
|
||||
|
||||
|
||||
class Entry(models.Model):
|
||||
"""
|
||||
@ -254,13 +252,14 @@ class Guest(models.Model):
|
||||
verbose_name=_("inviter"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("guest")
|
||||
verbose_name_plural = _("guests")
|
||||
unique_together = ("activity", "last_name", "first_name", )
|
||||
|
||||
def __str__(self):
|
||||
return self.first_name + " " + self.last_name
|
||||
@property
|
||||
def has_entry(self):
|
||||
try:
|
||||
if self.entry:
|
||||
return True
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
@ -291,14 +290,13 @@ class Guest(models.Model):
|
||||
|
||||
return super().save(force_insert, force_update, using, update_fields)
|
||||
|
||||
@property
|
||||
def has_entry(self):
|
||||
try:
|
||||
if self.entry:
|
||||
return True
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
def __str__(self):
|
||||
return self.first_name + " " + self.last_name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("guest")
|
||||
verbose_name_plural = _("guests")
|
||||
unique_together = ("activity", "last_name", "first_name", )
|
||||
|
||||
|
||||
class GuestTransaction(Transaction):
|
||||
|
@ -1,9 +1,7 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.utils import timezone
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
from django_tables2 import A
|
||||
@ -54,7 +52,7 @@ class GuestTable(tables.Table):
|
||||
def render_entry(self, record):
|
||||
if record.has_entry:
|
||||
return str(_("Entered on ") + str(_("{:%Y-%m-%d %H:%M:%S}").format(record.entry.time, )))
|
||||
return mark_safe('<button id="{id}" class="btn btn-danger btn-sm" onclick="remove_guest(this.id)"> '
|
||||
return format_html('<button id="{id}" class="btn btn-danger btn-sm" onclick="remove_guest(this.id)"> '
|
||||
'{delete_trans}</button>'.format(id=record.id, delete_trans=_("remove").capitalize()))
|
||||
|
||||
|
||||
@ -93,7 +91,7 @@ class EntryTable(tables.Table):
|
||||
if hasattr(record, 'username'):
|
||||
username = record.username
|
||||
if username != value:
|
||||
return mark_safe(escape(value) + " <em>aka.</em> " + escape(username))
|
||||
return format_html(value + " <em>aka.</em> " + username)
|
||||
return value
|
||||
|
||||
def render_balance(self, value):
|
||||
|
@ -63,12 +63,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
refreshBalance();
|
||||
}
|
||||
|
||||
alias_obj.keyup(function(event) {
|
||||
let code = event.originalEvent.keyCode
|
||||
if (65 <= code <= 122 || code === 13) {
|
||||
debounce(reloadTable)()
|
||||
}
|
||||
});
|
||||
alias_obj.keyup(reloadTable);
|
||||
|
||||
$(document).ready(init);
|
||||
|
||||
|
@ -18,26 +18,3 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
var date_end = document.getElementById("id_date_end");
|
||||
var date_start = document.getElementById("id_date_start");
|
||||
|
||||
function update_date_end (){
|
||||
if(date_end.value=="" || date_end.value<date_start.value){
|
||||
date_end.value = date_start.value;
|
||||
};
|
||||
};
|
||||
|
||||
function update_date_start (){
|
||||
if(date_start.value=="" || date_end.value<date_start.value){
|
||||
date_start.value = date_end.value;
|
||||
};
|
||||
};
|
||||
|
||||
date_start.addEventListener('focusout', update_date_end);
|
||||
date_end.addEventListener('focusout', update_date_start);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import timedelta
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from hashlib import md5
|
||||
@ -17,8 +17,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.generic import DetailView, TemplateView, UpdateView
|
||||
from django.views.generic.list import ListView
|
||||
from django_tables2.views import MultiTableMixin
|
||||
from django_tables2.views import SingleTableView
|
||||
from note.models import Alias, NoteSpecial, NoteUser
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||
@ -58,42 +57,30 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
|
||||
|
||||
|
||||
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
|
||||
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
"""
|
||||
Displays all Activities, and classify if they are on-going or upcoming ones.
|
||||
"""
|
||||
model = Activity
|
||||
tables = [ActivityTable, ActivityTable]
|
||||
table_class = ActivityTable
|
||||
ordering = ('-date_start',)
|
||||
extra_context = {"title": _("Activities")}
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
return super().get_queryset(**kwargs).distinct()
|
||||
|
||||
def get_tables(self):
|
||||
tables = super().get_tables()
|
||||
|
||||
tables[0].prefix = "all-"
|
||||
tables[1].prefix = "upcoming-"
|
||||
return tables
|
||||
|
||||
def get_tables_data(self):
|
||||
# first table = all activities, second table = upcoming
|
||||
return [
|
||||
self.get_queryset().order_by("-date_start"),
|
||||
Activity.objects.filter(date_end__gt=timezone.now())
|
||||
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))
|
||||
.distinct()
|
||||
.order_by("date_start")
|
||||
]
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().distinct()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
tables = context["tables"]
|
||||
for name, table in zip(["table", "upcoming"], tables):
|
||||
context[name] = table
|
||||
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
|
||||
context['upcoming'] = ActivityTable(
|
||||
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")),
|
||||
prefix='upcoming-',
|
||||
)
|
||||
|
||||
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
|
||||
started_activities = Activity.objects\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
||||
.filter(open=True, valid=True).all()
|
||||
context["started_activities"] = started_activities
|
||||
|
||||
return context
|
||||
@ -111,7 +98,7 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context = super().get_context_data()
|
||||
|
||||
table = GuestTable(data=Guest.objects.filter(activity=self.object)
|
||||
.filter(PermissionBackend.filter_queryset(self.request, Guest, "view")))
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view")))
|
||||
context["guests"] = table
|
||||
|
||||
context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start)
|
||||
@ -157,15 +144,15 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\
|
||||
.filter(pk=self.kwargs["pk"]).first()
|
||||
form.activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
||||
.get(pk=self.kwargs["pk"])
|
||||
form.fields["inviter"].initial = self.request.user.note
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.activity = Activity.objects\
|
||||
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")).get(pk=self.kwargs["pk"])
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"])
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
@ -183,13 +170,10 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||
Don't display the entry interface if the user has no right to see it (no right to add an entry for itself),
|
||||
it is closed or doesn't manage entries.
|
||||
"""
|
||||
if not self.request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
activity = Activity.objects.get(pk=self.kwargs["pk"])
|
||||
|
||||
sample_entry = Entry(activity=activity, note=self.request.user.note)
|
||||
if not PermissionBackend.check_perm(self.request, "activity.add_entry", sample_entry):
|
||||
if not PermissionBackend.check_perm(self.request.user, "activity.add_entry", sample_entry):
|
||||
raise PermissionDenied(_("You are not allowed to display the entry interface for this activity."))
|
||||
|
||||
if not activity.activity_type.manage_entries:
|
||||
@ -207,8 +191,8 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||
guest_qs = Guest.objects\
|
||||
.annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\
|
||||
.filter(activity=activity)\
|
||||
.filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))\
|
||||
.order_by('last_name', 'first_name')
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\
|
||||
.order_by('last_name', 'first_name').distinct()
|
||||
|
||||
if "search" in self.request.GET and self.request.GET["search"]:
|
||||
pattern = self.request.GET["search"]
|
||||
@ -222,7 +206,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||
)
|
||||
else:
|
||||
guest_qs = guest_qs.none()
|
||||
return guest_qs.distinct()
|
||||
return guest_qs
|
||||
|
||||
def get_invited_note(self, activity):
|
||||
"""
|
||||
@ -246,7 +230,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||
)
|
||||
|
||||
# Filter with permission backend
|
||||
note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request, Alias, "view"))
|
||||
note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
|
||||
|
||||
if "search" in self.request.GET and self.request.GET["search"]:
|
||||
pattern = self.request.GET["search"]
|
||||
@ -272,7 +256,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\
|
||||
activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
||||
.distinct().get(pk=self.kwargs["pk"])
|
||||
context["activity"] = activity
|
||||
|
||||
@ -297,9 +281,9 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||
context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
|
||||
|
||||
activities_open = Activity.objects.filter(open=True).filter(
|
||||
PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all()
|
||||
PermissionBackend.filter_queryset(self.request.user, Activity, "view")).distinct().all()
|
||||
context["activities_open"] = [a for a in activities_open
|
||||
if PermissionBackend.check_perm(self.request,
|
||||
if PermissionBackend.check_perm(self.request.user,
|
||||
"activity.add_entry",
|
||||
Entry(activity=a, note=self.request.user.note,))]
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'api.apps.APIConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,5 +0,0 @@
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
|
||||
|
||||
class CustomPagination(PageNumberPagination):
|
||||
page_size_query_param = 'page_size'
|
@ -1,20 +1,13 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
from member.api.serializers import ProfileSerializer, MembershipSerializer
|
||||
from member.models import Membership
|
||||
from note.api.serializers import NoteSerializer
|
||||
from note.models import Alias
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class UserSerializer(ModelSerializer):
|
||||
"""
|
||||
REST API Serializer for Users.
|
||||
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
||||
@ -29,7 +22,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class ContentTypeSerializer(serializers.ModelSerializer):
|
||||
class ContentTypeSerializer(ModelSerializer):
|
||||
"""
|
||||
REST API Serializer for Users.
|
||||
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
||||
@ -38,54 +31,3 @@ class ContentTypeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ContentType
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class OAuthSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Informations that are transmitted by OAuth.
|
||||
For now, this includes user, profile and valid memberships.
|
||||
This should be better managed later.
|
||||
"""
|
||||
normalized_name = serializers.SerializerMethodField()
|
||||
|
||||
profile = serializers.SerializerMethodField()
|
||||
|
||||
note = serializers.SerializerMethodField()
|
||||
|
||||
memberships = serializers.SerializerMethodField()
|
||||
|
||||
def get_normalized_name(self, obj):
|
||||
return Alias.normalize(obj.username)
|
||||
|
||||
def get_profile(self, obj):
|
||||
# Display the profile of the user only if we have rights to see it.
|
||||
return ProfileSerializer().to_representation(obj.profile) \
|
||||
if PermissionBackend.check_perm(get_current_request(), 'member.view_profile', obj.profile) else None
|
||||
|
||||
def get_note(self, obj):
|
||||
# Display the note of the user only if we have rights to see it.
|
||||
return NoteSerializer().to_representation(obj.note) \
|
||||
if PermissionBackend.check_perm(get_current_request(), 'note.view_note', obj.note) else None
|
||||
|
||||
def get_memberships(self, obj):
|
||||
# Display only memberships that we are allowed to see.
|
||||
return serializers.ListSerializer(child=MembershipSerializer()).to_representation(
|
||||
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now())
|
||||
.filter(PermissionBackend.filter_queryset(get_current_request(), Membership, 'view')))
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = (
|
||||
'id',
|
||||
'username',
|
||||
'normalized_name',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'is_superuser',
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'profile',
|
||||
'note',
|
||||
'memberships',
|
||||
)
|
||||
|
@ -1,9 +1,8 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
from urllib.parse import quote_plus
|
||||
from warnings import warn
|
||||
|
||||
@ -153,8 +152,6 @@ class TestAPI(TestCase):
|
||||
value = value.isoformat()
|
||||
elif isinstance(value, ImageFieldFile):
|
||||
value = value.name
|
||||
elif isinstance(value, Decimal):
|
||||
value = str(value)
|
||||
query = json.dumps({field.name: value})
|
||||
|
||||
# Create sample permission
|
||||
|
@ -1,11 +1,10 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url, include
|
||||
from rest_framework import routers
|
||||
|
||||
from .views import UserInformationView
|
||||
from .viewsets import ContentTypeViewSet, UserViewSet
|
||||
|
||||
# Routers provide an easy way of automatically determining the URL conf.
|
||||
@ -48,6 +47,5 @@ app_name = 'api'
|
||||
# Additionally, we include login URLs for the browsable API.
|
||||
urlpatterns = [
|
||||
url('^', include(router.urls)),
|
||||
url('^me/', UserInformationView.as_view()),
|
||||
url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
]
|
||||
|
@ -1,20 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
|
||||
from .serializers import OAuthSerializer
|
||||
|
||||
|
||||
class UserInformationView(RetrieveAPIView):
|
||||
"""
|
||||
These fields are give to OAuth authenticators.
|
||||
"""
|
||||
serializer_class = OAuthSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return User.objects.filter(pk=self.request.user.pk)
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@ -9,6 +9,7 @@ from django.contrib.auth.models import User
|
||||
from rest_framework.filters import SearchFilter
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
|
||||
from permission.backends import PermissionBackend
|
||||
from note_kfet.middlewares import get_current_session
|
||||
from note.models import Alias
|
||||
|
||||
from .serializers import UserSerializer, ContentTypeSerializer
|
||||
@ -24,7 +25,9 @@ class ReadProtectedModelViewSet(ModelViewSet):
|
||||
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.filter(PermissionBackend.filter_queryset(self.request, self.model, "view")).distinct()
|
||||
user = self.request.user
|
||||
get_current_session().setdefault("permission_mask", 42)
|
||||
return self.queryset.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
||||
|
||||
|
||||
class ReadOnlyProtectedModelViewSet(ReadOnlyModelViewSet):
|
||||
@ -37,7 +40,9 @@ class ReadOnlyProtectedModelViewSet(ReadOnlyModelViewSet):
|
||||
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.filter(PermissionBackend.filter_queryset(self.request, self.model, "view")).distinct()
|
||||
user = self.request.user
|
||||
get_current_session().setdefault("permission_mask", 42)
|
||||
return self.queryset.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
||||
|
||||
|
||||
class UserViewSet(ReadProtectedModelViewSet):
|
||||
|
@ -1,37 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
from django.db import transaction
|
||||
from note_kfet.admin import admin_site
|
||||
|
||||
from .models import Allergen, BasicFood, QRCode, TransformedFood
|
||||
|
||||
|
||||
@admin.register(QRCode, site=admin_site)
|
||||
class QRCodeAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(BasicFood, site=admin_site)
|
||||
class BasicFoodAdmin(admin.ModelAdmin):
|
||||
@transaction.atomic
|
||||
def save_related(self, *args, **kwargs):
|
||||
ans = super().save_related(*args, **kwargs)
|
||||
args[1].instance.update()
|
||||
return ans
|
||||
|
||||
|
||||
@admin.register(TransformedFood, site=admin_site)
|
||||
class TransformedFoodAdmin(admin.ModelAdmin):
|
||||
exclude = ["allergens", "expiry_date"]
|
||||
|
||||
@transaction.atomic
|
||||
def save_related(self, request, form, *args, **kwargs):
|
||||
super().save_related(request, form, *args, **kwargs)
|
||||
form.instance.update()
|
||||
|
||||
|
||||
@admin.register(Allergen, site=admin_site)
|
||||
class AllergenAdmin(admin.ModelAdmin):
|
||||
pass
|
@ -1,11 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FoodkfetConfig(AppConfig):
|
||||
name = 'food'
|
||||
verbose_name = _('food')
|
@ -1,107 +0,0 @@
|
||||
[
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "alcohol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "celery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "crustecean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "egg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "fish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"name": "gluten"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"name": "groundnut"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"name": "lupine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"name": "milk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"name": "mollusc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"name": "mustard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"name": "nut"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"name": "sesame"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"name": "soy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "food.allergen",
|
||||
"pk": 15,
|
||||
"fields": {
|
||||
"name": "sulphite"
|
||||
}
|
||||
}
|
||||
]
|
@ -1,99 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from random import shuffle
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from member.models import Club
|
||||
from note_kfet.inputs import Autocomplete, DateTimePickerInput
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .models import BasicFood, QRCode, TransformedFood
|
||||
|
||||
|
||||
class AddIngredientForms(forms.ModelForm):
|
||||
"""
|
||||
Form for add an ingredient
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['ingredient'].queryset = self.fields['ingredient'].queryset.filter(is_ready=False)
|
||||
|
||||
class Meta:
|
||||
model = TransformedFood
|
||||
fields = ('ingredient',)
|
||||
|
||||
|
||||
class BasicFoodForms(forms.ModelForm):
|
||||
"""
|
||||
Form for add non-transformed food
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['name'].widget.attrs.update({"autofocus": "autofocus"})
|
||||
self.fields['name'].required = True
|
||||
self.fields['owner'].required = True
|
||||
|
||||
# Some example
|
||||
self.fields['name'].widget.attrs.update({"placeholder": _("pasta")})
|
||||
clubs = list(Club.objects.filter(PermissionBackend.filter_queryset(get_current_request(), Club, "change")).all())
|
||||
shuffle(clubs)
|
||||
self.fields['owner'].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..."
|
||||
|
||||
class Meta:
|
||||
model = BasicFood
|
||||
fields = ('name', 'owner', 'date_type', 'expiry_date', 'allergens')
|
||||
widgets = {
|
||||
"owner": Autocomplete(
|
||||
model=Club,
|
||||
attrs={"api_url": "/api/members/club/"},
|
||||
),
|
||||
'expiry_date': DateTimePickerInput(),
|
||||
}
|
||||
|
||||
|
||||
class QRCodeForms(forms.ModelForm):
|
||||
"""
|
||||
Form for create QRCode
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['food_container'].queryset = self.fields['food_container'].queryset.filter(is_ready=False)
|
||||
|
||||
class Meta:
|
||||
model = QRCode
|
||||
fields = ('food_container',)
|
||||
|
||||
|
||||
class TransformedFoodForms(forms.ModelForm):
|
||||
"""
|
||||
Form for add transformed food
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['name'].widget.attrs.update({"autofocus": "autofocus"})
|
||||
self.fields['name'].required = True
|
||||
self.fields['owner'].required = True
|
||||
self.fields['creation_date'].required = True
|
||||
self.fields['creation_date'].initial = timezone.now
|
||||
self.fields['is_active'].initial = True
|
||||
|
||||
# Some example
|
||||
self.fields['name'].widget.attrs.update({"placeholder": _("lasagna")})
|
||||
clubs = list(Club.objects.filter(PermissionBackend.filter_queryset(get_current_request(), Club, "change")).all())
|
||||
shuffle(clubs)
|
||||
self.fields['owner'].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..."
|
||||
|
||||
class Meta:
|
||||
model = TransformedFood
|
||||
fields = ('name', 'creation_date', 'owner', 'is_active', 'shelf_life')
|
||||
widgets = {
|
||||
"owner": Autocomplete(
|
||||
model=Club,
|
||||
attrs={"api_url": "/api/members/club/"},
|
||||
),
|
||||
'creation_date': DateTimePickerInput(),
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
# Generated by Django 2.2.28 on 2024-07-05 08:57
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('member', '0011_profile_vss_charter_read'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Allergen',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Allergen',
|
||||
'verbose_name_plural': 'Allergens',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Food',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||
('expiry_date', models.DateTimeField(verbose_name='expiry date')),
|
||||
('was_eaten', models.BooleanField(default=False, verbose_name='was eaten')),
|
||||
('is_ready', models.BooleanField(default=False, verbose_name='is ready')),
|
||||
('allergens', models.ManyToManyField(blank=True, to='food.Allergen', verbose_name='allergen')),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='owner')),
|
||||
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_food.food_set+', to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'foods',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BasicFood',
|
||||
fields=[
|
||||
('food_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='food.Food')),
|
||||
('date_type', models.CharField(choices=[('DLC', 'DLC'), ('DDM', 'DDM')], max_length=255)),
|
||||
('arrival_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='arrival date')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Basic food',
|
||||
'verbose_name_plural': 'Basic foods',
|
||||
},
|
||||
bases=('food.food',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QRCode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('qr_code_number', models.PositiveIntegerField(unique=True, verbose_name='QR-code number')),
|
||||
('food_container', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='QR_code', to='food.Food', verbose_name='food container')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'QR-code',
|
||||
'verbose_name_plural': 'QR-codes',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TransformedFood',
|
||||
fields=[
|
||||
('food_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='food.Food')),
|
||||
('creation_date', models.DateTimeField(verbose_name='creation date')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='is active')),
|
||||
('ingredient', models.ManyToManyField(blank=True, related_name='transformed_ingredient_inv', to='food.Food', verbose_name='transformed ingredient')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Transformed food',
|
||||
'verbose_name_plural': 'Transformed foods',
|
||||
},
|
||||
bases=('food.food',),
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.2.28 on 2024-07-06 20:37
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='transformedfood',
|
||||
name='shelf_life',
|
||||
field=models.DurationField(default=datetime.timedelta(days=3), verbose_name='shelf life'),
|
||||
),
|
||||
]
|
@ -1,217 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from member.models import Club
|
||||
from polymorphic.models import PolymorphicModel
|
||||
|
||||
|
||||
class QRCode(models.Model):
|
||||
"""
|
||||
An QRCode model
|
||||
"""
|
||||
qr_code_number = models.PositiveIntegerField(
|
||||
verbose_name=_("QR-code number"),
|
||||
unique=True,
|
||||
)
|
||||
|
||||
food_container = models.OneToOneField(
|
||||
'Food',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='QR_code',
|
||||
verbose_name=_('food container'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("QR-code")
|
||||
verbose_name_plural = _("QR-codes")
|
||||
|
||||
def __str__(self):
|
||||
return _("QR-code number {qr_code_number}").format(qr_code_number=self.qr_code_number)
|
||||
|
||||
|
||||
class Allergen(models.Model):
|
||||
"""
|
||||
A list of allergen and alimentary restrictions
|
||||
"""
|
||||
name = models.CharField(
|
||||
verbose_name=_('name'),
|
||||
max_length=255,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Allergen')
|
||||
verbose_name_plural = _('Allergens')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Food(PolymorphicModel):
|
||||
name = models.CharField(
|
||||
verbose_name=_('name'),
|
||||
max_length=255,
|
||||
)
|
||||
|
||||
owner = models.ForeignKey(
|
||||
Club,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
verbose_name=_('owner'),
|
||||
)
|
||||
|
||||
allergens = models.ManyToManyField(
|
||||
Allergen,
|
||||
blank=True,
|
||||
verbose_name=_('allergen'),
|
||||
)
|
||||
|
||||
expiry_date = models.DateTimeField(
|
||||
verbose_name=_('expiry date'),
|
||||
null=False,
|
||||
)
|
||||
|
||||
was_eaten = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('was eaten'),
|
||||
)
|
||||
|
||||
is_ready = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('is ready'),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
return super().save(force_insert, force_update, using, update_fields)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('food')
|
||||
verbose_name = _('foods')
|
||||
|
||||
|
||||
class BasicFood(Food):
|
||||
"""
|
||||
Food which has been directly buy on supermarket
|
||||
"""
|
||||
date_type = models.CharField(
|
||||
max_length=255,
|
||||
choices=(
|
||||
("DLC", "DLC"),
|
||||
("DDM", "DDM"),
|
||||
)
|
||||
)
|
||||
|
||||
arrival_date = models.DateTimeField(
|
||||
verbose_name=_('arrival date'),
|
||||
default=timezone.now,
|
||||
)
|
||||
|
||||
# label = models.ImageField(
|
||||
# verbose_name=_('food label'),
|
||||
# max_length=255,
|
||||
# blank=False,
|
||||
# null=False,
|
||||
# upload_to='label/',
|
||||
# )
|
||||
|
||||
@transaction.atomic
|
||||
def update_allergens(self):
|
||||
# update parents
|
||||
for parent in self.transformed_ingredient_inv.iterator():
|
||||
parent.update_allergens()
|
||||
|
||||
@transaction.atomic
|
||||
def update_expiry_date(self):
|
||||
# update parents
|
||||
for parent in self.transformed_ingredient_inv.iterator():
|
||||
parent.update_expiry_date()
|
||||
|
||||
@transaction.atomic
|
||||
def update(self):
|
||||
self.update_allergens()
|
||||
self.update_expiry_date()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Basic food')
|
||||
verbose_name_plural = _('Basic foods')
|
||||
|
||||
|
||||
class TransformedFood(Food):
|
||||
"""
|
||||
Transformed food are a mix between basic food and meal
|
||||
"""
|
||||
creation_date = models.DateTimeField(
|
||||
verbose_name=_('creation date'),
|
||||
)
|
||||
|
||||
ingredient = models.ManyToManyField(
|
||||
Food,
|
||||
blank=True,
|
||||
symmetrical=False,
|
||||
related_name='transformed_ingredient_inv',
|
||||
verbose_name=_('transformed ingredient'),
|
||||
)
|
||||
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_('is active'),
|
||||
)
|
||||
|
||||
# Without microbiological analyzes, the storage time is 3 days
|
||||
shelf_life = models.DurationField(
|
||||
verbose_name=_("shelf life"),
|
||||
default=timedelta(days=3),
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def update_allergens(self):
|
||||
# When allergens are changed, simply update the parents' allergens
|
||||
old_allergens = list(self.allergens.all())
|
||||
self.allergens.clear()
|
||||
for ingredient in self.ingredient.iterator():
|
||||
self.allergens.set(self.allergens.union(ingredient.allergens.all()))
|
||||
|
||||
if old_allergens == list(self.allergens.all()):
|
||||
return
|
||||
super().save()
|
||||
|
||||
# update parents
|
||||
for parent in self.transformed_ingredient_inv.iterator():
|
||||
parent.update_allergens()
|
||||
|
||||
@transaction.atomic
|
||||
def update_expiry_date(self):
|
||||
# When expiry_date is changed, simply update the parents' expiry_date
|
||||
old_expiry_date = self.expiry_date
|
||||
self.expiry_date = self.creation_date + self.shelf_life
|
||||
for ingredient in self.ingredient.iterator():
|
||||
self.expiry_date = min(self.expiry_date, ingredient.expiry_date)
|
||||
|
||||
if old_expiry_date == self.expiry_date:
|
||||
return
|
||||
super().save()
|
||||
|
||||
# update parents
|
||||
for parent in self.transformed_ingredient_inv.iterator():
|
||||
parent.update_expiry_date()
|
||||
|
||||
@transaction.atomic
|
||||
def update(self):
|
||||
self.update_allergens()
|
||||
self.update_expiry_date()
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Transformed food')
|
||||
verbose_name_plural = _('Transformed foods')
|
@ -1,19 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import django_tables2 as tables
|
||||
from django_tables2 import A
|
||||
|
||||
from .models import TransformedFood
|
||||
|
||||
|
||||
class TransformedFoodTable(tables.Table):
|
||||
name = tables.LinkColumn(
|
||||
'food:food_view',
|
||||
args=[A('pk'), ],
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = TransformedFood
|
||||
template_name = 'django_tables2/bootstrap4.html'
|
||||
fields = ('name', )
|
@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
HTML not finished <br>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body" id="form">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
HTML not finished <br>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body" id="form">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,27 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
HTML not finished <br>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<p>name : {{ food.name }}</p>
|
||||
<p>owner : {{ food.owner }}</p>
|
||||
<p>arrival_date : {{ food.arrival_date }}</p>
|
||||
<p>expiry_date : {{ food.expiry_date }}</p>
|
||||
<p>allergens :</p>
|
||||
<ul>
|
||||
{% for allergen in food.allergens.iterator %}
|
||||
<li>{{ allergen.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{% url "food:basic_update" pk=food.pk %}">Update</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
HTML not finished <br>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="row">
|
||||
<div class="col-xl-12">
|
||||
<div class="btn-group btn-block">
|
||||
<a href="{% url "food:basic_create" %}" class="btn btn-sm btn-outline-primary">Basic</a>
|
||||
<a href="{% url "food:transformed_create" %}" class="btn btn-sm btn-outline-primary">Transformed</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,24 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
HTML not finished <br>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body" id="form">
|
||||
<a class="btn btn-sm btn-success" href="{% url "food:qrcode_basic_create" slug=slug %}" data-turbolinks="false">
|
||||
New basic food
|
||||
</a>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,24 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
HTML not finished <br>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<p>qrcode : {{ qrcode.qr_code_number }}</p>
|
||||
<p>name : {{ qrcode.food_container.name }}</p>
|
||||
{% if qrcode.food_container.polymorphic_ctype.name == 'Basic food' %}
|
||||
<a href="{% url "food:basic_update" pk=qrcode.food_container.pk %}">Update</a>
|
||||
{% else %}
|
||||
<a href="{% url "food:transformed_update" pk=qrcode.food_container.pk %}">Update</a>
|
||||
{% endif %}
|
||||
<a href="{% url "food:add_ingredient" pk=qrcode.food_container.pk %}">Add the ingrdient</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
HTML not finished <br>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body" id="form">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,33 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-white mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
HTML not finished <br>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<p>name : {{ food.name }}</p>
|
||||
<p>owner : {{ food.owner }}</p>
|
||||
<p>creation_date : {{ food.creation_date }}</p>
|
||||
<p>expiry_date : {{ food.expiry_date }}</p>
|
||||
<p>allergens :</p>
|
||||
<ul>
|
||||
{% for allergen in food.allergens.iterator %}
|
||||
<li>{{ allergen.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>ingredients :</p>
|
||||
<ul>
|
||||
{% for ingredient in food.ingredient.iterator %}
|
||||
<li><a href="{% url "food:food_view" pk=ingredient.pk %}">{{ ingredient.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{% url "food:transformed_update" pk=food.pk %}">Update</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,24 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-footer">
|
||||
<a class="btn btn-sm btn-success" href="{% url 'food:transformed_create' %}" data-turbolinks="false">
|
||||
New transformed food
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="card-header text-center">
|
||||
In preparation
|
||||
</h3>
|
||||
{% render_table table %}
|
||||
<h3 class="card-header text-center">
|
||||
Open
|
||||
</h3>
|
||||
{% render_table open_table %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,3 +0,0 @@
|
||||
# from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -1,24 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'food'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.TransfomedListView.as_view(), name='food_list'),
|
||||
path('<int:slug>', views.QRCodeView.as_view(), name='qrcode_view'),
|
||||
path('detail/<int:pk>', views.FoodView.as_view(), name='food_view'),
|
||||
|
||||
path('<int:slug>/create_qrcode', views.QRCodeCreateView.as_view(), name='qrcode_create'),
|
||||
path('create', views.FoodCreateView.as_view(), name='food_create'),
|
||||
path('<int:slug>/create_qrcode/basic', views.QRCodeBasicFoodCreateView.as_view(), name='qrcode_basic_create'),
|
||||
path('create/transformed', views.TransformedFoodCreateView.as_view(), name='transformed_create'),
|
||||
|
||||
path('update/basic/<int:pk>', views.BasicFoodUpdateView.as_view(), name='basic_update'),
|
||||
path('update/transformed/<int:pk>', views.TransformedFoodUpdateView.as_view(), name='transformed_update'),
|
||||
|
||||
path('add/<int:pk>', views.AddIngredientView.as_view(), name='add_ingredient'),
|
||||
]
|
@ -1,278 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.db import transaction
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django_tables2.views import SingleTableView
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.views.generic import DetailView, UpdateView, TemplateView
|
||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||
|
||||
from .forms import AddIngredientForms, BasicFoodForms, QRCodeForms, TransformedFoodForms
|
||||
from .models import BasicFood, Food, QRCode, TransformedFood
|
||||
from .tables import TransformedFoodTable
|
||||
|
||||
|
||||
class AddIngredientView(ProtectQuerysetMixin, UpdateView):
|
||||
"""
|
||||
A view to add an ingredient
|
||||
"""
|
||||
model = Food
|
||||
template_name = 'food/add_ingredient_form.html'
|
||||
extra_context = {"title": _("Add the ingredient")}
|
||||
form_class = AddIngredientForms
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["pk"] = self.kwargs["pk"]
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.creater = self.request.user
|
||||
food = Food.objects.get(pk=self.kwargs['pk'])
|
||||
add_ingredient_form = AddIngredientForms(data=self.request.POST)
|
||||
if not food.is_ready:
|
||||
form.add_error(None, _("The product isn't ready"))
|
||||
return self.form_invalid(form)
|
||||
if not add_ingredient_form.is_valid():
|
||||
return self.form_invalid(form)
|
||||
|
||||
# Save the aliment and the allergens associed
|
||||
for transformed_pk in self.request.POST.getlist('ingredient'):
|
||||
transformed = TransformedFood.objects.get(pk=transformed_pk)
|
||||
if not transformed.is_ready:
|
||||
transformed.ingredient.add(food)
|
||||
transformed.update()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
return reverse('food:food_list')
|
||||
|
||||
|
||||
class BasicFoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
"""
|
||||
A view to add a basic food
|
||||
"""
|
||||
model = BasicFood
|
||||
form_class = BasicFoodForms
|
||||
template_name = 'food/basic_food_form.html'
|
||||
extra_context = {"title": _("Add a new aliment")}
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.creater = self.request.user
|
||||
basic_food_form = BasicFoodForms(data=self.request.POST)
|
||||
if not basic_food_form.is_valid():
|
||||
return self.form_invalid(form)
|
||||
|
||||
ans = super().form_valid(form)
|
||||
form.instance.update()
|
||||
return ans
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
self.object.refresh_from_db()
|
||||
return reverse('food:food_view', kwargs={"pk": self.object.pk})
|
||||
|
||||
|
||||
class FoodCreateView(ProtectQuerysetMixin, LoginRequiredMixin, TemplateView):
|
||||
"""
|
||||
A view to add a new aliment
|
||||
"""
|
||||
template_name = 'food/create_food_form.html'
|
||||
extra_context = {"title": _("Add a new aliment")}
|
||||
|
||||
|
||||
class FoodView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
A view to see a food
|
||||
"""
|
||||
model = Food
|
||||
extra_context = {"title": _("Details")}
|
||||
context_object_name = "food"
|
||||
|
||||
|
||||
class QRCodeBasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
#####################################################################
|
||||
# TO DO
|
||||
# - fix picture save
|
||||
# - implement solution crop and convert image (reuse or recode ImageForm from members apps)
|
||||
#####################################################################
|
||||
"""
|
||||
A view to add a basic food with a qrcode
|
||||
"""
|
||||
model = BasicFood
|
||||
form_class = BasicFoodForms
|
||||
template_name = 'food/basic_food_form.html'
|
||||
extra_context = {"title": _("Add a new basic food with QRCode")}
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.creater = self.request.user
|
||||
basic_food_form = BasicFoodForms(data=self.request.POST)
|
||||
if not basic_food_form.is_valid():
|
||||
return self.form_invalid(form)
|
||||
|
||||
# Save the aliment and the allergens associed
|
||||
basic_food = form.save(commit=False)
|
||||
# We assume the date of labeling and the same as the date of arrival
|
||||
basic_food.arrival_date = timezone.now()
|
||||
basic_food.is_ready = True
|
||||
basic_food._force_save = True
|
||||
basic_food.save()
|
||||
basic_food.refresh_from_db()
|
||||
|
||||
qrcode = QRCode()
|
||||
qrcode.qr_code_number = self.kwargs['slug']
|
||||
qrcode.food_container = basic_food
|
||||
qrcode.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
self.object.refresh_from_db()
|
||||
return reverse('food:qrcode_view', kwargs={"slug": self.kwargs['slug']})
|
||||
|
||||
def get_sample_object(self):
|
||||
return BasicFood(
|
||||
name="",
|
||||
expiry_date=timezone.now(),
|
||||
)
|
||||
|
||||
|
||||
class QRCodeCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
"""
|
||||
A view to add a new qrcode
|
||||
"""
|
||||
model = QRCode
|
||||
template_name = 'food/create_qrcode_form.html'
|
||||
form_class = QRCodeForms
|
||||
extra_context = {"title": _("Add a new QRCode")}
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
qrcode = kwargs["slug"]
|
||||
if self.model.objects.filter(qr_code_number=qrcode).count() > 0:
|
||||
return HttpResponseRedirect(reverse("food:qrcode_view", kwargs=kwargs))
|
||||
else:
|
||||
return super().get(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["slug"] = self.kwargs["slug"]
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.creater = self.request.user
|
||||
qrcode_food_form = QRCodeForms(data=self.request.POST)
|
||||
if not qrcode_food_form.is_valid():
|
||||
return self.form_invalid(form)
|
||||
|
||||
# Save the qrcode
|
||||
qrcode = form.save(commit=False)
|
||||
qrcode.qr_code_number = self.kwargs["slug"]
|
||||
qrcode._force_save = True
|
||||
qrcode.save()
|
||||
qrcode.refresh_from_db()
|
||||
|
||||
qrcode.food_container.is_ready = True
|
||||
qrcode.food_container.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
self.object.refresh_from_db()
|
||||
return reverse('food:qrcode_view', kwargs={"slug": self.kwargs['slug']})
|
||||
|
||||
def get_sample_object(self):
|
||||
return QRCode(
|
||||
qr_code_number=self.kwargs["slug"],
|
||||
)
|
||||
|
||||
|
||||
class QRCodeView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
A view to see a qrcode
|
||||
"""
|
||||
model = QRCode
|
||||
extra_context = {"title": _("QRCode")}
|
||||
context_object_name = "qrcode"
|
||||
slug_field = "qr_code_number"
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
qrcode = kwargs["slug"]
|
||||
if self.model.objects.filter(qr_code_number=qrcode).count() > 0:
|
||||
return super().get(*args, **kwargs)
|
||||
else:
|
||||
return HttpResponseRedirect(reverse("food:qrcode_create", kwargs=kwargs))
|
||||
|
||||
|
||||
class TransformedFoodFormView(ProtectQuerysetMixin):
|
||||
"""
|
||||
A view to add a tranformed food
|
||||
"""
|
||||
model = TransformedFood
|
||||
template_name = 'food/transformed_food_form.html'
|
||||
form_class = TransformedFoodForms
|
||||
extra_context = {"title": _("Add a new meal")}
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.creater = self.request.user
|
||||
transformed_food_form = TransformedFoodForms(data=self.request.POST)
|
||||
if not transformed_food_form.is_valid():
|
||||
return self.form_invalid(form)
|
||||
|
||||
# Save the aliment and allergens associated
|
||||
transformed_food = form.save(commit=False)
|
||||
transformed_food.expiry_date = transformed_food.creation_date
|
||||
transformed_food._force_save = True
|
||||
transformed_food.save()
|
||||
transformed_food.refresh_from_db()
|
||||
ans = super().form_valid(form)
|
||||
transformed_food.update()
|
||||
return ans
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
self.object.refresh_from_db()
|
||||
return reverse('food:food_view', kwargs={"pk": self.object.pk})
|
||||
|
||||
|
||||
class TransformedFoodUpdateView(TransformedFoodFormView, LoginRequiredMixin, UpdateView):
|
||||
pass
|
||||
|
||||
|
||||
class TransformedFoodCreateView(TransformedFoodFormView, ProtectedCreateView):
|
||||
def get_sample_object(self):
|
||||
return TransformedFood(
|
||||
name="",
|
||||
creation_date=timezone.now(),
|
||||
)
|
||||
|
||||
|
||||
class TransfomedListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
"""
|
||||
Displays not ready TransformedFood
|
||||
"""
|
||||
model = TransformedFood
|
||||
table_class = TransformedFoodTable
|
||||
ordering = ('name',)
|
||||
extra_context = {"title": _("Transformed food")}
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
return super().get_queryset(**kwargs)\
|
||||
.filter(is_ready=False)\
|
||||
.distinct()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['open_table'] = TransformedFoodTable(
|
||||
TransformedFood.objects.filter(
|
||||
was_eaten=False,
|
||||
expiry_date__lt=timezone.now()
|
||||
),
|
||||
prefix="open-")
|
||||
return context
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'logs.apps.LogsConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import ChangelogViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
@ -76,6 +76,9 @@ class Changelog(models.Model):
|
||||
verbose_name=_('timestamp'),
|
||||
)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
raise ValidationError(_("Logs cannot be destroyed."))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("changelog")
|
||||
verbose_name_plural = _("changelogs")
|
||||
@ -83,6 +86,3 @@ class Changelog(models.Model):
|
||||
def __str__(self):
|
||||
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
|
||||
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
raise ValidationError(_("Logs cannot be destroyed."))
|
||||
|
@ -1,11 +1,11 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from note.models import NoteUser, Alias
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_ip
|
||||
|
||||
from .models import Changelog
|
||||
|
||||
@ -57,9 +57,9 @@ def save_object(sender, instance, **kwargs):
|
||||
previous = instance._previous
|
||||
|
||||
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||
request = get_current_request()
|
||||
user, ip = get_current_authenticated_user(), get_current_ip()
|
||||
|
||||
if request is None:
|
||||
if user is None:
|
||||
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
|
||||
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
|
||||
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info
|
||||
@ -71,23 +71,9 @@ def save_object(sender, instance, **kwargs):
|
||||
# else:
|
||||
if note.exists():
|
||||
user = note.get().user
|
||||
else:
|
||||
user = None
|
||||
else:
|
||||
user = request.user
|
||||
if 'HTTP_X_REAL_IP' in request.META:
|
||||
ip = request.META.get('HTTP_X_REAL_IP')
|
||||
elif 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||
ip = request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0]
|
||||
else:
|
||||
ip = request.META.get('REMOTE_ADDR')
|
||||
|
||||
if not user.is_authenticated:
|
||||
# For registration and OAuth2 purposes
|
||||
user = None
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
if request is not None and instance._meta.label_lower == "auth.user" and previous:
|
||||
if user is not None and instance._meta.label_lower == "auth.user" and previous:
|
||||
# On n'enregistre pas les connexions
|
||||
if instance.last_login != previous.last_login:
|
||||
return
|
||||
@ -135,9 +121,9 @@ def delete_object(sender, instance, **kwargs):
|
||||
return
|
||||
|
||||
# Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP
|
||||
request = get_current_request()
|
||||
user, ip = get_current_authenticated_user(), get_current_ip()
|
||||
|
||||
if request is None:
|
||||
if user is None:
|
||||
# Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py`
|
||||
# On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée
|
||||
# IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info
|
||||
@ -149,20 +135,6 @@ def delete_object(sender, instance, **kwargs):
|
||||
# else:
|
||||
if note.exists():
|
||||
user = note.get().user
|
||||
else:
|
||||
user = None
|
||||
else:
|
||||
user = request.user
|
||||
if 'HTTP_X_REAL_IP' in request.META:
|
||||
ip = request.META.get('HTTP_X_REAL_IP')
|
||||
elif 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||
ip = request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0]
|
||||
else:
|
||||
ip = request.META.get('REMOTE_ADDR')
|
||||
|
||||
if not user.is_authenticated:
|
||||
# For registration and OAuth2 purposes
|
||||
user = None
|
||||
|
||||
# On crée notre propre sérialiseur JSON pour pouvoir sauvegarder les modèles
|
||||
class CustomSerializer(ModelSerializer):
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'member.apps.MemberConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import ProfileViewSet, ClubViewSet, MembershipViewSet
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,17 +0,0 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from cas_server.auth import DjangoAuthUser # pragma: no cover
|
||||
from note.models import Alias
|
||||
|
||||
|
||||
class CustomAuthUser(DjangoAuthUser): # pragma: no cover
|
||||
"""
|
||||
Override Django Auth User model to define a custom Matrix username.
|
||||
"""
|
||||
|
||||
def attributs(self):
|
||||
d = super().attributs()
|
||||
if self.user:
|
||||
d["normalized_name"] = Alias.normalize(self.user.username)
|
||||
return d
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import io
|
||||
@ -47,13 +47,6 @@ class ProfileForm(forms.ModelForm):
|
||||
|
||||
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date"))
|
||||
|
||||
VSS_charter_read = forms.BooleanField(
|
||||
required=True,
|
||||
label=_("Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) charter read and approved"),
|
||||
help_text=_("Tick after having read and accepted the anti-VSS charter \
|
||||
<a href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> available here in pdf</a>")
|
||||
)
|
||||
|
||||
def clean_promotion(self):
|
||||
promotion = self.cleaned_data["promotion"]
|
||||
if promotion > timezone.now().year:
|
||||
@ -121,7 +114,7 @@ class ImageForm(forms.Form):
|
||||
frame = frame.crop((x, y, x + w, y + h))
|
||||
frame = frame.resize(
|
||||
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
|
||||
Image.LANCZOS,
|
||||
Image.ANTIALIAS,
|
||||
)
|
||||
frames.append(frame)
|
||||
|
||||
@ -138,9 +131,6 @@ class ImageForm(forms.Form):
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def is_valid(self):
|
||||
return super().is_valid() or super().clean().get('image') is None
|
||||
|
||||
|
||||
class ClubForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
@ -154,7 +144,7 @@ class ClubForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Club
|
||||
exclude = ("add_registration_form",)
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
"membership_fee_paid": AmountInput(),
|
||||
"membership_fee_unpaid": AmountInput(),
|
||||
|
@ -1,14 +1,12 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import hashlib
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import PBKDF2PasswordHasher, mask_hash
|
||||
from django.contrib.auth.hashers import PBKDF2PasswordHasher
|
||||
from django.utils.crypto import constant_time_compare
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
||||
|
||||
|
||||
class CustomNK15Hasher(PBKDF2PasswordHasher):
|
||||
@ -26,22 +24,16 @@ class CustomNK15Hasher(PBKDF2PasswordHasher):
|
||||
|
||||
def must_update(self, encoded):
|
||||
if settings.DEBUG:
|
||||
# Small hack to let superusers to impersonate people.
|
||||
# Don't change their password.
|
||||
request = get_current_request()
|
||||
current_user = request.user
|
||||
current_user = get_current_authenticated_user()
|
||||
if current_user is not None and current_user.is_superuser:
|
||||
return False
|
||||
return True
|
||||
|
||||
def verify(self, password, encoded):
|
||||
if settings.DEBUG:
|
||||
# Small hack to let superusers to impersonate people.
|
||||
# If a superuser is already connected, let him/her log in as another person.
|
||||
request = get_current_request()
|
||||
current_user = request.user
|
||||
current_user = get_current_authenticated_user()
|
||||
if current_user is not None and current_user.is_superuser\
|
||||
and request.session.get("permission_mask", -1) >= 42:
|
||||
and get_current_session().get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
|
||||
if '|' in encoded:
|
||||
@ -49,18 +41,6 @@ class CustomNK15Hasher(PBKDF2PasswordHasher):
|
||||
return constant_time_compare(hashlib.sha256((salt + password).encode("utf-8")).hexdigest(), db_hashed_pass)
|
||||
return super().verify(password, encoded)
|
||||
|
||||
def safe_summary(self, encoded):
|
||||
# Displayed information in Django Admin.
|
||||
if '|' in encoded:
|
||||
salt, db_hashed_pass = encoded.split('$')[2].split('|')
|
||||
return OrderedDict([
|
||||
(_('algorithm'), 'custom_nk15'),
|
||||
(_('iterations'), '1'),
|
||||
(_('salt'), mask_hash(salt)),
|
||||
(_('hash'), mask_hash(db_hashed_pass)),
|
||||
])
|
||||
return super().safe_summary(encoded)
|
||||
|
||||
|
||||
class DebugSuperuserBackdoor(PBKDF2PasswordHasher):
|
||||
"""
|
||||
@ -71,11 +51,8 @@ class DebugSuperuserBackdoor(PBKDF2PasswordHasher):
|
||||
|
||||
def verify(self, password, encoded):
|
||||
if settings.DEBUG:
|
||||
# Small hack to let superusers to impersonate people.
|
||||
# If a superuser is already connected, let him/her log in as another person.
|
||||
request = get_current_request()
|
||||
current_user = request.user
|
||||
current_user = get_current_authenticated_user()
|
||||
if current_user is not None and current_user.is_superuser\
|
||||
and request.session.get("permission_mask", -1) >= 42:
|
||||
and get_current_session().get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
return super().verify(password, encoded)
|
||||
|
@ -19,8 +19,8 @@ def create_bde_and_kfet(apps, schema_editor):
|
||||
membership_fee_paid=500,
|
||||
membership_fee_unpaid=500,
|
||||
membership_duration=396,
|
||||
membership_start="2021-08-01",
|
||||
membership_end="2022-09-30",
|
||||
membership_start="2020-08-01",
|
||||
membership_end="2021-09-30",
|
||||
)
|
||||
Club.objects.get_or_create(
|
||||
id=2,
|
||||
@ -31,8 +31,8 @@ def create_bde_and_kfet(apps, schema_editor):
|
||||
membership_fee_paid=3500,
|
||||
membership_fee_unpaid=3500,
|
||||
membership_duration=396,
|
||||
membership_start="2021-08-01",
|
||||
membership_end="2022-09-30",
|
||||
membership_start="2020-08-01",
|
||||
membership_end="2021-09-30",
|
||||
)
|
||||
|
||||
NoteClub.objects.get_or_create(
|
||||
|
@ -1,23 +0,0 @@
|
||||
# Generated by Django 2.2.19 on 2021-03-13 11:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0006_create_note_account_bde_membership'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='membership',
|
||||
name='roles',
|
||||
field=models.ManyToManyField(related_name='memberships', to='permission.Role', verbose_name='roles'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='promotion',
|
||||
field=models.PositiveSmallIntegerField(default=2021, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.24 on 2021-10-05 13:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0007_auto_20210313_1235'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='department',
|
||||
field=models.CharField(choices=[('A0', 'Informatics (A0)'), ('A1', 'Mathematics (A1)'), ('A2', 'Physics (A2)'), ("A'2", "Applied physics (A'2)"), ("A''2", "Chemistry (A''2)"), ('A3', 'Biology (A3)'), ('B1234', 'SAPHIRE (B1234)'), ('B1', 'Mechanics (B1)'), ('B2', 'Civil engineering (B2)'), ('B3', 'Mechanical engineering (B3)'), ('B4', 'EEA (B4)'), ('C', 'Design (C)'), ('D2', 'Economy-management (D2)'), ('D3', 'Social sciences (D3)'), ('E', 'English (E)'), ('EXT', 'External (EXT)')], max_length=8, verbose_name='department'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.26 on 2022-09-04 21:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0008_auto_20211005_1544'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='promotion',
|
||||
field=models.PositiveSmallIntegerField(default=2022, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.28 on 2023-08-23 21:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0009_auto_20220904_2325'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='promotion',
|
||||
field=models.PositiveSmallIntegerField(default=2023, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.28 on 2023-08-31 09:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0010_new_default_year'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='VSS_charter_read',
|
||||
field=models.BooleanField(default=False, verbose_name='VSS charter read'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.28 on 2024-07-15 09:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0011_profile_vss_charter_read'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='club',
|
||||
name='add_registration_form',
|
||||
field=models.BooleanField(default=False, verbose_name='add to registration form'),
|
||||
),
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import datetime
|
||||
@ -28,6 +28,7 @@ class Profile(models.Model):
|
||||
We do not want to patch the Django Contrib :model:`auth.User`model;
|
||||
so this model add an user profile with additional information.
|
||||
"""
|
||||
|
||||
user = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
@ -56,7 +57,7 @@ class Profile(models.Model):
|
||||
('A1', _("Mathematics (A1)")),
|
||||
('A2', _("Physics (A2)")),
|
||||
("A'2", _("Applied physics (A'2)")),
|
||||
("A''2", _("Chemistry (A''2)")),
|
||||
('A''2', _("Chemistry (A''2)")),
|
||||
('A3', _("Biology (A3)")),
|
||||
('B1234', _("SAPHIRE (B1234)")),
|
||||
('B1', _("Mechanics (B1)")),
|
||||
@ -73,7 +74,7 @@ class Profile(models.Model):
|
||||
|
||||
promotion = models.PositiveSmallIntegerField(
|
||||
null=True,
|
||||
default=datetime.date.today().year if datetime.date.today().month >= 8 else datetime.date.today().year - 1,
|
||||
default=datetime.date.today().year,
|
||||
verbose_name=_("promotion"),
|
||||
help_text=_("Year of entry to the school (None if not ENS student)"),
|
||||
)
|
||||
@ -133,22 +134,6 @@ class Profile(models.Model):
|
||||
default=False,
|
||||
)
|
||||
|
||||
VSS_charter_read = models.BooleanField(
|
||||
verbose_name=_("VSS charter read"),
|
||||
default=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('user profile')
|
||||
verbose_name_plural = _('user profile')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('member:user_detail', args=(self.user_id,))
|
||||
|
||||
@property
|
||||
def ens_year(self):
|
||||
"""
|
||||
@ -173,6 +158,17 @@ class Profile(models.Model):
|
||||
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('user profile')
|
||||
verbose_name_plural = _('user profile')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('member:user_detail', args=(self.user_id,))
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
def send_email_validation_link(self):
|
||||
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
|
||||
token = email_validation_token.make_token(self.user)
|
||||
@ -204,11 +200,9 @@ class Club(models.Model):
|
||||
max_length=255,
|
||||
unique=True,
|
||||
)
|
||||
|
||||
email = models.EmailField(
|
||||
verbose_name=_('email'),
|
||||
)
|
||||
|
||||
parent_club = models.ForeignKey(
|
||||
'self',
|
||||
null=True,
|
||||
@ -259,17 +253,23 @@ class Club(models.Model):
|
||||
help_text=_('Maximal date of a membership, after which members must renew it.'),
|
||||
)
|
||||
|
||||
add_registration_form = models.BooleanField(
|
||||
verbose_name=_("add to registration form"),
|
||||
default=False,
|
||||
)
|
||||
def update_membership_dates(self):
|
||||
"""
|
||||
This function is called each time the club detail view is displayed.
|
||||
Update the year of the membership dates.
|
||||
"""
|
||||
if not self.membership_start:
|
||||
return
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("club")
|
||||
verbose_name_plural = _("clubs")
|
||||
today = datetime.date.today()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
if (today - self.membership_start).days >= 365:
|
||||
self.membership_start = datetime.date(self.membership_start.year + 1,
|
||||
self.membership_start.month, self.membership_start.day)
|
||||
self.membership_end = datetime.date(self.membership_end.year + 1,
|
||||
self.membership_end.month, self.membership_end.day)
|
||||
self._force_save = True
|
||||
self.save(force_update=True)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
@ -282,29 +282,16 @@ class Club(models.Model):
|
||||
self.membership_end = None
|
||||
super().save(force_insert, force_update, update_fields)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("club")
|
||||
verbose_name_plural = _("clubs")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('member:club_detail', args=(self.pk,))
|
||||
|
||||
def update_membership_dates(self):
|
||||
"""
|
||||
This function is called each time the club detail view is displayed.
|
||||
Update the year of the membership dates.
|
||||
"""
|
||||
if not self.membership_start or not self.membership_end:
|
||||
return
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
while (today - self.membership_start).days >= 365:
|
||||
if self.membership_start:
|
||||
self.membership_start = datetime.date(self.membership_start.year + 1,
|
||||
self.membership_start.month, self.membership_start.day)
|
||||
if self.membership_end:
|
||||
self.membership_end = datetime.date(self.membership_end.year + 1,
|
||||
self.membership_end.month, self.membership_end.day)
|
||||
self._force_save = True
|
||||
self.save(force_update=True)
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
"""
|
||||
@ -344,66 +331,6 @@ class Membership(models.Model):
|
||||
verbose_name=_('fee'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('membership')
|
||||
verbose_name_plural = _('memberships')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
||||
def __str__(self):
|
||||
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||
"""
|
||||
# Ensure that club membership dates are valid
|
||||
old_membership_start = self.club.membership_start
|
||||
self.club.update_membership_dates()
|
||||
if self.club.membership_start != old_membership_start:
|
||||
self.club.save()
|
||||
|
||||
created = not self.pk
|
||||
if not created:
|
||||
for role in self.roles.all():
|
||||
club = role.for_club
|
||||
if club is not None:
|
||||
if club.pk != self.club_id:
|
||||
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
||||
.format(role=role.name, club=club.name))
|
||||
else:
|
||||
if Membership.objects.filter(
|
||||
user=self.user,
|
||||
club=self.club,
|
||||
date_start__lte=self.date_start,
|
||||
date_end__gte=self.date_start,
|
||||
).exists():
|
||||
raise ValidationError(_('User is already a member of the club'))
|
||||
|
||||
if self.club.parent_club is not None:
|
||||
# Check that the user is already a member of the parent club if the membership is created
|
||||
if not Membership.objects.filter(
|
||||
user=self.user,
|
||||
club=self.club.parent_club,
|
||||
date_start__gte=self.club.parent_club.membership_start,
|
||||
).exists():
|
||||
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
|
||||
self.renew_parent()
|
||||
else:
|
||||
raise ValidationError(_('User is not a member of the parent club')
|
||||
+ ' ' + self.club.parent_club.name)
|
||||
|
||||
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
|
||||
|
||||
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
|
||||
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
|
||||
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
|
||||
self.date_end = self.club.membership_end
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
self.make_transaction()
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"""
|
||||
@ -481,6 +408,52 @@ class Membership(models.Model):
|
||||
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
||||
parent_membership.save()
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||
"""
|
||||
created = not self.pk
|
||||
if not created:
|
||||
for role in self.roles.all():
|
||||
club = role.for_club
|
||||
if club is not None:
|
||||
if club.pk != self.club_id:
|
||||
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
||||
.format(role=role.name, club=club.name))
|
||||
else:
|
||||
if Membership.objects.filter(
|
||||
user=self.user,
|
||||
club=self.club,
|
||||
date_start__lte=self.date_start,
|
||||
date_end__gte=self.date_start,
|
||||
).exists():
|
||||
raise ValidationError(_('User is already a member of the club'))
|
||||
|
||||
if self.club.parent_club is not None:
|
||||
# Check that the user is already a member of the parent club if the membership is created
|
||||
if not Membership.objects.filter(
|
||||
user=self.user,
|
||||
club=self.club.parent_club,
|
||||
date_start__gte=self.club.parent_club.membership_start,
|
||||
).exists():
|
||||
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
|
||||
self.renew_parent()
|
||||
else:
|
||||
raise ValidationError(_('User is not a member of the parent club')
|
||||
+ ' ' + self.club.parent_club.name)
|
||||
|
||||
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
|
||||
|
||||
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
|
||||
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
|
||||
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
|
||||
self.date_end = self.club.membership_end
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
self.make_transaction()
|
||||
|
||||
def make_transaction(self):
|
||||
"""
|
||||
Create Membership transaction associated to this membership.
|
||||
@ -518,3 +491,11 @@ class Membership(models.Model):
|
||||
soge_credit.save()
|
||||
else:
|
||||
transaction.save(force_insert=True)
|
||||
|
||||
def __str__(self):
|
||||
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('membership')
|
||||
verbose_name_plural = _('memberships')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
|
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* On form submit, create a new friendship
|
||||
*/
|
||||
function form_create_trust (e) {
|
||||
// Do not submit HTML form
|
||||
e.preventDefault()
|
||||
|
||||
// Get data and send to API
|
||||
const formData = new FormData(e.target)
|
||||
$.getJSON('/api/note/alias/'+formData.get('trusted') + '/',
|
||||
function (trusted_alias) {
|
||||
if ((trusted_alias.note == formData.get('trusting')))
|
||||
{
|
||||
addMsg(gettext("You can't add yourself as a friend"), "danger")
|
||||
return
|
||||
}
|
||||
create_trust(formData.get('trusting'), trusted_alias.note)
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a trust between users
|
||||
* @param trusting:Integer trusting note id
|
||||
* @param trusted:Integer trusted note id
|
||||
*/
|
||||
function create_trust(trusting, trusted) {
|
||||
$.post('/api/note/trust/', {
|
||||
trusting: trusting,
|
||||
trusted: trusted,
|
||||
csrfmiddlewaretoken: CSRF_TOKEN
|
||||
}).done(function () {
|
||||
// Reload tables
|
||||
$('#trust_table').load(location.pathname + ' #trust_table')
|
||||
$('#trusted_table').load(location.pathname + ' #trusted_table')
|
||||
addMsg(gettext('Friendship successfully added'), 'success')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On click of "delete", delete the trust
|
||||
* @param button_id:Integer Trust id to remove
|
||||
*/
|
||||
function delete_button (button_id) {
|
||||
$.ajax({
|
||||
url: '/api/note/trust/' + button_id + '/',
|
||||
method: 'DELETE',
|
||||
headers: { 'X-CSRFTOKEN': CSRF_TOKEN }
|
||||
}).done(function () {
|
||||
addMsg(gettext('Friendship successfully deleted'), 'success')
|
||||
$('#trust_table').load(location.pathname + ' #trust_table')
|
||||
$('#trusted_table').load(location.pathname + ' #trusted_table')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// Attach event
|
||||
document.getElementById('form_trust').addEventListener('submit', form_create_trust)
|
||||
})
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import date
|
||||
@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.html import format_html
|
||||
from note.templatetags.pretty_money import pretty_money
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from note_kfet.middlewares import get_current_authenticated_user
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .models import Club, Membership
|
||||
@ -31,8 +31,7 @@ class ClubTable(tables.Table):
|
||||
row_attrs = {
|
||||
'class': 'table-row',
|
||||
'id': lambda record: "row-" + str(record.pk),
|
||||
'data-href': lambda record: record.pk,
|
||||
'style': 'cursor:pointer',
|
||||
'data-href': lambda record: record.pk
|
||||
}
|
||||
|
||||
|
||||
@ -52,19 +51,19 @@ class UserTable(tables.Table):
|
||||
def render_email(self, record, value):
|
||||
# Replace the email by a dash if the user can't see the profile detail
|
||||
# Replace also the URL
|
||||
if not PermissionBackend.check_perm(get_current_request(), "member.view_profile", record.profile):
|
||||
if not PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile):
|
||||
value = "—"
|
||||
record.email = value
|
||||
return value
|
||||
|
||||
def render_section(self, record, value):
|
||||
return value \
|
||||
if PermissionBackend.check_perm(get_current_request(), "member.view_profile", record.profile) \
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile) \
|
||||
else "—"
|
||||
|
||||
def render_balance(self, record, value):
|
||||
return pretty_money(value)\
|
||||
if PermissionBackend.check_perm(get_current_request(), "note.view_note", record.note) else "—"
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", record.note) else "—"
|
||||
|
||||
class Meta:
|
||||
attrs = {
|
||||
@ -75,8 +74,7 @@ class UserTable(tables.Table):
|
||||
model = User
|
||||
row_attrs = {
|
||||
'class': 'table-row',
|
||||
'data-href': lambda record: record.pk,
|
||||
'style': 'cursor:pointer',
|
||||
'data-href': lambda record: record.pk
|
||||
}
|
||||
|
||||
|
||||
@ -95,7 +93,7 @@ class MembershipTable(tables.Table):
|
||||
def render_user(self, value):
|
||||
# If the user has the right, link the displayed user with the page of its detail.
|
||||
s = value.username
|
||||
if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value):
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value):
|
||||
s = format_html("<a href={url}>{name}</a>",
|
||||
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
||||
|
||||
@ -104,7 +102,7 @@ class MembershipTable(tables.Table):
|
||||
def render_club(self, value):
|
||||
# If the user has the right, link the displayed club with the page of its detail.
|
||||
s = value.name
|
||||
if PermissionBackend.check_perm(get_current_request(), "member.view_club", value):
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_club", value):
|
||||
s = format_html("<a href={url}>{name}</a>",
|
||||
url=reverse_lazy('member:club_detail', kwargs={"pk": value.pk}), name=s)
|
||||
|
||||
@ -120,7 +118,7 @@ class MembershipTable(tables.Table):
|
||||
club=record.club,
|
||||
user=record.user,
|
||||
date_start__gte=record.club.membership_start,
|
||||
date_end__lte=record.club.membership_end or date(9999, 12, 31),
|
||||
date_end__lte=record.club.membership_end,
|
||||
).exists(): # If the renew is not yet performed
|
||||
empty_membership = Membership(
|
||||
club=record.club,
|
||||
@ -129,7 +127,7 @@ class MembershipTable(tables.Table):
|
||||
date_end=date.today(),
|
||||
fee=0,
|
||||
)
|
||||
if PermissionBackend.check_perm(get_current_request(),
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(),
|
||||
"member.add_membership", empty_membership): # If the user has right
|
||||
renew_url = reverse_lazy('member:club_renew_membership',
|
||||
kwargs={"pk": record.pk})
|
||||
@ -144,7 +142,7 @@ class MembershipTable(tables.Table):
|
||||
# If the user has the right to manage the roles, display the link to manage them
|
||||
roles = record.roles.all()
|
||||
s = ", ".join(str(role) for role in roles)
|
||||
if PermissionBackend.check_perm(get_current_request(), "member.change_membership_roles", record):
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.change_membership_roles", record):
|
||||
s = format_html("<a href='" + str(reverse_lazy("member:club_manage_roles", kwargs={"pk": record.pk}))
|
||||
+ "'>" + s + "</a>")
|
||||
return s
|
||||
@ -167,7 +165,7 @@ class ClubManagerTable(tables.Table):
|
||||
def render_user(self, value):
|
||||
# If the user has the right, link the displayed user with the page of its detail.
|
||||
s = value.username
|
||||
if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value):
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value):
|
||||
s = format_html("<a href={url}>{name}</a>",
|
||||
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
||||
|
||||
|
@ -25,14 +25,6 @@
|
||||
</a>
|
||||
</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'friendships'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:user_trust' user_object.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans 'Manage friendships' %} ({{ user_object.note.trusting.all|length }})
|
||||
</a>
|
||||
</dd>
|
||||
|
||||
{% if "member.view_profile"|has_perm:user_object.profile %}
|
||||
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.section }}</dd>
|
||||
@ -47,13 +39,13 @@
|
||||
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if user_object.note and "note.view_note"|has_perm:user_object.note %}
|
||||
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
|
@ -5,26 +5,17 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h3>{% trans "Token authentication" %}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<div class="alert alert-info">
|
||||
<h4>À quoi sert un jeton d'authentification ?</h4>
|
||||
|
||||
Un jeton vous permet de vous connecter à <a href="/api/">l'API de la Note Kfet</a> via votre propre compte
|
||||
depuis un client externe.<br />
|
||||
Un jeton vous permet de vous connecter à <a href="/api/">l'API de la Note Kfet</a>.<br />
|
||||
Il suffit pour cela d'ajouter en en-tête de vos requêtes <code>Authorization: Token <TOKEN></code>
|
||||
pour pouvoir vous identifier.<br /><br />
|
||||
|
||||
La documentation de l'API est disponible ici :
|
||||
<a href="/doc/api/">{{ request.scheme }}://{{ request.get_host }}/doc/api/</a>.
|
||||
</div>
|
||||
Une documentation de l'API arrivera ultérieurement.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<div class="alert alert-info">
|
||||
<strong>{%trans 'Token' %} :</strong>
|
||||
{% if 'show' in request.GET %}
|
||||
{{ token.key }} (<a href="?">cacher</a>)
|
||||
@ -33,70 +24,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endif %}
|
||||
<br />
|
||||
<strong>{%trans 'Created' %} :</strong> {{ token.created }}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>{% trans "Warning" %} :</strong> regénérer le jeton va révoquer tout accès autorisé à l'API via ce jeton !
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<a href="?regenerate">
|
||||
<button class="btn btn-primary">{% trans 'Regenerate token' %}</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h3>{% trans "OAuth2 authentication" %}</h3>
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
La Note Kfet implémente également le protocole <a href="https://oauth.net/2/">OAuth2</a>, afin de
|
||||
permettre à des applications tierces d'interagir avec la Note en récoltant des informations
|
||||
(de connexion par exemple) voir en permettant des modifications à distance, par exemple lorsqu'il
|
||||
s'agit d'avoir un site marchand sur lequel faire des transactions via la Note Kfet.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
L'usage de ce protocole est recommandé pour tout usage non personnel, car permet de mieux cibler
|
||||
les droits dont on a besoin, en restreignant leur usage par jeton généré.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
La documentation vis-à-vis de l'usage de ce protocole est disponible ici :
|
||||
<a href="/doc/external_services/oauth2/">{{ request.scheme }}://{{ request.get_host }}/doc/external_services/oauth2/</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Liste des URL à communiquer à votre application :
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
{% trans "Authorization:" %}
|
||||
<a href="{% url 'oauth2_provider:authorize' %}">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:authorize' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% trans "Token:" %}
|
||||
<a href="{% url 'oauth2_provider:authorize' %}">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:token' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% trans "Revoke Token:" %}
|
||||
<a href="{% url 'oauth2_provider:authorize' %}">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:revoke-token' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% trans "Introspect Token:" %}
|
||||
<a href="{% url 'oauth2_provider:authorize' %}">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:introspect' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<a class="btn btn-primary" href="{% url 'oauth2_provider:list' %}">{% trans "Show my applications" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Attention :</strong> regénérer le jeton va révoquer tout accès autorisé à l'API via ce jeton !
|
||||
</div>
|
||||
|
||||
<a href="?regenerate">
|
||||
<button class="btn btn-primary">{% trans 'Regenerate token' %}</button>
|
||||
</a>
|
||||
{% endblock %}
|
@ -14,9 +14,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<form method="post" enctype="multipart/form-data" id="formUpload">
|
||||
{% csrf_token %}
|
||||
{{ form |crispy }}
|
||||
{% if user.note.display_image != "pic/default.png" %}
|
||||
<input type="submit" class="btn btn-primary" value="{% trans "Remove" %}">
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
<!-- MODAL TO CROP THE IMAGE -->
|
||||
|
@ -1,48 +0,0 @@
|
||||
{% extends "member/base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load static django_tables2 i18n %}
|
||||
|
||||
{% block profile_content %}
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{% trans "Add friends" %}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
{% if can_create %}
|
||||
<form class="input-group" method="POST" id="form_trust">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="trusting" value="{{ object.note.pk }}">
|
||||
{%include "autocomplete_model.html" %}
|
||||
<div class="input-group-append">
|
||||
<input type="submit" class="btn btn-success" value="{% trans "Add" %}">
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% render_table trusting %}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning card mb-3">
|
||||
{% blocktrans trimmed %}
|
||||
Adding someone as a friend enables them to initiate transactions coming
|
||||
from your account (while keeping your balance positive). This is
|
||||
designed to simplify using note kfet transfers to transfer money between
|
||||
users. The intent is that one person can make all transfers for a group of
|
||||
friends without needing additional rights among them.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{% trans "People having you as a friend" %}
|
||||
</h3>
|
||||
{% render_table trusted_by %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script src="{% static "member/js/trust.js" %}"></script>
|
||||
<script src="{% static "js/autocomplete_model.js" %}"></script>
|
||||
{% endblock%}
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import date
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import hashlib
|
||||
@ -183,7 +183,7 @@ class TestMemberships(TestCase):
|
||||
club = Club.objects.get(name="Kfet")
|
||||
else:
|
||||
club = Club.objects.create(
|
||||
name="Second club without BDE",
|
||||
name="Second club " + ("with BDE" if bde_parent else "without BDE"),
|
||||
parent_club=None,
|
||||
email="newclub@example.com",
|
||||
require_memberships=True,
|
||||
@ -335,7 +335,6 @@ class TestMemberships(TestCase):
|
||||
ml_sports_registration=True,
|
||||
ml_art_registration=True,
|
||||
report_frequency=7,
|
||||
VSS_charter_read=True
|
||||
))
|
||||
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
|
||||
self.assertTrue(User.objects.filter(username="toto changed").exists())
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
@ -23,6 +23,5 @@ urlpatterns = [
|
||||
path('user/<int:pk>/update/', views.UserUpdateView.as_view(), name="user_update_profile"),
|
||||
path('user/<int:pk>/update_pic/', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"),
|
||||
path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"),
|
||||
path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"),
|
||||
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from datetime import timedelta, date
|
||||
@ -18,15 +18,15 @@ from django.views.generic import DetailView, UpdateView, TemplateView
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django_tables2.views import SingleTableView
|
||||
from rest_framework.authtoken.models import Token
|
||||
from note.models import Alias, NoteClub, NoteUser, Trust
|
||||
from note.models import Alias, NoteUser
|
||||
from note.models.transactions import Transaction, SpecialTransaction
|
||||
from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable
|
||||
from note_kfet.middlewares import _set_current_request
|
||||
from note.tables import HistoryTable, AliasTable
|
||||
from note_kfet.middlewares import _set_current_user_and_ip
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.models import Role
|
||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||
|
||||
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
|
||||
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
|
||||
CustomAuthenticationForm, MembershipRolesForm
|
||||
from .models import Club, Membership
|
||||
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
|
||||
@ -41,8 +41,7 @@ class CustomLoginView(LoginView):
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
logout(self.request)
|
||||
self.request.user = form.get_user()
|
||||
_set_current_request(self.request)
|
||||
_set_current_user_and_ip(form.get_user(), self.request.session, None)
|
||||
self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank
|
||||
return super().form_valid(form)
|
||||
|
||||
@ -71,7 +70,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
form.fields['email'].required = True
|
||||
form.fields['email'].help_text = _("This address must be valid.")
|
||||
|
||||
if PermissionBackend.check_perm(self.request, "member.change_profile", context['user_object'].profile):
|
||||
if PermissionBackend.check_perm(self.request.user, "member.change_profile", context['user_object'].profile):
|
||||
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
||||
data=self.request.POST if self.request.POST else None)
|
||||
if not self.object.profile.report_frequency:
|
||||
@ -154,13 +153,13 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
history_list = \
|
||||
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))\
|
||||
.order_by("-created_at")\
|
||||
.filter(PermissionBackend.filter_queryset(self.request, Transaction, "view"))
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))
|
||||
history_table = HistoryTable(history_list, prefix='transaction-')
|
||||
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
||||
context['history_list'] = history_table
|
||||
|
||||
club_list = Membership.objects.filter(user=user, date_end__gte=date.today() - timedelta(days=15))\
|
||||
.filter(PermissionBackend.filter_queryset(self.request, Membership, "view"))\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\
|
||||
.order_by("club__name", "-date_start")
|
||||
# Display only the most recent membership
|
||||
club_list = club_list.distinct("club__name")\
|
||||
@ -174,23 +173,24 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
modified_note = NoteUser.objects.get(pk=user.note.pk)
|
||||
# Don't log these tests
|
||||
modified_note._no_signal = True
|
||||
modified_note.is_active = False
|
||||
modified_note.is_active = True
|
||||
modified_note.inactivity_reason = 'manual'
|
||||
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
||||
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||
.check_perm(self.request.user, "note.change_noteuser_is_active",
|
||||
modified_note)
|
||||
old_note = NoteUser.objects.select_for_update().get(pk=user.note.pk)
|
||||
modified_note.inactivity_reason = 'forced'
|
||||
modified_note._force_save = True
|
||||
modified_note.save()
|
||||
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
||||
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||
.check_perm(self.request.user, "note.change_note_is_active", modified_note)
|
||||
old_note._force_save = True
|
||||
old_note._no_signal = True
|
||||
old_note.save()
|
||||
modified_note.refresh_from_db()
|
||||
modified_note.is_active = True
|
||||
context["can_unlock_note"] = not user.note.is_active and PermissionBackend\
|
||||
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
|
||||
.check_perm(self.request.user, "note.change_note_is_active", modified_note)
|
||||
|
||||
return context
|
||||
|
||||
@ -237,46 +237,12 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request, User, "view"))\
|
||||
pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view"))\
|
||||
.filter(profile__registration_valid=False)
|
||||
context["can_manage_registrations"] = pre_registered_users.exists()
|
||||
return context
|
||||
|
||||
|
||||
class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
View and manage user trust relationships
|
||||
"""
|
||||
model = User
|
||||
template_name = 'member/profile_trust.html'
|
||||
context_object_name = 'user_object'
|
||||
extra_context = {"title": _("Note friendships")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
context["trusting"] = TrustTable(
|
||||
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
|
||||
context["trusted_by"] = TrustedTable(
|
||||
note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
|
||||
trusting=context["object"].note,
|
||||
trusted=context["object"].note
|
||||
))
|
||||
context["widget"] = {
|
||||
"name": "trusted",
|
||||
"resetable": True,
|
||||
"attrs": {
|
||||
"class": "autocomplete form-control",
|
||||
"id": "trusted",
|
||||
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
|
||||
"name_field": "name",
|
||||
"placeholder": ""
|
||||
}
|
||||
}
|
||||
return context
|
||||
|
||||
|
||||
class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
View and manage user aliases.
|
||||
@ -290,9 +256,8 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
context["aliases"] = AliasTable(
|
||||
note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct()
|
||||
.order_by('normalized_name').all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
|
||||
note.alias.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
||||
note=context["object"].note,
|
||||
name="",
|
||||
normalized_name="",
|
||||
@ -326,9 +291,6 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
|
||||
"""Save image to note"""
|
||||
image = form.cleaned_data['image']
|
||||
|
||||
if image is None:
|
||||
image = "pic/default.png"
|
||||
else:
|
||||
# Rename as a PNG or GIF
|
||||
extension = image.name.split(".")[-1]
|
||||
if extension == "gif":
|
||||
@ -420,7 +382,7 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["can_add_club"] = PermissionBackend.check_perm(self.request, "member.add_club", Club(
|
||||
context["can_add_club"] = PermissionBackend.check_perm(self.request.user, "member.add_club", Club(
|
||||
name="",
|
||||
email="club@example.com",
|
||||
))
|
||||
@ -441,12 +403,9 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
club = self.object
|
||||
context["note"] = club.note
|
||||
|
||||
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
|
||||
club = context["club"]
|
||||
if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
|
||||
club.update_membership_dates()
|
||||
|
||||
# managers list
|
||||
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
||||
date_start__lte=date.today(), date_end__gte=date.today())\
|
||||
@ -454,7 +413,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
|
||||
# transaction history
|
||||
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
|
||||
.filter(PermissionBackend.filter_queryset(self.request, Transaction, "view"))\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
||||
.order_by('-created_at')
|
||||
history_table = HistoryTable(club_transactions, prefix="history-")
|
||||
history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
|
||||
@ -463,7 +422,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
club_member = Membership.objects.filter(
|
||||
club=club,
|
||||
date_end__gte=date.today() - timedelta(days=15),
|
||||
).filter(PermissionBackend.filter_queryset(self.request, Membership, "view"))\
|
||||
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\
|
||||
.order_by("user__username", "-date_start")
|
||||
# Display only the most recent membership
|
||||
club_member = club_member.distinct("user__username")\
|
||||
@ -484,29 +443,6 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context["can_add_members"] = PermissionBackend()\
|
||||
.has_perm(self.request.user, "member.add_membership", empty_membership)
|
||||
|
||||
# Check permissions to see if the authenticated user can lock/unlock the note
|
||||
with transaction.atomic():
|
||||
modified_note = NoteClub.objects.get(pk=club.note.pk)
|
||||
# Don't log these tests
|
||||
modified_note._no_signal = True
|
||||
modified_note.is_active = False
|
||||
modified_note.inactivity_reason = 'manual'
|
||||
context["can_lock_note"] = club.note.is_active and PermissionBackend \
|
||||
.check_perm(self.request, "note.change_noteclub_is_active", modified_note)
|
||||
old_note = NoteClub.objects.select_for_update().get(pk=club.note.pk)
|
||||
modified_note.inactivity_reason = 'forced'
|
||||
modified_note._force_save = True
|
||||
modified_note.save()
|
||||
context["can_force_lock"] = club.note.is_active and PermissionBackend \
|
||||
.check_perm(self.request, "note.change_noteclub_is_active", modified_note)
|
||||
old_note._force_save = True
|
||||
old_note._no_signal = True
|
||||
old_note.save()
|
||||
modified_note.refresh_from_db()
|
||||
modified_note.is_active = True
|
||||
context["can_unlock_note"] = not club.note.is_active and PermissionBackend \
|
||||
.check_perm(self.request, "note.change_noteclub_is_active", modified_note)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@ -523,8 +459,8 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
context["aliases"] = AliasTable(note.alias.filter(
|
||||
PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
|
||||
PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
|
||||
note=context["object"].note,
|
||||
name="",
|
||||
normalized_name="",
|
||||
@ -599,7 +535,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
form = context['form']
|
||||
|
||||
if "club_pk" in self.kwargs: # We create a new membership.
|
||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request, Club, "view"))\
|
||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
|
||||
.get(pk=self.kwargs["club_pk"], weiclub=None)
|
||||
form.fields['credit_amount'].initial = club.membership_fee_paid
|
||||
# Ensure that the user is member of the parent club and all its the family tree.
|
||||
@ -689,6 +625,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
# Retrieve form data
|
||||
credit_type = form.cleaned_data["credit_type"]
|
||||
credit_amount = form.cleaned_data["credit_amount"]
|
||||
last_name = form.cleaned_data["last_name"]
|
||||
first_name = form.cleaned_data["first_name"]
|
||||
bank = form.cleaned_data["bank"]
|
||||
soge = form.cleaned_data["soge"] and not user.profile.soge and (club.name == "BDE" or club.name == "Kfet")
|
||||
|
||||
if not credit_type:
|
||||
@ -719,7 +658,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
|
||||
user=form.instance.user,
|
||||
club=club.parent_club,
|
||||
date_start__gte=club.parent_club.membership_start,
|
||||
date_start__lte=timezone.now(),
|
||||
date_end__gte=club.parent_club.membership_end,
|
||||
).exists():
|
||||
form.add_error('user', _('User is not a member of the parent club') + ' ' + club.parent_club.name)
|
||||
error = True
|
||||
@ -734,8 +674,16 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
.format(form.instance.club.membership_end))
|
||||
error = True
|
||||
|
||||
if credit_amount and not SpecialTransaction.validate_payment_form(form):
|
||||
# Check that special information for payment are filled
|
||||
if credit_amount:
|
||||
if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"):
|
||||
if not last_name:
|
||||
form.add_error('last_name', _("This field is required."))
|
||||
error = True
|
||||
if not first_name:
|
||||
form.add_error('first_name', _("This field is required."))
|
||||
error = True
|
||||
if not bank and credit_type.special_type == "Chèque":
|
||||
form.add_error('bank', _("This field is required."))
|
||||
error = True
|
||||
|
||||
return not error
|
||||
@ -747,7 +695,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
"""
|
||||
# Get the club that is concerned by the membership
|
||||
if "club_pk" in self.kwargs: # get from url of new membership
|
||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request, Club, "view")) \
|
||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
||||
.get(pk=self.kwargs["club_pk"])
|
||||
user = form.instance.user
|
||||
old_membership = None
|
||||
@ -756,10 +704,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
club = old_membership.club
|
||||
user = old_membership.user
|
||||
|
||||
# Update club membership date
|
||||
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
|
||||
club.update_membership_dates()
|
||||
|
||||
form.instance.club = club
|
||||
|
||||
# Get form data
|
||||
@ -802,7 +746,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
# When we renew the BDE membership, we update the profile section
|
||||
# that should happens at least once a year.
|
||||
user.profile.section = user.profile.section_generated
|
||||
user.profile._force_save = True
|
||||
user.profile.save()
|
||||
|
||||
# Credit note before the membership is created.
|
||||
@ -935,7 +878,7 @@ class ClubMembersListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
club = Club.objects.filter(
|
||||
PermissionBackend.filter_queryset(self.request, Club, "view")
|
||||
PermissionBackend.filter_queryset(self.request.user, Club, "view")
|
||||
).get(pk=self.kwargs["pk"])
|
||||
context["club"] = club
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'note.apps.NoteConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
@ -7,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
|
||||
PolymorphicChildModelFilter, PolymorphicParentModelAdmin
|
||||
from note_kfet.admin import admin_site
|
||||
|
||||
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
|
||||
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
||||
from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \
|
||||
RecurrentTransaction, MembershipTransaction, SpecialTransaction
|
||||
from .templatetags.pretty_money import pretty_money
|
||||
@ -21,16 +21,6 @@ class AliasInlines(admin.TabularInline):
|
||||
model = Alias
|
||||
|
||||
|
||||
class TrustInlines(admin.TabularInline):
|
||||
"""
|
||||
Define trusts when editing the trusting note
|
||||
"""
|
||||
model = Trust
|
||||
fk_name = "trusting"
|
||||
extra = 0
|
||||
readonly_fields = ("trusted",)
|
||||
|
||||
|
||||
@admin.register(Note, site=admin_site)
|
||||
class NoteAdmin(PolymorphicParentModelAdmin):
|
||||
"""
|
||||
@ -102,7 +92,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
|
||||
"""
|
||||
Child for an user note, see NoteAdmin
|
||||
"""
|
||||
inlines = (AliasInlines, TrustInlines)
|
||||
inlines = (AliasInlines,)
|
||||
|
||||
# We can't change user after creation or the balance
|
||||
readonly_fields = ('user', 'balance')
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
@ -8,12 +8,11 @@ from rest_framework.exceptions import ValidationError
|
||||
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||
from member.api.serializers import MembershipSerializer
|
||||
from member.models import Membership
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from note_kfet.middlewares import get_current_authenticated_user
|
||||
from permission.backends import PermissionBackend
|
||||
from rest_framework.utils import model_meta
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust
|
||||
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
|
||||
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
|
||||
RecurrentTransaction, SpecialTransaction
|
||||
|
||||
@ -78,20 +77,6 @@ class NoteUserSerializer(serializers.ModelSerializer):
|
||||
return str(obj)
|
||||
|
||||
|
||||
class TrustSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
REST API Serializer for Trusts.
|
||||
The djangorestframework plugin will analyse the model `Trust` and parse all fields in the API.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Trust
|
||||
fields = '__all__'
|
||||
validators = [UniqueTogetherValidator(
|
||||
queryset=Trust.objects.all(), fields=('trusting', 'trusted'),
|
||||
message=_("This friendship already exists"))]
|
||||
|
||||
|
||||
class AliasSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
REST API Serializer for Aliases.
|
||||
@ -141,7 +126,7 @@ class ConsumerSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
# If the user has no right to see the note, then we only display the note identifier
|
||||
return NotePolymorphicSerializer().to_representation(obj.note)\
|
||||
if PermissionBackend.check_perm(get_current_request(), "note.view_note", obj.note)\
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", obj.note)\
|
||||
else dict(
|
||||
id=obj.note.id,
|
||||
name=str(obj.note),
|
||||
@ -157,7 +142,7 @@ class ConsumerSerializer(serializers.ModelSerializer):
|
||||
def get_membership(self, obj):
|
||||
if isinstance(obj.note, NoteUser):
|
||||
memberships = Membership.objects.filter(
|
||||
PermissionBackend.filter_queryset(get_current_request(), Membership, "view")).filter(
|
||||
PermissionBackend.filter_queryset(get_current_authenticated_user(), Membership, "view")).filter(
|
||||
user=obj.note.user,
|
||||
club=2, # Kfet
|
||||
).order_by("-date_start")
|
||||
|
@ -1,9 +1,8 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
|
||||
TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet, \
|
||||
TrustViewSet
|
||||
TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet
|
||||
|
||||
|
||||
def register_note_urls(router, path):
|
||||
@ -12,7 +11,6 @@ def register_note_urls(router, path):
|
||||
"""
|
||||
router.register(path + '/note', NotePolymorphicViewSet)
|
||||
router.register(path + '/alias', AliasViewSet)
|
||||
router.register(path + '/trust', TrustViewSet)
|
||||
router.register(path + '/consumer', ConsumerViewSet)
|
||||
|
||||
router.register(path + '/transaction/category', TemplateCategoryViewSet)
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
@ -11,12 +10,12 @@ from rest_framework import viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||
from note_kfet.middlewares import get_current_session
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \
|
||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
|
||||
TrustSerializer
|
||||
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
||||
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial
|
||||
from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
|
||||
|
||||
|
||||
@ -41,11 +40,12 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
||||
Parse query and apply filters.
|
||||
:return: The filtered set of requested notes
|
||||
"""
|
||||
queryset = self.queryset.filter(PermissionBackend.filter_queryset(self.request, Note, "view")
|
||||
| PermissionBackend.filter_queryset(self.request, NoteUser, "view")
|
||||
| PermissionBackend.filter_queryset(self.request, NoteClub, "view")
|
||||
| PermissionBackend.filter_queryset(self.request, NoteSpecial, "view"))\
|
||||
.distinct()
|
||||
user = self.request.user
|
||||
get_current_session().setdefault("permission_mask", 42)
|
||||
queryset = self.queryset.filter(PermissionBackend.filter_queryset(user, Note, "view")
|
||||
| PermissionBackend.filter_queryset(user, NoteUser, "view")
|
||||
| PermissionBackend.filter_queryset(user, NoteClub, "view")
|
||||
| PermissionBackend.filter_queryset(user, NoteSpecial, "view")).distinct()
|
||||
|
||||
alias = self.request.query_params.get("alias", ".*")
|
||||
queryset = queryset.filter(
|
||||
@ -57,55 +57,24 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
||||
return queryset.order_by("id")
|
||||
|
||||
|
||||
class TrustViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST Trust View set.
|
||||
The djangorestframework plugin will get all `Trust` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/note/trust/
|
||||
"""
|
||||
queryset = Trust.objects
|
||||
serializer_class = TrustSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
search_fields = ['$trusting__alias__name', '$trusting__alias__normalized_name',
|
||||
'$trusted__alias__name', '$trusted__alias__normalized_name']
|
||||
filterset_fields = ['trusting', 'trusting__noteuser__user', 'trusted', 'trusted__noteuser__user']
|
||||
ordering_fields = ['trusting', 'trusted', ]
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = self.serializer_class
|
||||
if self.request.method in ['PUT', 'PATCH']:
|
||||
# trust relationship can't change people involved
|
||||
serializer_class.Meta.read_only_fields = ('trusting', 'trusting',)
|
||||
return serializer_class
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
try:
|
||||
self.perform_destroy(instance)
|
||||
except ValidationError as e:
|
||||
return Response({e.code: str(e)}, status.HTTP_400_BAD_REQUEST)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class AliasViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/note/aliases/
|
||||
then render it on /api/aliases/
|
||||
"""
|
||||
queryset = Alias.objects
|
||||
serializer_class = AliasSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||
filterset_fields = ['name', 'normalized_name', 'note', 'note__noteuser__user',
|
||||
'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
ordering_fields = ['name', 'normalized_name', ]
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = self.serializer_class
|
||||
if self.request.method in ['PUT', 'PATCH']:
|
||||
# alias owner cannot be change once establish
|
||||
serializer_class.Meta.read_only_fields = ('note',)
|
||||
setattr(serializer_class.Meta, 'read_only_fields', ('note',))
|
||||
return serializer_class
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
@ -113,7 +82,7 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
try:
|
||||
self.perform_destroy(instance)
|
||||
except ValidationError as e:
|
||||
return Response({e.code: str(e)}, status.HTTP_400_BAD_REQUEST)
|
||||
return Response({e.code: e.message}, status.HTTP_400_BAD_REQUEST)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def get_queryset(self):
|
||||
@ -149,8 +118,7 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||
serializer_class = ConsumerSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||
filterset_fields = ['name', 'normalized_name', 'note', 'note__noteuser__user',
|
||||
'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
filterset_fields = ['note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
ordering_fields = ['name', 'normalized_name', ]
|
||||
|
||||
def get_queryset(self):
|
||||
@ -165,31 +133,23 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
|
||||
|
||||
alias = self.request.query_params.get("alias", None)
|
||||
# Check if this is a valid regex. If not, we won't check regex
|
||||
try:
|
||||
re.compile(alias)
|
||||
valid_regex = True
|
||||
except (re.error, TypeError):
|
||||
valid_regex = False
|
||||
suffix = '__iregex' if valid_regex else '__istartswith'
|
||||
alias_prefix = '^' if valid_regex else ''
|
||||
queryset = queryset.prefetch_related('note')
|
||||
|
||||
if alias:
|
||||
# We match first an alias if it is matched without normalization,
|
||||
# then if the normalized pattern matches a normalized alias.
|
||||
queryset = queryset.filter(
|
||||
**{f'name{suffix}': alias_prefix + alias}
|
||||
name__iregex="^" + alias
|
||||
).union(
|
||||
queryset.filter(
|
||||
Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
|
||||
& ~Q(**{f'name{suffix}': alias_prefix + alias})
|
||||
Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
& ~Q(name__iregex="^" + alias)
|
||||
),
|
||||
all=True).union(
|
||||
queryset.filter(
|
||||
Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
|
||||
& ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
|
||||
& ~Q(**{f'name{suffix}': alias_prefix + alias})
|
||||
Q(normalized_name__iregex="^" + alias.lower())
|
||||
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
& ~Q(name__iregex="^" + alias)
|
||||
),
|
||||
all=True)
|
||||
|
||||
@ -245,5 +205,7 @@ class TransactionViewSet(ReadProtectedModelViewSet):
|
||||
ordering_fields = ['created_at', 'amount', ]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(PermissionBackend.filter_queryset(self.request, self.model, "view"))\
|
||||
user = self.request.user
|
||||
get_current_session().setdefault("permission_mask", 42)
|
||||
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))\
|
||||
.order_by("created_at", "id")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user