From a456468a71813ef72e12d54a2bb050b1e639d5d3 Mon Sep 17 00:00:00 2001 From: PA Date: Wed, 14 Aug 2019 16:02:43 +0200 Subject: [PATCH 01/14] Pretty print feature Prints the money of each user perfectly --- apps/note/templatetags/__init__.py | 0 apps/note/templatetags/pretty_money.py | 12 ++++++++++++ templates/member/profile_detail.html | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 apps/note/templatetags/__init__.py create mode 100644 apps/note/templatetags/pretty_money.py diff --git a/apps/note/templatetags/__init__.py b/apps/note/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/note/templatetags/pretty_money.py b/apps/note/templatetags/pretty_money.py new file mode 100644 index 00000000..5b7acd12 --- /dev/null +++ b/apps/note/templatetags/pretty_money.py @@ -0,0 +1,12 @@ +from django import template + + +def pretty_money(value): + if value%100 == 0: + return str(value//100) + '€' + else: + return str(value//100) + '€ ' + str(value%100) + + +register = template.Library() +register.filter('pretty_money', pretty_money) diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index ea2f0f07..11f50f65 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load i18n static %} +{% load i18n static pretty_money %} {% block content %}

Compte n° {{ object.pk }}

@@ -20,7 +20,7 @@
{% trans 'address'|capfirst %}
{{ object.address }}
{% trans 'balance'|capfirst %}
-
{{ object.user.note.balance }}
+
{{ object.user.note.balance | pretty_money }}
{% trans 'Change password' %} From 66b70e69f383844ee15c7e9aaf0b55922ed52adf Mon Sep 17 00:00:00 2001 From: PA Date: Wed, 14 Aug 2019 16:42:05 +0200 Subject: [PATCH 02/14] Add transaction history list on the user --- apps/member/views.py | 11 ++++++++++- templates/member/profile_detail.html | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 37c47212..d664df70 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -9,9 +9,11 @@ from django.views.generic import CreateView, ListView, DetailView from django.http import HttpResponseRedirect from django.contrib.auth.forms import UserCreationForm from django.urls import reverse_lazy +from django.db.models import Q from .models import Profile, Club from .forms import ProfileForm, ClubForm +from note.models.transactions import Transaction class UserCreateView(CreateView): """ @@ -39,9 +41,16 @@ class UserCreateView(CreateView): return super().form_valid(form) - class UserDetailView(LoginRequiredMixin,DetailView): model = Profile + + def get_context_data(slef,**kwargs): + context = super().get_context_data(**kwargs) + user = context['object'].user.note + user_transactions = \ + Transaction.objects.all().filter(Q(source=user) | Q(destination=user)) + context['history_list'] = user_transactions + return context class ClubCreateView(LoginRequiredMixin,CreateView): diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 11f50f65..82be255a 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load i18n static pretty_money %} +{% load i18n static pretty_money django_tables2 %} {% block content %}

Compte n° {{ object.pk }}

@@ -24,4 +24,6 @@ {% trans 'Change password' %} + + {% render_table history_list %} {% endblock %} From 40c697e57ffca8a845944c9e22ab6631c44803f3 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Wed, 14 Aug 2019 18:47:46 +0200 Subject: [PATCH 03/14] =?UTF-8?q?interface=20pour=20ajouter=20des=20membre?= =?UTF-8?q?s=20=C3=A0=20un=20club?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/member/forms.py | 40 +++++- apps/member/urls.py | 1 + apps/member/views.py | 29 +++- static/js/dynamic-formset.js | 231 ++++++++++++++++++++++++++++++ templates/member/add_members.html | 23 +++ templates/member/club_detail.html | 2 + 6 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 static/js/dynamic-formset.js create mode 100644 templates/member/add_members.html diff --git a/apps/member/forms.py b/apps/member/forms.py index e024ee84..5dabef24 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -6,7 +6,15 @@ from django.contrib.auth.forms import UserChangeForm, UserCreationForm from django.contrib.auth.models import User from django import forms -from .models import Profile, Club +from .models import Profile, Club, Membership + +from django.utils.translation import gettext_lazy as _ + +from crispy_forms.helper import FormHelper +from crispy_forms import layout, bootstrap +from crispy_forms.bootstrap import InlineField, FormActions, StrictButton, Div, Field +from crispy_forms.layout import Layout + class ProfileForm(forms.ModelForm): """ @@ -21,3 +29,33 @@ class ClubForm(forms.ModelForm): class Meta: model = Club fields ='__all__' + +class AddMembersForm(forms.Form): + class Meta: + fields = ('',) + +class MembershipForm(forms.ModelForm): + class Meta: + model = Membership + fields = ('user','roles','date_start') + +MemberFormSet = forms.modelformset_factory(Membership, + form=MembershipForm, + extra=2, + can_delete=True) + +class FormSetHelper(FormHelper): + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + self.form_tag = False + self.form_method = 'POST' + self.form_class='form-inline' + # self.template = 'bootstrap/table_inline_formset.html' + self.layout = Layout( + Div( + Div('user',css_class='col-sm-2'), + Div('roles',css_class='col-sm-2'), + Div('date_start',css_class='col-sm-2'), + css_class="row formset-row", + ) + ) diff --git a/apps/member/urls.py b/apps/member/urls.py index 7b179b56..39d3d896 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ path('signup/',views.UserCreateView.as_view(),name="signup"), path('club/',views.ClubListView.as_view(),name="club_list"), path('club//',views.ClubDetailView.as_view(),name="club_detail"), + path('club//add_member/',views.ClubAddMemberView.as_view(),name="club_add_member"), path('club/create/',views.ClubCreateView.as_view(),name="club_create"), path('user/',views.UserDetailView.as_view(),name="user_detail") ] diff --git a/apps/member/views.py b/apps/member/views.py index 37c47212..6408dc5f 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -10,8 +10,8 @@ from django.http import HttpResponseRedirect from django.contrib.auth.forms import UserCreationForm from django.urls import reverse_lazy -from .models import Profile, Club -from .forms import ProfileForm, ClubForm +from .models import Profile, Club, Membership +from .forms import ProfileForm, ClubForm,MembershipForm, MemberFormSet,FormSetHelper class UserCreateView(CreateView): """ @@ -24,7 +24,7 @@ class UserCreateView(CreateView): second_form = UserCreationForm def get_context_data(self,**kwargs): - context = super(SignUp,self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context["user_form"] = self.second_form return context @@ -62,6 +62,25 @@ class ClubListView(LoginRequiredMixin,ListView): form_class = ClubForm class ClubDetailView(LoginRequiredMixin,DetailView): - """ - """ model = Club + +class ClubAddMemberView(LoginRequiredMixin,CreateView): + model = Membership + form_class = MembershipForm + template_name = 'member/add_members.html' + def get_context_data(self,**kwargs): + context = super().get_context_data(**kwargs) + context['formset'] = MemberFormSet() + context['helper'] = FormSetHelper() + return context + + def post(self,request,*args,**kwargs): + formset = MembershipFormset(request.POST) + if formset.is_valid(): + return self.form_valid(formset) + else: + return self.form_invalid(formset) + + def form_valid(self,formset): + formset.save() + return super().form_valid(formset) diff --git a/static/js/dynamic-formset.js b/static/js/dynamic-formset.js new file mode 100644 index 00000000..95406a8c --- /dev/null +++ b/static/js/dynamic-formset.js @@ -0,0 +1,231 @@ +/** + * jQuery Formset 1.3-pre + * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) + * @requires jQuery 1.2.6 or later + * + * Copyright (c) 2009, Stanislaus Madueke + * All rights reserved. + * + * Licensed under the New BSD License + * See: http://www.opensource.org/licenses/bsd-license.php + */ +;(function($) { + $.fn.formset = function(opts) + { + var options = $.extend({}, $.fn.formset.defaults, opts), + flatExtraClasses = options.extraClasses.join(' '), + totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'), + maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'), + minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'), + childElementSelector = 'input,select,textarea,label,div', + $$ = $(this), + + applyExtraClasses = function(row, ndx) { + if (options.extraClasses) { + row.removeClass(flatExtraClasses); + row.addClass(options.extraClasses[ndx % options.extraClasses.length]); + } + }, + + updateElementIndex = function(elem, prefix, ndx) { + var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'), + replacement = prefix + '-' + ndx + '-'; + if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement)); + if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement)); + if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement)); + }, + + hasChildElements = function(row) { + return row.find(childElementSelector).length > 0; + }, + + showAddButton = function() { + return maxForms.length == 0 || // For Django versions pre 1.2 + (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0)); + }, + + /** + * Indicates whether delete link(s) can be displayed - when total forms > min forms + */ + showDeleteLinks = function() { + return minForms.length == 0 || // For Django versions pre 1.7 + (minForms.val() == '' || (totalForms.val() - minForms.val() > 0)); + }, + + insertDeleteLink = function(row) { + var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'), + addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.'); + if (row.is('TR')) { + // If the forms are laid out in table rows, insert + // the remove button into the last table cell: + row.children(':last').append('' + options.deleteText + ''); + } else if (row.is('UL') || row.is('OL')) { + // If they're laid out as an ordered/unordered list, + // insert an
  • after the last list item: + row.append('
  • ' + options.deleteText +'
  • '); + } else { + // Otherwise, just insert the remove button as the + // last child element of the form's container: + row.append('' + options.deleteText +''); + } + // Check if we're under the minimum number of forms - not to display delete link at rendering + if (!showDeleteLinks()){ + row.find('a.' + delCssSelector).hide(); + } + + row.find('a.' + delCssSelector).click(function() { + var row = $(this).parents('.' + options.formCssClass), + del = row.find('input:hidden[id $= "-DELETE"]'), + buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'), + forms; + if (del.length) { + // We're dealing with an inline formset. + // Rather than remove this form from the DOM, we'll mark it as deleted + // and hide it, then let Django handle the deleting: + del.val('on'); + row.hide(); + forms = $('.' + options.formCssClass).not(':hidden'); + } else { + row.remove(); + // Update the TOTAL_FORMS count: + forms = $('.' + options.formCssClass).not('.formset-custom-template'); + totalForms.val(forms.length); + } + for (var i=0, formCount=forms.length; i