Merge branch 'front_club' into 'master'

Finitions sur l'interface club

See merge request bde/nk20!70
This commit is contained in:
Pierre-antoine Comby 2020-03-27 14:11:28 +01:00
commit 8b4c39269c
22 changed files with 224 additions and 166 deletions

View File

@ -14,12 +14,12 @@ urlpatterns = [
path('club/create/', views.ClubCreateView.as_view(), name="club_create"),
path('club/<int:pk>/update', views.ClubUpdateView.as_view(), name="club_update"),
path('club/<int:pk>/update_pic', views.ClubPictureUpdateView.as_view(), name="club_update_pic"),
path('club/<int:pk>/aliases', views.ClubAliasView.as_view(), name="club_alias"),
path('user/', views.UserListView.as_view(), name="user_list"),
path('user/<int:pk>', views.UserDetailView.as_view(), name="user_detail"),
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.AliasView.as_view(), name="user_alias"),
path('user/aliases/delete/<int:pk>', views.DeleteAliasView.as_view(), name="user_alias_delete"),
path('user/<int:pk>/aliases', views.ProfileAliasView.as_view(), name="user_alias"),
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
# API for the user autocompleter
path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"),

View File

@ -20,7 +20,8 @@ from django.views.generic import CreateView, DetailView, UpdateView, TemplateVie
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.forms import ImageForm
#from note.forms import AliasForm, ImageForm
from note.models import Alias, NoteUser
from note.models.transactions import Transaction
from note.tables import HistoryTable, AliasTable
@ -143,10 +144,6 @@ class UserDetailView(LoginRequiredMixin, DetailView):
club_list = \
Membership.objects.all().filter(user=user).only("club")
context['club_list'] = ClubTable(club_list)
context['title'] = _("Account #%(id)s: %(username)s") % {
'id': user.pk,
'username': user.username,
}
return context
@ -171,57 +168,18 @@ class UserListView(LoginRequiredMixin, SingleTableView):
context["filter"] = self.filter
return context
class AliasView(LoginRequiredMixin, FormMixin, DetailView):
class ProfileAliasView(LoginRequiredMixin, DetailView):
model = User
template_name = 'member/profile_alias.html'
context_object_name = 'user_object'
form_class = AliasForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
note = context['user_object'].note
note = context['object'].note
context["aliases"] = AliasTable(note.alias_set.all())
return context
def get_success_url(self):
return reverse_lazy('member:user_alias', kwargs={'pk': self.object.id})
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
alias = form.save(commit=False)
alias.note = self.object.note
alias.save()
return super().form_valid(form)
class DeleteAliasView(LoginRequiredMixin, DeleteView):
model = Alias
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))
else:
messages.success(self.request, _("Alias successfully deleted"))
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
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 PictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
form_class = ImageForm
@ -368,6 +326,17 @@ class ClubDetailView(LoginRequiredMixin, DetailView):
context['member_list'] = club_member
return context
class ClubAliasView(LoginRequiredMixin, DetailView):
model = Club
template_name = 'member/club_alias.html'
context_object_name = 'club'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
note = context['object'].note
context["aliases"] = AliasTable(note.alias_set.all())
return context
class ClubUpdateView(LoginRequiredMixin, UpdateView):
model = Club
@ -395,12 +364,12 @@ class ClubAddMemberView(LoginRequiredMixin, CreateView):
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")
| PermissionBackend.filter_queryset(self.request.user, Membership,
"change"))
def get_context_data(self, **kwargs):
club = Club.objects.get(pk=self.kwargs["pk"])
context = super().get_context_data(**kwargs)
context['formset'] = MemberFormSet()
context['helper'] = FormSetHelper()
context['club'] = club
context['no_cache'] = True
return context

View File

@ -78,7 +78,11 @@ class AliasSerializer(serializers.ModelSerializer):
class Meta:
model = Alias
fields = '__all__'
read_only_fields = ('note', )
def validate(self, attrs):
instance = Alias(**attrs)
instance.clean()
return attrs
class NotePolymorphicSerializer(PolymorphicSerializer):

View File

