Merge branch 'fix-ci' into 'master'

Rendre Erdnaxe heureux

See merge request bde/nk20!42
This commit is contained in:
ynerant 2020-03-07 22:37:37 +01:00
commit 7f91f27753
30 changed files with 215 additions and 205 deletions

View File

@ -11,7 +11,7 @@ class ActivityAdmin(admin.ModelAdmin):
Admin customisation for Activity
"""
list_display = ('name', 'activity_type', 'organizer')
list_filter = ('activity_type', )
list_filter = ('activity_type',)
search_fields = ['name', 'organizer__name']
# Organize activities by start date

View File

@ -11,6 +11,7 @@ class ActivityTypeSerializer(serializers.ModelSerializer):
REST API Serializer for Activity types.
The djangorestframework plugin will analyse the model `ActivityType` and parse all fields in the API.
"""
class Meta:
model = ActivityType
fields = '__all__'
@ -21,6 +22,7 @@ class ActivitySerializer(serializers.ModelSerializer):
REST API Serializer for Activities.
The djangorestframework plugin will analyse the model `Activity` and parse all fields in the API.
"""
class Meta:
model = Activity
fields = '__all__'
@ -31,6 +33,7 @@ class GuestSerializer(serializers.ModelSerializer):
REST API Serializer for Guests.
The djangorestframework plugin will analyse the model `Guest` and parse all fields in the API.
"""
class Meta:
model = Guest
fields = '__all__'

View File

@ -3,8 +3,8 @@
from rest_framework import viewsets
from ..models import ActivityType, Activity, Guest
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer
from ..models import ActivityType, Activity, Guest
class ActivityTypeViewSet(viewsets.ModelViewSet):

View File

