Commit 5a831136 authored by Benjamin Graillot's avatar Benjamin Graillot

Merge branch 'master' into rights

parents 67d1d9f7 9f11d530
Pipeline #1487 passed with stage
in 2 minutes and 53 seconds
This diff is collapsed.
......@@ -22,7 +22,7 @@ On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout n
$ cd note_kfet
$ git clone git@gitlab.crans.org:bde/nk20.git .
3. Environment Virtuel
À la racine du projet:
$ virtualenv env
......@@ -35,24 +35,45 @@ On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout n
On utilise uwsgi et Nginx pour gérer le coté serveu :
$ sudo ln -s /var/www/note_kfet/nginx_note.conf /etc/nginx/sites-enabled/
**Modifier la config nginx pour l'adapter à votre server!**
Si l'on a un emperor (plusieurs instance uwsgi):
$ sudo ln -s /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/sites/
Sinon:
Sinon:
$ sudo ln -s /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/apps-enabled/
5. Base de données
Pour le moment c'est du sqllite, pas de config particulière.
## Développer en local
Il est tout a fait possible de travailler en local, vive `./manage.py runserver` !
1. Cloner le dépot là ou vous voulez:
$ git@gitlab.crans.org:bde/nk20.git
2. Environnement Virtuel
$ virtualenv env
$ source /env/bin/activate
(env)$ pip install -r requirements.txt
3. Migrations:
(env)$ ./manage.py makemigrations
(env)$ ./manage.py migrate
4. Enjoy:
(env)$ ./manage.py runserver
## Cahier des Charges
Il est disponible [ici](https://wiki.crans.org/NoteKfet/NoteKfet2018/CdC).
......
......@@ -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",
)
)
......@@ -9,7 +9,7 @@ from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from django.urls import reverse, reverse_lazy
class Profile(models.Model):
......@@ -100,7 +100,7 @@ class Club(models.Model):
return self.name
def get_absolute_url(self):
return reverse('member:club_detail', args=(self.pk,))
return reverse_lazy('member:club_detail', args=(self.pk,))
class Role(models.Model):
......
#!/usr/bin/env python
import django_tables2 as tables
from .models import Club
class ClubTable(tables.Table):
class Meta:
attrs = {'class':'table table-bordered table-condensed table-striped table-hover'}
model = Club
template_name = 'django_tables2/bootstrap.html'
fields= ('id','name','email')
row_attrs = {'class':'table-row',
'data-href': lambda record: record.pk }
......@@ -13,6 +13,7 @@ urlpatterns = [
path('signup/',views.UserCreateView.as_view(),name="signup"),
path('club/',views.ClubListView.as_view(),name="club_list"),
path('club/<int:pk>/',views.ClubDetailView.as_view(),name="club_detail"),
path('club/<int:pk>/add_member/',views.ClubAddMemberView.as_view(),name="club_add_member"),
path('club/create/',views.ClubCreateView.as_view(),name="club_create"),
path('user/<int:pk>',views.UserDetailView.as_view(),name="user_detail")
]
......@@ -9,10 +9,16 @@ 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 django_tables2.views import SingleTableView
from .models import Profile, Club, Membership
from .forms import ProfileForm, ClubForm,MembershipForm, MemberFormSet,FormSetHelper
from .tables import ClubTable
from note.models.transactions import Transaction
from note.tables import HistoryTable
class UserCreateView(CreateView):
"""
Une vue pour inscrire un utilisateur et lui créer un profile
......@@ -24,7 +30,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
......@@ -39,9 +45,20 @@ class UserCreateView(CreateView):
return super().form_valid(form)
class UserDetailView(LoginRequiredMixin,DetailView):
model = Profile
context_object_name = "profile"
def get_context_data(slef,**kwargs):
context = super().get_context_data(**kwargs)
user = context['profile'].user
history_list = \
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))
context['history_list'] = HistoryTable(history_list)
club_list = \
Membership.objects.all().filter(user=user).only("club")
context['club_list'] = ClubTable(club_list)
return context
class ClubCreateView(LoginRequiredMixin,CreateView):
......@@ -54,14 +71,46 @@ class ClubCreateView(LoginRequiredMixin,CreateView):
def form_valid(self,form):
return super().form_valid(form)
class ClubListView(LoginRequiredMixin,ListView):
class ClubListView(LoginRequiredMixin,SingleTableView):
"""
List TransactionsTemplates
List existing tables
"""
model = Club
form_class = ClubForm
table_class = ClubTable
class ClubDetailView(LoginRequiredMixin,DetailView):
"""
"""
model = Club
context_object_name="club"
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
club = context["club"]
club_transactions = \
Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))
context['history_list'] = HistoryTable(club_transactions)
club_member = \
Membership.objects.all().filter(club=club)
# TODO: consider only valid Membership
context['member_list'] = club_member
return context
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)
......@@ -202,7 +202,7 @@ class Alias(models.Model):
for char in unicodedata.normalize('NFKD', string.casefold())
if all(not unicodedata.category(char).startswith(cat)
for cat in {'M', 'P', 'Z', 'C'})
)
).casefold()
def save(self, *args, **kwargs):
"""
......
......@@ -106,6 +106,10 @@ class Transaction(models.Model):
self.destination.save()
super().save(*args, **kwargs)
@property
def total(self):
return self.amount*self.quantity
class MembershipTransaction(Transaction):
membership = models.OneToOneField(
......
#!/usr/bin/env python
import django_tables2 as tables
from .models.transactions import Transaction
class HistoryTable(tables.Table):
class Meta:
attrs = {'class':'table table-bordered table-condensed table-striped table-hover'}
model = Transaction
template_name = 'django_tables2/bootstrap.html'
sequence = ('...','total','valid')
total = tables.Column() #will use Transaction.total() !!
def order_total(self, QuerySet, is_descending):
# needed for rendering
QuerySet = QuerySet.annotate(
total=F('amount') * F('quantity')
).order_by(('-' if is_descending else '') + 'total')
return (QuerySet, True)
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)
This diff is collapsed.
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<form method="post" action="">
{% csrf_token %}
{% crispy formset helper %}
<div class="form-actions">
<input type="submit" name="submit" value="Add Members" class="btn btn-primary" id="submit-save">
</div>
</form>
<!-- Include formset plugin - including jQuery dependency -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'js/dynamic-formset.js' %}"></script>
<script>
$('.formset-row').formset({
addText: 'add another', // Text for the add link
deleteText: 'remove', // Text for the delete link
addCssClass: 'btn btn-primary', // CSS class applied to the add link
deleteCssClass: 'btn btn-danger h-50 my-auto',
});
</script>
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% load pretty_money %}
{% block content %}
<p><a class="btn btn-default" href="{% url 'member:club_list' %}">Clubs</a></p>
<h5>{{ object.name }}</h5>
{% endblock %}
<p><a class="btn btn-primary" href="{% url 'member:club_list' %}">Clubs</a></p>
<h3 class="text-center"> Club {{ object.name }}</h3>
<dl>
<dt>{% trans 'Membership starts on' %}</dt>
<dd>{{ club.membership_start }}</dd>
<dt>{% trans 'Membership ends on' %}</dt>
<dd>{{ club.membership_end }}</dd>
<dt>{% trans 'Membership duration' %}</dt>
<dd>{{ club.membership_duration }}</dd>
<dt> Aliases </dt>
<dd>{{ club.note.aliases_set.all }}</dd>
<dt>{% trans 'balance' %}</dt>
<dd>{{ club.note.balance | pretty_money }}</dd>
</dl>
<div class="btn-group" role="group">
<a class="btn btn-primary" href="{% url 'member:club_add_member' pk=object.pk %}"> Ajouter des membres </a>
<a class="btn btn-primary" href="{% url 'member:club_add_member' pk=object.pk %}"> Modifier les informations </a>
<a class="btn btn-primary" href="{% url 'member:club_add_member' pk=object.pk %}"> Ajouter des roles </a>
</div>
<div class="accordion" id="accordionExample">
<div class="card">
<div class="card-header" id="headingOne">
<h5 class="mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
<i class="fa fa-users"></i> Membres du club
</button>
</h5>
</div>
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
<div class="card-body">
{% render_table member_list %}
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingTwo">
<h5 class="mb-0">
<button class="btn btn-link collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
<i class="fa fa-euro"></i> Historique des transactions
</button>
</h5>
</div>
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
<div class="card-body">
{% render_table history_list %}
</div>
</div>
</div>
</div>
{% endblock %}
......@@ -2,7 +2,19 @@
{% load render_table from django_tables2 %}
{% block content %}
{% render_table object_list %}
{% render_table table %}
<a class="btn btn-primary" href="{% url 'member:club_create' %}">New Club</a>
{% endblock %}
{% block extrajavascript %}
<script type="text/javascript">
$(document).ready(function($) {
$(".table-row").click(function() {
window.document.location = $(this).data("href");
});
});
</script>
{% endblock %}
{% extends "base.html" %}
{% load i18n static %}
{% load i18n static pretty_money django_tables2 %}
{% block content %}
<h3>Compte n° {{ object.pk }}</h3>
......@@ -20,8 +20,44 @@
<dt class="col-6 col-md-3">{% trans 'address'|capfirst %}</dt>
<dd class="col-6 col-md-3">{{ object.address }}</dd>
<dt class="col-6 col-md-3">{% trans 'balance'|capfirst %}</dt>
<dd class="col-6 col-md-3">{{ object.user.note.balance }}</dd>
<dd class="col-6 col-md-3">{{ object.user.note.balance | pretty_money }}</dd>
</dl>
<a href="{% url "password_change" %}">{% trans 'Change password' %}</a>
{% endblock %}
<a class="btn btn-primary" href="{% url 'password_change' %}">{% trans 'Change password' %}</a>
<div class="accordion" id="accordionExample">
<div class="card">
<div class="card-header" id="headingOne">
<h5 class="mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
<i class="fa fa-users"></i> {% trans "View my memberships" %}
</button>
</h5>
</div>
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
<div class="card-body">
{% render_table club_list %}
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingTwo">
<h5 class="mb-0">
<button class="btn btn-link collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
<i class="fa fa-euro"></i> Historique des transactions
</button>
</h5>
</div>
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
<div class="card-body">
{% render_table history_list %}
</div>
</div>
</div>
</div>
{% endblock %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment