diff --git a/.gitignore b/.gitignore index 96622d0f..3828edd8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -settings.py -settings* +settings_local.py *.swp *.pyc +__pycache__ diff --git a/cotisations/admin.py b/cotisations/admin.py index 03a7efbe..40b04591 100644 --- a/cotisations/admin.py +++ b/cotisations/admin.py @@ -3,10 +3,10 @@ from django.contrib import admin from .models import Facture, Article, Banque, Paiement, Cotisation class FactureAdmin(admin.ModelAdmin): - list_display = ('user','paiement','name', 'number', 'date') + list_display = ('user','paiement','name', 'number','prix', 'date','valid') class ArticleAdmin(admin.ModelAdmin): - list_display = ('name','prix','cotisation') + list_display = ('name','prix','cotisation','duration') class BanqueAdmin(admin.ModelAdmin): list_display = ('name',) diff --git a/cotisations/forms.py b/cotisations/forms.py new file mode 100644 index 00000000..d2a1e69e --- /dev/null +++ b/cotisations/forms.py @@ -0,0 +1,88 @@ +from django import forms +from django.forms import ModelForm +from .models import Article, Paiement, Facture, Banque + +class NewFactureForm(ModelForm): + article = forms.ModelMultipleChoiceField(queryset=Article.objects.all(), label="Article") + + def __init__(self, *args, **kwargs): + super(NewFactureForm, self).__init__(*args, **kwargs) + self.fields['number'].label = 'Quantité' + self.fields['cheque'].required = False + self.fields['banque'].required = False + self.fields['cheque'].label = 'Numero de chèque' + self.fields['banque'].empty_label = "Non renseigné" + self.fields['paiement'].empty_label = "Séléctionner un moyen de paiement" + + class Meta: + model = Facture + fields = ['paiement','banque','cheque','number'] + + def clean(self): + cleaned_data=super(NewFactureForm, self).clean() + paiement = cleaned_data.get("paiement") + cheque = cleaned_data.get("cheque") + banque = cleaned_data.get("banque") + if paiement.moyen=="chèque" and not (cheque and banque): + raise forms.ValidationError("Le numero de chèque et la banque sont obligatoires") + return cleaned_data + +class EditFactureForm(NewFactureForm): + class Meta(NewFactureForm.Meta): + fields = '__all__' + + def __init__(self, *args, **kwargs): + super(EditFactureForm, self).__init__(*args, **kwargs) + self.fields['user'].label = 'Adherent' + self.fields['name'].label = 'Designation' + self.fields['prix'].label = 'Prix unitaire' + self.fields['user'].empty_label = "Séléctionner l'adhérent propriétaire" + self.fields.pop('article') + +class ArticleForm(ModelForm): + class Meta: + model = Article + fields = '__all__' + + def __init__(self, *args, **kwargs): + super(ArticleForm, self).__init__(*args, **kwargs) + self.fields['name'].label = "Désignation de l'article" + +class DelArticleForm(ModelForm): + articles = forms.ModelMultipleChoiceField(queryset=Article.objects.all(), label="Articles actuels", widget=forms.CheckboxSelectMultiple) + + class Meta: + fields = ['articles'] + model = Article + +class PaiementForm(ModelForm): + class Meta: + model = Paiement + fields = ['moyen'] + + def __init__(self, *args, **kwargs): + super(PaiementForm, self).__init__(*args, **kwargs) + self.fields['moyen'].label = 'Moyen de paiement à ajouter' + +class DelPaiementForm(ModelForm): + paiements = forms.ModelMultipleChoiceField(queryset=Paiement.objects.all(), label="Moyens de paiement actuels", widget=forms.CheckboxSelectMultiple) + + class Meta: + exclude = ['moyen'] + model = Paiement + +class BanqueForm(ModelForm): + class Meta: + model = Banque + fields = ['name'] + + def __init__(self, *args, **kwargs): + super(BanqueForm, self).__init__(*args, **kwargs) + self.fields['name'].label = 'Banque à ajouter' + +class DelBanqueForm(ModelForm): + banques = forms.ModelMultipleChoiceField(queryset=Banque.objects.all(), label="Banques actuelles", widget=forms.CheckboxSelectMultiple) + + class Meta: + exclude = ['name'] + model = Banque diff --git a/cotisations/models.py b/cotisations/models.py index 60c6cc33..db064d7f 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -1,8 +1,5 @@ from django.db import models -from django import forms -from django.forms import ModelForm -from users.models import User class Facture(models.Model): user = models.ForeignKey('users.User', on_delete=models.PROTECT) @@ -47,31 +44,3 @@ class Cotisation(models.Model): def __str__(self): return str(self.facture) -class NewFactureForm(ModelForm): - article = forms.ModelMultipleChoiceField(queryset=Article.objects.all(), label="Article") - - def __init__(self, *args, **kwargs): - super(NewFactureForm, self).__init__(*args, **kwargs) - self.fields['number'].label = 'Quantité' - self.fields['cheque'].required = False - self.fields['banque'].required = False - self.fields['cheque'].label = 'Numero de chèque' - - class Meta: - model = Facture - exclude = ['user', 'prix', 'name', 'valid'] - -class EditFactureForm(ModelForm): - def __init__(self, *args, **kwargs): - super(EditFactureForm, self).__init__(*args, **kwargs) - self.fields['user'].label = 'Adherent' - self.fields['number'].label = 'Quantité' - self.fields['cheque'].required = False - self.fields['banque'].required = False - self.fields['cheque'].label = 'Numero de chèque' - self.fields['name'].label = 'Designation' - self.fields['prix'].label = 'Prix unitaire' - - class Meta: - model = Facture - fields = '__all__' diff --git a/cotisations/templates/cotisations/aff_cotisations.html b/cotisations/templates/cotisations/aff_cotisations.html index cfb7f25c..c1a88139 100644 --- a/cotisations/templates/cotisations/aff_cotisations.html +++ b/cotisations/templates/cotisations/aff_cotisations.html @@ -18,7 +18,7 @@ {{ facture.prix }} {{ facture.paiement }} {{ facture.date }} - Editer + Editer {% endfor %} diff --git a/cotisations/templates/cotisations/sidebar.html b/cotisations/templates/cotisations/sidebar.html index 8894fcbb..be622d09 100644 --- a/cotisations/templates/cotisations/sidebar.html +++ b/cotisations/templates/cotisations/sidebar.html @@ -1,7 +1,11 @@ {% extends "base.html" %} {% block sidebar %} -