@ -14,6 +14,7 @@ class UserSerializer(serializers.ModelSerializer):
REST API Serializer for Users.
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
"""
class Meta:
model = User
exclude = (

View File

@ -1,11 +1,11 @@
# 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.utils.translation import gettext_lazy as _
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
class Changelog(models.Model):

View File

@ -7,6 +7,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core import serializers
from django.db.models.signals import pre_save, post_save, post_delete
from django.dispatch import receiver
from .models import Changelog
@ -43,20 +44,20 @@ def get_user_and_ip(sender):
EXCLUDED = [
'admin.logentry',
'authtoken.token',
'cas_server.user',
'cas_server.userattributes',
'contenttypes.contenttype',
'logs.changelog',
'migrations.migration',
'note.noteuser',
'note.noteclub',
'note.notespecial',
'sessions.session',
'reversion.revision',
'reversion.version',
]
'admin.logentry',
'authtoken.token',
'cas_server.user',
'cas_server.userattributes',
'contenttypes.contenttype',
'logs.changelog',
'migrations.migration',
'note.noteuser',
'note.noteclub',
'note.notespecial',
'sessions.session',
'reversion.revision',
'reversion.version',
]
@receiver(pre_save)

View File

@ -18,9 +18,9 @@ class ProfileInline(admin.StackedInline):
class CustomUserAdmin(UserAdmin):
inlines = (ProfileInline, )
inlines = (ProfileInline,)
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_select_related = ('profile', )
list_select_related = ('profile',)
form = ProfileForm
def get_inline_instances(self, request, obj=None):

View File

@ -11,6 +11,7 @@ class ProfileSerializer(serializers.ModelSerializer):
REST API Serializer for Profiles.
The djangorestframework plugin will analyse the model `Profile` and parse all fields in the API.
"""
class Meta:
model = Profile
fields = '__all__'
@ -21,6 +22,7 @@ class ClubSerializer(serializers.ModelSerializer):
REST API Serializer for Clubs.
The djangorestframework plugin will analyse the model `Club` and parse all fields in the API.
"""
class Meta:
model = Club
fields = '__all__'
@ -31,6 +33,7 @@ class RoleSerializer(serializers.ModelSerializer):
REST API Serializer for Roles.
The djangorestframework plugin will analyse the model `Role` and parse all fields in the API.
"""
class Meta:
model = Role
fields = '__all__'
@ -41,6 +44,7 @@ class MembershipSerializer(serializers.ModelSerializer):
REST API Serializer for Memberships.
The djangorestframework plugin will analyse the model `Memberships` and parse all fields in the API.
"""
class Meta:
model = Membership
fields = '__all__'

View File

@ -3,8 +3,8 @@
from rest_framework import viewsets
from ..models import Profile, Club, Role, Membership
from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer
from ..models import Profile, Club, Role, Membership
class ProfileViewSet(viewsets.ModelViewSet):

View File

@ -1,11 +1,11 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django_filters import FilterSet, CharFilter
from django.contrib.auth.models import User
from django.db.models import CharField
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
from django.contrib.auth.models import User
from django.db.models import CharField
from django_filters import FilterSet, CharFilter
class UserFilter(FilterSet):

View File

@ -1,23 +1,22 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from crispy_forms.bootstrap import Div
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout
from dal import autocomplete
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django import forms
from .models import Profile, Club, Membership
from crispy_forms.helper import FormHelper
from crispy_forms.bootstrap import Div
from crispy_forms.layout import Layout
class SignUpForm(UserCreationForm):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.pop("autofocus", None)
self.fields['first_name'].widget.attrs.update({"autofocus":"autofocus"})
self.fields['first_name'].widget.attrs.update({"autofocus": "autofocus"})
class Meta:
model = User
@ -28,6 +27,7 @@ class ProfileForm(forms.ModelForm):
"""
A form for the extras field provided by the :model:`member.Profile` model.
"""
class Meta:
model = Profile
fields = '__all__'
@ -42,7 +42,7 @@ class ClubForm(forms.ModelForm):
class AddMembersForm(forms.Form):
class Meta:
fields = ('', )
fields = ('',)
class MembershipForm(forms.ModelForm):
@ -54,13 +54,13 @@ class MembershipForm(forms.ModelForm):
# et récupère les noms d'utilisateur valides
widgets = {
'user':
autocomplete.ModelSelect2(
url='member:user_autocomplete',
attrs={
'data-placeholder': 'Nom ...',
'data-minimum-input-length': 1,
},
),
autocomplete.ModelSelect2(
url='member:user_autocomplete',
attrs={
'data-placeholder': 'Nom ...',
'data-minimum-input-length': 1,
},
),
}

View File

@ -3,8 +3,8 @@
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
class Profile(models.Model):
@ -48,7 +48,7 @@ class Profile(models.Model):
verbose_name_plural = _('user profile')
def get_absolute_url(self):
return reverse('user_detail', args=(self.pk, ))
return reverse('user_detail', args=(self.pk,))
class Club(models.Model):
@ -97,7 +97,7 @@ class Club(models.Model):
return self.name
def get_absolute_url(self):
return reverse_lazy('member:club_detail', args=(self.pk, ))
return reverse_lazy('member:club_detail', args=(self.pk,))
class Role(models.Model):
@ -153,7 +153,6 @@ class Membership(models.Model):
verbose_name = _('membership')
verbose_name_plural = _('memberships')
# @receiver(post_save, sender=settings.AUTH_USER_MODEL)
# def save_user_profile(instance, created, **_kwargs):
# """

View File