@ -2,10 +2,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from django.db.models import Q
from django.core.exceptions import ValidationError
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \
TransactionTemplateSerializer, TransactionPolymorphicSerializer
@ -53,6 +57,22 @@ class AliasViewSet(ReadProtectedModelViewSet):
search_fields = ['$normalized_name', '$name', '$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
setattr(serializer_class.Meta, 'read_only_fields', ('note',))
return serializer_class
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
try:
self.perform_destroy(instance)
except ValidationError as e:
print(e)
return Response({e.code:e.message},status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_204_NO_CONTENT)
def get_queryset(self):
"""
Parse query and apply filters.

View File

@ -0,0 +1,58 @@
[
{
"model": "note.templatecategory",
"pk": 1,
"fields": {
"name": "Soft"
}
},
{
"model": "note.templatecategory",
"pk": 2,
"fields": {
"name": "Pulls"
}
},
{
"model": "note.templatecategory",
"pk": 3,
"fields": {
"name": "Gala"
}
},
{
"model": "note.templatecategory",
"pk": 4,
"fields": {
"name": "Clubs"
}
},
{
"model": "note.templatecategory",
"pk": 5,
"fields": {
"name": "Bouffe"
}
},
{
"model": "note.templatecategory",
"pk": 6,
"fields": {
"name": "BDA"
}
},
{
"model": "note.templatecategory",
"pk": 7,
"fields": {
"name": "Autre"
}
},
{
"model": "note.templatecategory",
"pk": 8,
"fields": {
"name": "Alcool"
}
}
]

View File

@ -184,61 +184,5 @@
"normalized_name": "kfet",
"note": 6
}
},
{
"model": "note.templatecategory",
"pk": 1,
"fields": {
"name": "Soft"
}
},
{
"model": "note.templatecategory",
"pk": 2,
"fields": {
"name": "Pulls"
}
},
{
"model": "note.templatecategory",
"pk": 3,
"fields": {
"name": "Gala"
}
},
{
"model": "note.templatecategory",
"pk": 4,
"fields": {
"name": "Clubs"
}
},
{
"model": "note.templatecategory",
"pk": 5,
"fields": {
"name": "Bouffe"
}
},
{
"model": "note.templatecategory",
"pk": 6,
"fields": {
"name": "BDA"
}
},
{
"model": "note.templatecategory",
"pk": 7,
"fields": {
"name": "Autre"
}
},
{
"model": "note.templatecategory",
"pk": 8,
"fields": {
"name": "Alcool"
}
}
]

View File

@ -9,17 +9,6 @@ from .models import Alias
from .models import TransactionTemplate
class AliasForm(forms.ModelForm):
class Meta:
model = Alias
fields = ("name",)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["name"].label = False
self.fields["name"].widget.attrs = {"placeholder": _('New Alias')}
class ImageForm(forms.Form):
image = forms.ImageField(required=False,
label=_('select an image'),

View File

@ -228,7 +228,7 @@ class Alias(models.Model):
for cat in {'M', 'P', 'Z', 'C'})).casefold()
def clean(self):
normalized_name = Alias.normalize(self.name)
normalized_name = self.normalize(self.name)
if len(normalized_name) >= 255:
raise ValidationError(_('Alias is too long.'),
code='alias_too_long')
@ -242,8 +242,12 @@ class Alias(models.Model):
pass
self.normalized_name = normalized_name
def save(self,*args,**kwargs):
self.normalized_name = self.normalize(self.name)
super().save(*args,**kwargs)
def delete(self, using=None, keep_parents=False):
if self.name == str(self.note):
raise ValidationError(_("You can't delete your main alias."),
code="cant_delete_main_alias")
code="main_alias")
return super().delete(using, keep_parents)

View File

@ -99,7 +99,7 @@ class HistoryTable(tables.Table):
# function delete_button(id) provided in template file
DELETE_TEMPLATE = """
<button id="{{ record.pk }}" class="btn btn-danger" onclick="delete_button(this.id)"> {{ delete_trans }}</button>
<button id="{{ record.pk }}" class="btn btn-danger btn-sm" onclick="delete_button(this.id)"> {{ delete_trans }}</button>
"""
@ -107,7 +107,8 @@ class AliasTable(tables.Table):
class Meta:
attrs = {
'class':
'table table condensed table-striped table-hover'
'table table condensed table-striped table-hover',
'id':"alias_table"
}
model = Alias
fields = ('name',)
@ -115,15 +116,11 @@ class AliasTable(tables.Table):
show_header = False
name = tables.Column(attrs={'td': {'class': 'text-center'}})
# delete = tables.TemplateColumn(template_code=delete_template,
# attrs={'td':{'class': 'col-sm-1'}})
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')
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
extra_context={"delete_trans": _('delete')},
attrs={'td': {'class': 'col-sm-1'}})
class ButtonTable(tables.Table):
@ -143,11 +140,11 @@ class ButtonTable(tables.Table):
edit = tables.LinkColumn('note:template_update',
args=[A('pk')],
attrs={'td': {'class': 'col-sm-1'},
'a': {'class': 'btn btn-primary'}},
'a': {'class': 'btn btn-sm btn-primary'}},
text=_('edit'),
accessor='pk')
delete = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
extra_context={"delete_trans": _('delete')},
attrs={'td': {'class': 'col-sm-1'}})

37
static/js/alias.js Normal file
View File

@ -0,0 +1,37 @@
$("#alias_input").on('keypress',function(e) {
if(e.which == 13) {
$("#alias_submit").click();
}
});
function create_alias(note_id){
$.post("/api/note/alias/",
{
"csrfmiddlewaretoken": CSRF_TOKEN,
"name": $("#alias_input").val(),
"note": note_id
}
).done(function(){
$("#alias_table").load(location.href+ " #alias_table");
addMsg("Alias ajouté","success");
})
.fail(function(xhr, textStatus, error){
errMsg(xhr.responseJSON);
});
}
// on click of button "delete" , call the API
function delete_button(button_id){
$.ajax({
url:"/api/note/alias/"+button_id+"/",
method:"DELETE",
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
})
.done(function(){
addMsg('Alias supprimé','success');
$("#alias_table").load(location.href + " #alias_table");
})
.fail(function(xhr,textStatus, error){
errMsg(xhr.responseJSON);
});
}

View File

@ -28,7 +28,15 @@ function addMsg(msg, alert_type) {
+ msg + "</div>\n";
msgDiv.html(html);
}
/**
* add Muliple error message from err_obj
* @param err_obj {error_code:erro_message}
*/
function errMsg(errs_obj){
for (const err_msg of Object.values(errs_obj)) {
addMsg(err_msg,'danger');
}
}
/**
* Reload the balance of the user on the right top corner
*/

View File

@ -221,7 +221,7 @@ function consume(source, source_alias, dest, quantity, amount, reason, type, cat
addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", "danger");
}).fail(function () {
reset();
addMsg("Une erreur est survenue lors de la transaction : " + e.responseText, "danger");
errMsg(e.responseJSON);
});
});
}

View File

@ -79,9 +79,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="nav-link" href="{% url 'note:consos' %}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
</li>
{% endif %}
{% if "note.transaction"|not_empty_model_list %}
<li class="nav-item active">
<a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a>
</li>
{% endif %}
{% if "member.club"|not_empty_model_list %}
<li class="nav-item active">
<a class="nav-link" href="{% url 'member:club_list' %}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a>

View File

@ -1,7 +1,11 @@
{% extends "base.html" %}
{% extends "member/noteowner_detail.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
{% block profile_info %}
{% include "member/club_info.html" %}
{% endblock %}
{% block profile_content %}
<form method="post" action="">
{% csrf_token %}
@ -10,9 +14,9 @@
<input type="submit" name="submit" value="Add Members" class="btn btn-primary" id="submit-save">
</div>
</form>
{% endblock %}
<!-- Include formset plugin - including jQuery dependency -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
{% block extrajavascript %}
<script src="{% static 'js/dynamic-formset.js' %}"></script>
<script>
$('.formset-row').formset({

View File

@ -0,0 +1,12 @@
{% load django_tables2 crispy_forms_tags i18n %}
<div class="d-flex justify-content-center">
<input id="alias_input" type="text" value=""/>
<button id="alias_submit" class="btn btn-primary mx-2" onclick="create_alias( {{ object.note.pk }} )" type="submit">
{% trans "Add alias" %}
</button>
</div>
<div class="card bg-light shadow">
<div class="card-body">
{% render_table aliases %}
</div>
</div>

View File

@ -0,0 +1,10 @@
{% extends "member/club_detail.html" %}
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
{% block profile_content %}
{% include "member/alias_update.html" %}
{% endblock %}
{% block extrajavascript %}
<script src="/static/js/alias.js"></script>
{% endblock%}

View File

@ -1,5 +1,8 @@
{% load i18n static pretty_money %}
<div class="card bg-light shadow">
<div class="card-header text-center">
<h4> Club {{ club.name }} </h4>
</div>
<div class="card-top text-center">
<a href="{% url 'member:club_update_pic' club.pk %}">
<img src="{{ club.note.display_image.url }}" class="img-thumbnail mt-2" >
@ -10,6 +13,9 @@
<dt class="col-xl-6">{% trans 'name'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.name}}</dd>
<dt class="col-xl-6"><a href="{% url 'member:club_detail' club.parent_club.pk %}">{% trans 'Club Parent'|capfirst %}</a></dt>
<dd class="col-xl-6"> {{ club.parent_club.name}}</dd>
<dt class="col-xl-6">{% trans 'membership start'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.membership_start }}</dd>
@ -22,11 +28,19 @@
<dt class="col-xl-6">{% trans 'membership fee'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.membership_fee|pretty_money }}</dd>
<dt class="col-xl-6"><a href="{% url 'member:user_alias' club.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
<dt class="col-xl-6"><a href="{% url 'member:club_alias' club.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
<dd class="col-xl-6 text-truncate">{{ object.note.alias_set.all|join:", " }}</dd>
<dt class="col-xl-3">{% trans 'email'|capfirst %}</dt>
<dd class="col-xl-9"><a href="mailto:{{ club.email }}">{{ club.email}}</a></dd>
</dl>
</div>
<div class="card-footer text-center">
<a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_add_member' pk=club.pk %}"> {% trans "Add member" %}</a>
<a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_update' pk=club.pk %}"> {% trans "Edit" %}</a>
<a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_add_member' pk=club.pk %}"> {% trans "Add roles" %}</a>
{% url 'member:club_detail' club.pk as club_detail_url %}
{%if request.get_full_path != club_detail_url %}
<a class="btn btn-primary btn-sm my-1" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
{% endif %} </div>
</div>

View File

@ -1,10 +1,6 @@
{% extends "member/club_detail.html" %}
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
{% block profile_info %}
{% include "member/club_info.html" %}
{% endblock%}
{% block profile_content%}
{% include "member/picture_update.html" %}
{% endblock%}

View File

@ -2,18 +2,9 @@
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
{% block profile_content %}
<div class="d-flex justify-content-center">
<form class=" text-center form my-2" action="" method="post">
{% csrf_token %}
{{ form |crispy }}
<button class="btn btn-primary mx-2" type="submit">
{% trans "Add alias" %}
</button>
</form>
</div>
<div class="card bg-light shadow">
<div class="card-body">
{% render_table aliases %}
</div>
</div>
{% include "member/alias_update.html"%}
{% endblock %}
{% block extrajavascript %}
<script src="/static/js/alias.js"></script>
{% endblock%}

View File

@ -1,6 +1,9 @@
{% load i18n static pretty_money %}
<div class="card bg-light shadow">
<div class="card-header text-center" >
<h4> {% trans "Account #" %} {{ object.pk }}</h4>
</div>
<div class="card-top text-center">
<a href="{% url 'member:user_update_pic' object.pk %}">
<img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >

View File

@ -1,10 +1,6 @@
{% extends "member/noteowner_detail.html" %}
{% extends "member/profile_detail.html" %}
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
{% block profile_info %}
{% include "member/profile_info.html" %}
{% endblock%}
{% block profile_content%}
{% include "member/picture_update.html" %}
{% endblock%}

View File

@ -10,7 +10,7 @@
</h4>
<input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved();return(false);" id="search_field"/>
<hr>
<a class="btn btn-primary text-center my-4" href="{% url 'note:template_create' %}">Créer un bouton</a>
<a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}">{% trans "New button" %}</a>
</div>
</div>
<div class="row justify-content-center">