Créer une facture

-

Editer une facture

Liste des factures

+

Ajouter un article

+

Retirer un article

+

Ajouter un moyen de paiement

+

Retirer un moyen de paiement

+

Ajouter une banque

+

Retirer une banque

{% endblock %} diff --git a/cotisations/urls.py b/cotisations/urls.py index 3c0bbdf7..fca9c331 100644 --- a/cotisations/urls.py +++ b/cotisations/urls.py @@ -5,6 +5,12 @@ from . import views urlpatterns = [ url(r'^new_facture/(?P[0-9]+)$', views.new_facture, name='new-facture'), url(r'^edit_facture/(?P[0-9]+)$', views.edit_facture, name='edit-facture'), + url(r'^add_article/$', views.add_article, name='add-article'), + url(r'^del_article/$', views.del_article, name='del-article'), + url(r'^add_paiement/$', views.add_paiement, name='add-paiement'), + url(r'^del_paiement/$', views.del_paiement, name='del-paiement'), + url(r'^add_banque/$', views.add_banque, name='add-banque'), + url(r'^del_banque/$', views.del_banque, name='del-banque'), url(r'^$', views.index, name='index'), ] diff --git a/cotisations/views.py b/cotisations/views.py index 571be4a8..fa72a46e 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -6,9 +6,10 @@ from django.shortcuts import render_to_response, get_object_or_404 from django.core.context_processors import csrf from django.template import Context, RequestContext, loader from django.contrib import messages -from django.db.models import Max +from django.db.models import Max, ProtectedError -from cotisations.models import NewFactureForm, EditFactureForm, Facture, Article, Cotisation +from .models import Facture, Article, Cotisation, Article +from .forms import NewFactureForm, EditFactureForm, ArticleForm, DelArticleForm, PaiementForm, DelPaiementForm, BanqueForm, DelBanqueForm from users.models import User from dateutil.relativedelta import relativedelta @@ -34,14 +35,14 @@ def is_adherent(user): else: return True -def create_cotis(facture, user, article): +def create_cotis(facture, user, duration): """ Update et crée l'objet cotisation associé à une facture, prend en argument l'user, la facture pour la quantitéi, et l'article pour la durée""" cotisation=Cotisation(facture=facture) date_max = end_adhesion(user) or timezone.now() if date_max < timezone.now(): datemax = timezone.now() cotisation.date_start=date_max - cotisation.date_end = cotisation.date_start + relativedelta(months=article[0].duration*facture.number) + cotisation.date_end = cotisation.date_start + relativedelta(months=duration) cotisation.save() return @@ -56,11 +57,12 @@ def new_facture(request, userid): if facture_form.is_valid(): new_facture = facture_form.save(commit=False) article = facture_form.cleaned_data['article'] - new_facture.prix = article[0].prix - new_facture.name = article[0].name + new_facture.prix = sum(art.prix for art in article) + new_facture.name = ' - '.join(art.name for art in article) new_facture.save() - if article[0].cotisation == True: - create_cotis(new_facture, user, article) + if any(art.cotisation for art in article): + duration = sum(art.duration*facture.number for art in article if art.cotisation) + create_cotis(new_facture, user, duration) messages.success(request, "La cotisation a été prolongée pour l'adhérent %s " % user.name ) else: messages.success(request, "La facture a été crée") @@ -80,6 +82,65 @@ def edit_facture(request, factureid): return redirect("/cotisations/") return form({'factureform': facture_form}, 'cotisations/facture.html', request) +def add_article(request): + article = ArticleForm(request.POST or None) + if article.is_valid(): + article.save() + messages.success(request, "L'article a été ajouté") + return redirect("/cotisations/") + return form({'factureform': article}, 'cotisations/facture.html', request) + +def del_article(request): + article = DelArticleForm(request.POST or None) + if article.is_valid(): + article_del = article.cleaned_data['articles'] + article_del.delete() + messages.success(request, "Le/les articles ont été supprimé") + return redirect("/cotisations/") + return form({'factureform': article}, 'cotisations/facture.html', request) + +def add_paiement(request): + paiement = PaiementForm(request.POST or None) + if paiement.is_valid(): + paiement.save() + messages.success(request, "Le moyen de paiement a été ajouté") + return redirect("/cotisations/") + return form({'factureform': paiement}, 'cotisations/facture.html', request) + +def del_paiement(request): + paiement = DelPaiementForm(request.POST or None) + if paiement.is_valid(): + paiement_dels = paiement.cleaned_data['paiements'] + for paiement_del in paiement_dels: + try: + paiement_del.delete() + messages.success(request, "Le moyen de paiement a été supprimé") + except ProtectedError: + messages.error(request, "Le moyen de paiement %s est affecté à au moins une facture, vous ne pouvez pas le supprimer" % paiement_del) + return redirect("/cotisations/") + return form({'factureform': paiement}, 'cotisations/facture.html', request) + +def add_banque(request): + banque = BanqueForm(request.POST or None) + if banque.is_valid(): + banque.save() + messages.success(request, "La banque a été ajoutée") + return redirect("/cotisations/") + return form({'factureform': banque}, 'cotisations/facture.html', request) + +def del_banque(request): + banque = DelBanqueForm(request.POST or None) + if banque.is_valid(): + banque_dels = banque.cleaned_data['banques'] + for banque_del in banque_dels: + try: + banque_del.delete() + messages.success(request, "La banque a été supprimée") + except ProtectedError: + messages.error(request, "La banque %s est affectée à au moins une facture, vous ne pouvez pas la supprimer" % banque_del) + return redirect("/cotisations/") + return form({'factureform': banque}, 'cotisations/facture.html', request) + def index(request): - facture_list = Facture.objects.order_by('pk') + facture_list = Facture.objects.order_by('date').reverse() return render(request, 'cotisations/index.html', {'facture_list': facture_list}) diff --git a/machines/admin.py b/machines/admin.py index 5931c99d..0122d746 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -3,7 +3,7 @@ from django.contrib import admin from .models import Machine, MachineType, IpList, Interface class MachineAdmin(admin.ModelAdmin): - list_display = ('user','name','type') + list_display = ('user','name','type','active') class MachineTypeAdmin(admin.ModelAdmin): list_display = ('type',) diff --git a/machines/migrations/0013_auto_20160705_1014.py b/machines/migrations/0013_auto_20160705_1014.py new file mode 100644 index 00000000..68b67758 --- /dev/null +++ b/machines/migrations/0013_auto_20160705_1014.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0012_auto_20160704_0118'), + ] + + operations = [ + migrations.AddField( + model_name='machine', + name='active', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='interface', + name='dns', + field=models.CharField(max_length=255, unique=True, help_text='Obligatoire et unique, doit se terminer en .rez et ne pas comporter de points'), + ), + ] diff --git a/machines/migrations/0014_auto_20160706_1220.py b/machines/migrations/0014_auto_20160706_1220.py new file mode 100644 index 00000000..c7d7bf04 --- /dev/null +++ b/machines/migrations/0014_auto_20160706_1220.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import machines.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0013_auto_20160705_1014'), + ] + + operations = [ + migrations.AlterField( + model_name='interface', + name='dns', + field=models.CharField(unique=True, validators=[machines.models.full_domain_validator], max_length=255, help_text="Obligatoire et unique, doit se terminer en .rez et ne pas comporter d'autres points"), + ), + ] diff --git a/machines/models.py b/machines/models.py index 1cf9c557..da9289ab 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1,13 +1,38 @@ from django.db import models -from django.forms import ModelForm, Form +from django.forms import ModelForm, Form, ValidationError from macaddress.fields import MACAddressField -from users.models import User +from django.conf import settings +import re + +def full_domain_validator(hostname): + """ Validation du nom de domaine, extensions dans settings, prefixe pas plus long que 63 caractères """ + HOSTNAME_LABEL_PATTERN = re.compile("(?!-)[A-Z\d-]+(? 63: + raise ValidationError( + ", le nom de domaine '%(label)s' est trop long (maximum de 63 caractères).", + params={'label': hostname}, + ) + if not HOSTNAME_LABEL_PATTERN.match(hostname): + raise ValidationError( + ", ce nom de domaine '%(label)s' contient des carractères interdits.", + params={'label': hostname}, + ) class Machine(models.Model): user = models.ForeignKey('users.User', on_delete=models.PROTECT) type = models.ForeignKey('MachineType', on_delete=models.PROTECT) name = models.CharField(max_length=255, help_text="Optionnel", blank=True, null=True) + active = models.BooleanField(default=True) def __str__(self): return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name) @@ -25,11 +50,14 @@ class Interface(models.Model): mac_address = MACAddressField(integer=False, unique=True) machine = models.ForeignKey('Machine', on_delete=models.PROTECT) details = models.CharField(max_length=255, blank=True) - dns = models.CharField(help_text="Obligatoire et unique", max_length=255, unique=True) + dns = models.CharField(help_text="Obligatoire et unique, doit se terminer en %s et ne pas comporter d'autres points" % ", ".join(settings.ALLOWED_EXTENSIONS), max_length=255, unique=True, validators=[full_domain_validator]) def __str__(self): return self.dns + def clean(self): + self.dns=self.dns.lower() + class IpList(models.Model): ipv4 = models.GenericIPAddressField(protocol='IPv4', unique=True) @@ -45,6 +73,7 @@ class EditMachineForm(ModelForm): super(EditMachineForm, self).__init__(*args, **kwargs) self.fields['name'].label = 'Nom de la machine' self.fields['type'].label = 'Type de machine' + self.fields['type'].empty_label = "Séléctionner un type de machine" class NewMachineForm(EditMachineForm): class Meta(EditMachineForm.Meta): diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html index 8840a7b7..612c1366 100644 --- a/machines/templates/machines/aff_machines.html +++ b/machines/templates/machines/aff_machines.html @@ -16,7 +16,7 @@ {{ machine.machine.type }} {{ machine.mac_address }} {{ machine.ipv4 }} - Editer + Editer {% endfor %} diff --git a/machines/templates/machines/sidebar.html b/machines/templates/machines/sidebar.html index a7464bb5..2d6342fc 100644 --- a/machines/templates/machines/sidebar.html +++ b/machines/templates/machines/sidebar.html @@ -1,5 +1,4 @@ {% extends "base.html" %} {% block sidebar %} -

Nouvelle machine

{% endblock %} diff --git a/re2o/settings.py b/re2o/settings.py new file mode 100644 index 00000000..51604a9d --- /dev/null +++ b/re2o/settings.py @@ -0,0 +1,108 @@ +""" +Django settings for re2o project. + +Generated by 'django-admin startproject' using Django 1.8.13. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os +from .settings_local import SECRET_KEY, DATABASES, DEBUG, ALLOWED_HOSTS, ALLOWED_EXTENSIONS + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'bootstrap3', + 'users', + 'machines', + 'cotisations', + 'topologie', + 'search', + 'logs', +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', +) + +ROOT_URLCONF = 're2o.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + os.path.join(BASE_DIR, 'templates').replace('\\', '/'), + ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 're2o.wsgi.application' + + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ + +LANGUAGE_CODE = 'fr-fr' + +TIME_ZONE = 'Europe/Paris' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# django-bootstrap3 config dictionnary +BOOTSTRAP3 = { + 'jquery_url': '/static/js/jquery-2.2.4.min.js', + 'base_url': '/static/bootstrap/', + 'include_jquery': True, + } + +BOOTSTRAP_BASE_URL = '/static/bootstrap/' + +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + os.path.join( + BASE_DIR, + 'static', + ), +) + +STATIC_URL = '/static/' diff --git a/re2o/settings_local.example.py b/re2o/settings_local.example.py new file mode 100644 index 00000000..3da70fb2 --- /dev/null +++ b/re2o/settings_local.example.py @@ -0,0 +1,20 @@ +SECRET_KEY = 'SUPER_SECRET' + +DB_PASSWORD = 'SUPER_SECRET' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +ALLOWED_HOSTS = [] + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 're2o', + 'USER': 're2o', + 'PASSWORD': DB_PASSWORD, + 'HOST': 'localhost', + } +} + +ALLOWED_EXTENSIONS = ['.example'] diff --git a/re2o/urls.py b/re2o/urls.py index 96ae98be..fe6776d8 100644 --- a/re2o/urls.py +++ b/re2o/urls.py @@ -24,5 +24,6 @@ urlpatterns = [ url(r'^search/', include('search.urls', namespace='search')), url(r'^cotisations/', include('cotisations.urls', namespace='cotisations')), url(r'^machines/', include('machines.urls', namespace='machines')), + url(r'^topologie/', include('topologie.urls', namespace='topologie')), #url(r'^logs/', include('logs.urls', namespace='logs')), ] diff --git a/search/models.py b/search/models.py index 6e3c14d3..60b776fe 100644 --- a/search/models.py +++ b/search/models.py @@ -3,9 +3,6 @@ from django import forms from django.forms import Form from django.forms import ModelForm -from users.models import User -# Create your models here. - CHOICES = ( ('0', 'Actifs'), ('1', 'Désactivés'), diff --git a/search/views.py b/search/views.py index b608885a..28830313 100644 --- a/search/views.py +++ b/search/views.py @@ -12,6 +12,7 @@ from machines.models import Machine, Interface from cotisations.models import Facture from search.models import SearchForm, SearchFormPlus from users.views import has_access +from cotisations.views import end_adhesion def form(ctx, template, request): c = ctx @@ -23,7 +24,7 @@ def search_result(search, type): date_fin = None states=[] co=[] - aff=[0,1,2,3,4] + aff=['0','1','2','3','4'] if(type): aff = search.cleaned_data['affichage'] co = search.cleaned_data['connexion'] @@ -32,7 +33,7 @@ def search_result(search, type): date_fin = search.cleaned_data['date_fin'] date_query = Q() if aff==[]: - aff = [0,1,2,3,4] + aff = ['0','1','2','3','4'] if date_deb != None: date_query = date_query & Q(date__gte=date_deb) if date_fin != None: @@ -54,9 +55,13 @@ def search_result(search, type): users = User.objects.filter((Q(pseudo__icontains = search) | Q(name__icontains = search) | Q(surname__icontains = search)) & query) connexion = [] for user in users: + end=end_adhesion(user) access=has_access(user) if(len(co)==0 or (len(co)==1 and bool(co[0])==access) or (len(co)==2 and (bool(co[0])==access or bool(co[1])==access))): - connexion.append([user, access]) + if(end!=None): + connexion.append([user, access, end]) + else: + connexion.append([user, access, "Non adhérent"]) query = Q(user__pseudo__icontains = search) | Q(user__name__icontains = search) | Q(user__surname__icontains = search) if i == '1': machines = Interface.objects.filter(machine=Machine.objects.filter(query)) | Interface.objects.filter(Q(dns__icontains = search)) diff --git a/templates/base.html b/templates/base.html index 34079495..544c0952 100644 --- a/templates/base.html +++ b/templates/base.html @@ -30,7 +30,7 @@
  • Adhérents
  • Machines
  • Cotisations
  • -
  • Topologie
  • +
  • Topologie
  • Statistiques