@ -1,6 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
def save_user_profile(instance, created, raw, **_kwargs):
"""
Hook to create and save a profile when an user is updated if it is not registered with the signup form

View File

@ -1,33 +1,33 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView,DeleteView
from django.views.generic.edit import FormMixin
from django.contrib.auth.models import User
from django.contrib import messages
from django.urls import reverse_lazy
from django.http import HttpResponseRedirect
from django.db.models import Q
from django.core.exceptions import ValidationError
from django.conf import settings
from django_tables2.views import SingleTableView
from rest_framework.authtoken.models import Token
from dal import autocomplete
from PIL import Image
import io
from PIL import Image
from dal import autocomplete
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView, DeleteView
from django.views.generic.edit import FormMixin
from django_tables2.views import SingleTableView
from rest_framework.authtoken.models import Token
from note.forms import AliasForm, ImageForm
from note.models import Alias, NoteUser
from note.models.transactions import Transaction
from note.tables import HistoryTable, AliasTable
from note.forms import AliasForm, ImageForm
from .models import Profile, Club, Membership
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
from .tables import ClubTable, UserTable
from .filters import UserFilter, UserFilterFormHelper
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
from .models import Club, Membership
from .tables import ClubTable, UserTable
class UserCreateView(CreateView):
@ -109,7 +109,7 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
return reverse_lazy('member:user_detail',
kwargs={'pk': kwargs['id']})
else:
return reverse_lazy('member:user_detail', args=(self.object.id, ))
return reverse_lazy('member:user_detail', args=(self.object.id,))
class UserDetailView(LoginRequiredMixin, DetailView):
@ -157,13 +157,14 @@ class UserListView(LoginRequiredMixin, SingleTableView):
context["filter"] = self.filter
return context
class AliasView(LoginRequiredMixin,FormMixin,DetailView):
class AliasView(LoginRequiredMixin, FormMixin, DetailView):
model = User
template_name = 'member/profile_alias.html'
context_object_name = 'user_object'
form_class = AliasForm
def get_context_data(self,**kwargs):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
note = context['user_object'].note
context["aliases"] = AliasTable(note.alias_set.all())
@ -172,7 +173,7 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView):
def get_success_url(self):
return reverse_lazy('member:user_alias', kwargs={'pk': self.object.id})
def post(self,request,*args,**kwargs):
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
@ -186,42 +187,45 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView):
alias.save()
return super().form_valid(form)
class DeleteAliasView(LoginRequiredMixin, DeleteView):
model = Alias
def delete(self,request,*args,**kwargs):
def delete(self, request, *args, **kwargs):
try:
self.object = self.get_object()
self.object.delete()
except ValidationError as e:
# TODO: pass message to redirected view.
messages.error(self.request,str(e))
messages.error(self.request, str(e))
else:
messages.success(self.request,_("Alias successfully deleted"))
messages.success(self.request, _("Alias successfully deleted"))
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
print(self.request)
return reverse_lazy('member:user_alias',kwargs={'pk':self.object.note.user.pk})
return reverse_lazy('member:user_alias', kwargs={'pk': self.object.note.user.pk})
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
model = User
template_name = 'member/profile_picture_update.html'
context_object_name = 'user_object'
form_class = ImageForm
def get_context_data(self,*args,**kwargs):
context = super().get_context_data(*args,**kwargs)
context['form'] = self.form_class(self.request.POST,self.request.FILES)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['form'] = self.form_class(self.request.POST, self.request.FILES)
return context
def get_success_url(self):
return reverse_lazy('member:user_detail', kwargs={'pk': self.object.id})
def post(self,request,*args,**kwargs):
form = self.get_form()
def post(self, request, *args, **kwargs):
form = self.get_form()
self.object = self.get_object()
if form.is_valid():
return self.form_valid(form)
@ -230,7 +234,7 @@ class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
print(form)
return self.form_invalid(form)
def form_valid(self,form):
def form_valid(self, form):
image_field = form.cleaned_data['image']
x = form.cleaned_data['x']
y = form.cleaned_data['y']
@ -238,15 +242,15 @@ class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
h = form.cleaned_data['height']
# image crop and resize
image_file = io.BytesIO(image_field.read())
ext = image_field.name.split('.')[-1].lower()
#TODO: support GIF format
# ext = image_field.name.split('.')[-1].lower()
# TODO: support GIF format
image = Image.open(image_file)
image = image.crop((x, y, x+w, y+h))
image = image.crop((x, y, x + w, y + h))
image_clean = image.resize((settings.PIC_WIDTH,
settings.PIC_RATIO*settings.PIC_WIDTH),
Image.ANTIALIAS)
settings.PIC_RATIO * settings.PIC_WIDTH),
Image.ANTIALIAS)
image_file = io.BytesIO()
image_clean.save(image_file,"PNG")
image_clean.save(image_file, "PNG")
image_field.file = image_file
# renaming
filename = "{}_pic.png".format(self.object.note.pk)
@ -255,7 +259,7 @@ class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
self.object.note.save()
return super().form_valid(form)
class ManageAuthTokens(LoginRequiredMixin, TemplateView):
"""
Affiche le jeton d'authentification, et permet de le regénérer
@ -283,6 +287,7 @@ class UserAutocomplete(autocomplete.Select2QuerySetView):
"""
Auto complete users by usernames
"""
def get_queryset(self):
"""
Quand une personne cherche un utilisateur par pseudo, une requête est envoyée sur l'API dédiée à l'auto-complétion.
@ -331,7 +336,7 @@ class ClubDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
club = context["club"]
club_transactions = \
club_transactions = \
Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))
context['history_list'] = HistoryTable(club_transactions)
club_member = \

View File

@ -47,11 +47,11 @@ class NoteClubAdmin(PolymorphicChildModelAdmin):
"""
Child for a club note, see NoteAdmin
"""
inlines = (AliasInlines, )
inlines = (AliasInlines,)
# We can't change club after creation or the balance
readonly_fields = ('club', 'balance')
search_fields = ('club', )
search_fields = ('club',)
def has_add_permission(self, request):
"""
@ -71,7 +71,7 @@ class NoteSpecialAdmin(PolymorphicChildModelAdmin):
"""
Child for a special note, see NoteAdmin
"""
readonly_fields = ('balance', )
readonly_fields = ('balance',)
@admin.register(NoteUser)
@ -79,7 +79,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
"""
Child for an user note, see NoteAdmin
"""
inlines = (AliasInlines, )
inlines = (AliasInlines,)
# We can't change user after creation or the balance
readonly_fields = ('user', 'balance')
@ -133,7 +133,7 @@ class TransactionAdmin(PolymorphicParentModelAdmin):
Else the amount of money would not be transferred
"""
if obj: # user is editing an existing object
return 'created_at', 'source', 'destination', 'quantity',\
return 'created_at', 'source', 'destination', 'quantity', \
'amount'
return []
@ -143,9 +143,9 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
"""
Admin customisation for TransactionTemplate
"""
list_display = ('name', 'poly_destination', 'amount', 'category', 'display', )
list_display = ('name', 'poly_destination', 'amount', 'category', 'display',)
list_filter = ('category', 'display')
autocomplete_fields = ('destination', )
autocomplete_fields = ('destination',)
def poly_destination(self, obj):
"""
@ -161,5 +161,5 @@ class TemplateCategoryAdmin(admin.ModelAdmin):
"""
Admin customisation for TransactionTemplate
"""
list_display = ('name', )
list_filter = ('name', )
list_display = ('name',)
list_filter = ('name',)

View File

@ -13,6 +13,7 @@ class NoteSerializer(serializers.ModelSerializer):
REST API Serializer for Notes.
The djangorestframework plugin will analyse the model `Note` and parse all fields in the API.
"""
class Meta:
model = Note
fields = '__all__'
@ -29,6 +30,7 @@ class NoteClubSerializer(serializers.ModelSerializer):
REST API Serializer for Club's notes.
The djangorestframework plugin will analyse the model `NoteClub` and parse all fields in the API.
"""
class Meta:
model = NoteClub
fields = '__all__'
@ -39,6 +41,7 @@ class NoteSpecialSerializer(serializers.ModelSerializer):
REST API Serializer for special notes.
The djangorestframework plugin will analyse the model `NoteSpecial` and parse all fields in the API.
"""
class Meta:
model = NoteSpecial
fields = '__all__'
@ -49,6 +52,7 @@ class NoteUserSerializer(serializers.ModelSerializer):
REST API Serializer for User's notes.
The djangorestframework plugin will analyse the model `NoteUser` and parse all fields in the API.
"""
class Meta:
model = NoteUser
fields = '__all__'
@ -59,6 +63,7 @@ class AliasSerializer(serializers.ModelSerializer):
REST API Serializer for Aliases.
The djangorestframework plugin will analyse the model `Alias` and parse all fields in the API.
"""
class Meta:
model = Alias
fields = '__all__'
@ -78,6 +83,7 @@ class TransactionTemplateSerializer(serializers.ModelSerializer):
REST API Serializer for Transaction templates.
The djangorestframework plugin will analyse the model `TransactionTemplate` and parse all fields in the API.
"""
class Meta:
model = TransactionTemplate
fields = '__all__'
@ -88,6 +94,7 @@ class TransactionSerializer(serializers.ModelSerializer):
REST API Serializer for Transactions.
The djangorestframework plugin will analyse the model `Transaction` and parse all fields in the API.
"""
class Meta:
model = Transaction
fields = '__all__'
@ -98,6 +105,7 @@ class MembershipTransactionSerializer(serializers.ModelSerializer):
REST API Serializer for Membership transactions.
The djangorestframework plugin will analyse the model `MembershipTransaction` and parse all fields in the API.
"""
class Meta:
model = MembershipTransaction
fields = '__all__'

View File

@ -4,11 +4,11 @@
from django.db.models import Q
from rest_framework import viewsets
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction
from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \
NoteUserSerializer, AliasSerializer, \
TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction
class NoteViewSet(viewsets.ModelViewSet):

View File

@ -3,31 +3,25 @@
from dal import autocomplete
from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _
import os
from crispy_forms.helper import FormHelper
from crispy_forms.bootstrap import Div
from crispy_forms.layout import Layout, HTML
from .models import Alias
from .models import Transaction, TransactionTemplate, TemplateTransaction
from .models import Note, Alias
class AliasForm(forms.ModelForm):
class Meta:
model = Alias
fields = ("name",)
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["name"].label = False
self.fields["name"].widget.attrs={"placeholder":_('New Alias')}
self.fields["name"].widget.attrs = {"placeholder": _('New Alias')}
class ImageForm(forms.Form):
image = forms.ImageField(required = False,
image = forms.ImageField(required=False,
label=_('select an image'),
help_text=_('Maximal size: 2MB'))
x = forms.FloatField(widget=forms.HiddenInput())
@ -35,7 +29,7 @@ class ImageForm(forms.Form):
width = forms.FloatField(widget=forms.HiddenInput())
height = forms.FloatField(widget=forms.HiddenInput())
class TransactionTemplateForm(forms.ModelForm):
class Meta:
model = TransactionTemplate
@ -48,13 +42,13 @@ class TransactionTemplateForm(forms.ModelForm):
# forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special}
widgets = {
'destination':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
}
@ -62,7 +56,6 @@ class TransactionForm(forms.ModelForm):
def save(self, commit=True):
super().save(commit)
def clean(self):
"""
If the user has no right to transfer funds, then it will be the source of the transfer by default.
@ -70,7 +63,7 @@ class TransactionForm(forms.ModelForm):
"""
cleaned_data = super().clean()
if not "source" in cleaned_data: # TODO Replace it with "if %user has no right to transfer funds"
if "source" not in cleaned_data: # TODO Replace it with "if %user has no right to transfer funds"
cleaned_data["source"] = self.user.note
if cleaned_data["source"].pk == cleaned_data["destination"].pk:
@ -78,7 +71,6 @@ class TransactionForm(forms.ModelForm):
return cleaned_data
class Meta:
model = Transaction
fields = (
@ -91,21 +83,21 @@ class TransactionForm(forms.ModelForm):
# Voir ci-dessus
widgets = {
'source':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
'destination':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
}
@ -122,18 +114,18 @@ class ConsoForm(forms.ModelForm):
class Meta:
model = TemplateTransaction
fields = ('source', )
fields = ('source',)
# Le champ d'utilisateur est remplacé par un champ d'auto-complétion.
# Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion
# et récupère les aliases de note valides
widgets = {
'source':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
}

View File

@ -9,6 +9,7 @@ from django.core.validators import RegexValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from polymorphic.models import PolymorphicModel
"""
Defines each note types
"""
@ -27,7 +28,7 @@ class Note(PolymorphicModel):
help_text=_('in centimes, money credited for this instance'),
default=0,
)
last_negative= models.DateTimeField(
last_negative = models.DateTimeField(
verbose_name=_('last negative date'),
help_text=_('last time the balance was negative'),
null=True,
@ -98,7 +99,7 @@ class Note(PolymorphicModel):
# Alias exists, so check if it is linked to this note
if aliases.first().note != self:
raise ValidationError(_('This alias is already taken.'),
code="same_alias",)
code="same_alias", )
else:
# Alias does not exist yet, so check if it can exist
a = Alias(name=str(self))
@ -231,12 +232,12 @@ class Alias(models.Model):
sim_alias = Alias.objects.get(normalized_name=normalized_name)
if self != sim_alias:
raise ValidationError(_('An alias with a similar name already exists: {} '.format(sim_alias)),
code="same_alias"
)
code="same_alias"
)
except Alias.DoesNotExist:
pass
self.normalized_name = normalized_name
def delete(self, using=None, keep_parents=False):
if self.name == str(self.note):
raise ValidationError(_("You can't delete your main alias."),

View File

@ -2,9 +2,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from polymorphic.models import PolymorphicModel
from .notes import Note, NoteClub
@ -44,7 +44,7 @@ class TransactionTemplate(models.Model):
verbose_name=_('name'),
max_length=255,
unique=True,
error_messages={'unique':_("A template with this name already exist")},
error_messages={'unique': _("A template with this name already exist")},
)
destination = models.ForeignKey(
NoteClub,
@ -63,7 +63,7 @@ class TransactionTemplate(models.Model):
max_length=31,
)
display = models.BooleanField(
default = True,
default=True,
)
description = models.CharField(
verbose_name=_('description'),
@ -75,7 +75,7 @@ class TransactionTemplate(models.Model):
verbose_name_plural = _("transaction templates")
def get_absolute_url(self):
return reverse('note:template_update', args=(self.pk, ))
return reverse('note:template_update', args=(self.pk,))
class Transaction(PolymorphicModel):
@ -168,6 +168,7 @@ class TemplateTransaction(Transaction):
on_delete=models.PROTECT,
)
class MembershipTransaction(Transaction):
"""
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.

View File

@ -4,14 +4,16 @@
import django_tables2 as tables
from django.db.models import F
from django_tables2.utils import A
from .models.transactions import Transaction
from .models.notes import Alias
from .models.transactions import Transaction
class HistoryTable(tables.Table):
class Meta:
attrs = {
'class':
'table table-condensed table-striped table-hover'
'table table-condensed table-striped table-hover'
}
model = Transaction
template_name = 'django_tables2/bootstrap4.html'
@ -25,21 +27,22 @@ class HistoryTable(tables.Table):
.order_by(('-' if is_descending else '') + 'total')
return (queryset, True)
class AliasTable(tables.Table):
class Meta:
attrs = {
'class':
'table table condensed table-striped table-hover'
'table table condensed table-striped table-hover'
}
model = Alias
fields =('name',)
fields = ('name',)
template_name = 'django_tables2/bootstrap4.html'
show_header = False
name = tables.Column(attrs={'td':{'class':'text-center'}})
name = tables.Column(attrs={'td': {'class': 'text-center'}})
delete = tables.LinkColumn('member:user_alias_delete',
args=[A('pk')],
attrs={
'td': {'class':'col-sm-2'},
'a': {'class': 'btn btn-danger'} },
text='delete',accessor='pk')
'td': {'class': 'col-sm-2'},
'a': {'class': 'btn btn-danger'}},
text='delete', accessor='pk')

View File

@ -8,8 +8,8 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, ListView, UpdateView
from .models import Transaction, TransactionTemplate, Alias, TemplateTransaction
from .forms import TransactionForm, TransactionTemplateForm, ConsoForm
from .models import Transaction, TransactionTemplate, Alias, TemplateTransaction
class TransactionCreate(LoginRequiredMixin, CreateView):
@ -53,6 +53,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView):
"""
Auto complete note by aliases
"""
def get_queryset(self):
"""
Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion.
@ -66,7 +67,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView):
# self.q est le paramètre de la recherche
if self.q:
qs = qs.filter(Q(name__regex=self.q) | Q(normalized_name__regex=Alias.normalize(self.q)))\
qs = qs.filter(Q(name__regex=self.q) | Q(normalized_name__regex=Alias.normalize(self.q))) \
.order_by('normalized_name').distinct()
# Filtrage par type de note (user, club, special)
@ -147,4 +148,3 @@ class ConsoView(LoginRequiredMixin, CreateView):
When clicking a button, reload the same page
"""
return reverse('note:consos')

View File

@ -1,10 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.http import HttpResponseRedirect
from urllib.parse import urlencode, parse_qs, urlsplit, urlunsplit
class TurbolinksMiddleware(object):
"""
@ -35,4 +31,3 @@ class TurbolinksMiddleware(object):
location = request.session.pop('_turbolinks_redirect_to')
response['Turbolinks-Location'] = location
return response

View File

@ -1,8 +1,6 @@
import os
import re
from .base import *
def read_env():
"""Pulled from Honcho code with minor updates, reads local default
environment variables from a .env file located in the project root
@ -25,14 +23,16 @@ def read_env():
val = re.sub(r'\\(.)', r'\1', m3.group(1))
os.environ.setdefault(key, val)
read_env()
app_stage = os.environ.get('DJANGO_APP_STAGE', 'dev')
if app_stage == 'prod':
from .production import *
DATABASES["default"]["PASSWORD"] = os.environ.get('DJANGO_DB_PASSWORD','CHANGE_ME_IN_ENV_SETTINGS')
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY','CHANGE_ME_IN_ENV_SETTINGS')
ALLOWED_HOSTS.append(os.environ.get('ALLOWED_HOSTS','localhost'))
DATABASES["default"]["PASSWORD"] = os.environ.get('DJANGO_DB_PASSWORD', 'CHANGE_ME_IN_ENV_SETTINGS')
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'CHANGE_ME_IN_ENV_SETTINGS')
ALLOWED_HOSTS.append(os.environ.get('ALLOWED_HOSTS', 'localhost'))
else:
from .development import *
@ -43,4 +43,3 @@ except ImportError:
# env variables set at the of in /env/bin/activate
# don't forget to unset in deactivate !

View File

@ -97,7 +97,7 @@ TEMPLATES = [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
# 'django.template.context_processors.media',
# 'django.template.context_processors.media',
],
},
},
@ -183,7 +183,7 @@ FIXTURE_DIRS = [os.path.join(BASE_DIR, "note_kfet/fixtures")]
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/var/www/example.com/static/"
STATIC_ROOT = os.path.join(BASE_DIR,"static/")
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
# STATICFILES_DIRS = [
# os.path.join(BASE_DIR, 'static')]
STATICFILES_DIRS = []
@ -195,8 +195,8 @@ STATIC_URL = '/static/'
ALIAS_VALIDATOR_REGEX = r''
MEDIA_ROOT=os.path.join(BASE_DIR,"media")
MEDIA_URL='/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = '/media/'
# Profile Picture Settings
PIC_WIDTH = 200
@ -211,7 +211,7 @@ CAS_SHOW_POWERED = False
CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT = False
CAS_INFO_MESSAGES = {
"cas_explained": {
"message":_(
"message": _(
u"The Central Authentication Service grants you access to most of our websites by "
u"authenticating only once, so you don't need to type your credentials again unless "
u"your session expires or you logout."
@ -224,4 +224,3 @@ CAS_INFO_MESSAGES = {
CAS_INFO_MESSAGES_ORDER = [
'cas_explained',
]

View File

@ -11,10 +11,11 @@
# - and more ...
import os
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
from . import *
import os
DATABASES = {
'default': {
@ -53,8 +54,6 @@ SESSION_COOKIE_AGE = 60 * 60 * 3
# Can be modified in secrets.py
CAS_SERVER_URL = "http://localhost:8000/cas/"
STATIC_ROOT = '' # not needed in development settings
STATIC_ROOT = '' # not needed in development settings
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')]
os.path.join(BASE_DIR, 'static')]

View File

@ -26,7 +26,7 @@ DATABASES = {
DEBUG = True
# Mandatory !
ALLOWED_HOSTS = ['127.0.0.1','note.comby.xyz']
ALLOWED_HOSTS = ['127.0.0.1', 'note.comby.xyz']
# Emails
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

View File

@ -1,13 +1,12 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from cas import views as cas_views
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from django.views.generic import RedirectView
from django.conf.urls.static import static
from django.conf import settings
from cas import views as cas_views
urlpatterns = [
# Dev so redirect to something random
@ -36,5 +35,5 @@ urlpatterns = [
path('logs/', include('logs.urls')),
]
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -1,11 +1,11 @@
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" IssueInstant="{{IssueInstant}}"
MajorVersion="1" MinorVersion="1" Recipient="{{Recipient}}"
ResponseID="{{ResponseID}}">
<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"
IssueInstant="{{ IssueInstant }}"
MajorVersion="1" MinorVersion="1" Recipient="{{ Recipient }}"
ResponseID="{{ ResponseID }}">
<Status>
<StatusCode Value="samlp:Success">
</StatusCode>

View File

@ -1,11 +1,11 @@
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" IssueInstant="{{IssueInstant}}"
MajorVersion="1" MinorVersion="1" Recipient="{{Recipient}}"
ResponseID="{{ResponseID}}">
<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"
IssueInstant="{{ IssueInstant }}"
MajorVersion="1" MinorVersion="1" Recipient="{{ Recipient }}"
ResponseID="{{ ResponseID }}">
<Status>
<StatusCode Value="samlp:{{code}}">{{msg}}</StatusCode>
</Status>