8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-30 08:32:26 +00:00

Merge branch 'tickets' into 'dev'

Tickets

See merge request federez/re2o!427
This commit is contained in:
grizzly 2019-08-21 13:14:12 +02:00
commit 7f07b1ed48
35 changed files with 904 additions and 4 deletions

View file

@ -503,5 +503,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</div> </div>
</div> </div>
{% for template in optionnal_templates_list %}
{{ template }}
{% endfor %}
{% endblock %} {% endblock %}

View file

@ -40,6 +40,8 @@ from django.utils.translation import ugettext as _
from reversion import revisions as reversion from reversion import revisions as reversion
from importlib import import_module
from re2o.settings_local import OPTIONNAL_APPS_RE2O
from re2o.views import form from re2o.views import form
from re2o.acl import can_create, can_edit, can_delete_set, can_view_all, can_delete from re2o.acl import can_create, can_edit, can_delete_set, can_view_all, can_delete
@ -94,6 +96,10 @@ def display_options(request):
radiusoptions, _ = RadiusOption.objects.get_or_create() radiusoptions, _ = RadiusOption.objects.get_or_create()
cotisationsoptions, _created = CotisationsOption.objects.get_or_create() cotisationsoptions, _created = CotisationsOption.objects.get_or_create()
document_template_list = DocumentTemplate.objects.order_by('name') document_template_list = DocumentTemplate.objects.order_by('name')
optionnal_apps = [import_module(app) for app in OPTIONNAL_APPS_RE2O]
optionnal_templates_list = [app.views.preferences(request) for app in optionnal_apps]
return form({ return form({
'useroptions': useroptions, 'useroptions': useroptions,
'machineoptions': machineoptions, 'machineoptions': machineoptions,
@ -109,6 +115,7 @@ def display_options(request):
'switchmanagementcred_list': switchmanagementcred_list, 'switchmanagementcred_list': switchmanagementcred_list,
'radiusoptions' : radiusoptions, 'radiusoptions' : radiusoptions,
'cotisationsoptions': cotisationsoptions, 'cotisationsoptions': cotisationsoptions,
'optionnal_templates_list': optionnal_templates_list,
'document_template_list': document_template_list, 'document_template_list': document_template_list,
}, 'preferences/display_preferences.html', request) }, 'preferences/display_preferences.html', request)

View file

@ -29,7 +29,8 @@ from django.contrib import messages
from django.http import HttpRequest from django.http import HttpRequest
from preferences.models import GeneralOption, OptionalMachine from preferences.models import GeneralOption, OptionalMachine
from django.utils.translation import get_language from django.utils.translation import get_language
from importlib import import_module
from re2o.settings_local import OPTIONNAL_APPS_RE2O
def context_user(request): def context_user(request):
"""Fonction de context lorsqu'un user est logué (ou non), """Fonction de context lorsqu'un user est logué (ou non),
@ -57,6 +58,15 @@ def context_user(request):
'ipv6_enabled': OptionalMachine.get_cached_value('ipv6'), 'ipv6_enabled': OptionalMachine.get_cached_value('ipv6'),
} }
def context_optionnal_apps(request):
"""Fonction de context pour générer la navbar en fonction des
apps optionnels"""
optionnal_apps = [import_module(app) for app in OPTIONNAL_APPS_RE2O]
optionnal_templates_navbar_user_list = [app.views.navbar_user(request) for app in optionnal_apps]
optionnal_templates_navbar_logout_list = [app.views.navbar_logout(request) for app in optionnal_apps]
return {'optionnal_templates_navbar_user_list':optionnal_templates_navbar_user_list,
'optionnal_templates_navbar_logout_list':optionnal_templates_navbar_logout_list}
def date_now(request): def date_now(request):
"""Add the current date in the context for quick informations and """Add the current date in the context for quick informations and

View file

@ -62,6 +62,7 @@ DJANGO_CONTRIB_APPS = (
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.humanize',
) )
EXTERNAL_CONTRIB_APPS = ( EXTERNAL_CONTRIB_APPS = (
'bootstrap3', 'bootstrap3',
@ -131,6 +132,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request', 'django.template.context_processors.request',
're2o.context_processors.context_user', 're2o.context_processors.context_user',
're2o.context_processors.context_optionnal_apps',
're2o.context_processors.date_now', 're2o.context_processors.date_now',
], ],
}, },

View file

@ -108,5 +108,8 @@ GID_RANGES = {
'posix': [501, 600], 'posix': [501, 600],
} }
# Some optionnal Re2o Apps
OPTIONNAL_APPS_RE2O = ()
# Some Django apps you want to add in you local project # Some Django apps you want to add in you local project
OPTIONNAL_APPS = () OPTIONNAL_APPS = OPTIONNAL_APPS_RE2O + ()

View file

@ -32,6 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<h2>{% blocktrans %}Contact the organisation {{asso_name}}{% endblocktrans %}</h2> <h2>{% blocktrans %}Contact the organisation {{asso_name}}{% endblocktrans %}</h2>
</br> </br>
{% for template in optionnal_templates_contact_list %}
{{template}}
{% endfor %}
{% for contact in contacts %} {% for contact in contacts %}
<div class="panel panel-info"> <div class="panel panel-info">

View file

@ -49,6 +49,8 @@ from django.contrib import admin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import RedirectView from django.views.generic import RedirectView
from .settings_local import OPTIONNAL_APPS_RE2O
from .views import index, about_page, contact_page from .views import index, about_page, contact_page
# Admin site configuration # Admin site configuration
@ -83,6 +85,10 @@ urlpatterns = [
url(r'^admin/login/$', RedirectView.as_view(pattern_name='login')), url(r'^admin/login/$', RedirectView.as_view(pattern_name='login')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
] ]
urlpatterns += [url(r'^{}/'.format(app), include('{}.urls'.format(app), namespace=app)) for app in OPTIONNAL_APPS_RE2O]
# Add debug_toolbar URLs if activated # Add debug_toolbar URLs if activated
if 'debug_toolbar' in settings.INSTALLED_APPS: if 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar import debug_toolbar

View file

@ -43,6 +43,8 @@ from preferences.models import (
) )
from .contributors import CONTRIBUTORS from .contributors import CONTRIBUTORS
from importlib import import_module
from re2o.settings_local import OPTIONNAL_APPS_RE2O
def form(ctx, template, request): def form(ctx, template, request):
@ -113,12 +115,16 @@ def contact_page(request):
""" """
address = MailContact.objects.all() address = MailContact.objects.all()
optionnal_apps = [import_module(app) for app in OPTIONNAL_APPS_RE2O]
optionnal_templates_contact_list = [app.views.contact(request) for app in optionnal_apps]
return render( return render(
request, request,
"re2o/contact.html", "re2o/contact.html",
{ {
'contacts': address, 'contacts': address,
'asso_name': AssoOption.objects.first().name 'asso_name': AssoOption.objects.first().name,
'optionnal_templates_contact_list':optionnal_templates_contact_list,
} }
) )

View file

@ -100,6 +100,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% can_view_app cotisations %} {% can_view_app cotisations %}
<li><a href="{% url 'cotisations:index' %}"><i class="fa fa-eur"></i> {% trans "Manage the subscriptions" %}</a></li> <li><a href="{% url 'cotisations:index' %}"><i class="fa fa-eur"></i> {% trans "Manage the subscriptions" %}</a></li>
{% acl_end %} {% acl_end %}
{% for template in optionnal_templates_navbar_user_list%}
{{ template }}
{% endfor %}
</ul> </ul>
</li> </li>
{% acl_end %} {% acl_end %}
@ -126,13 +130,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fa fa-info"></i> {% trans "More information" %}<span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fa fa-info"></i> {% trans "Information and contact" %}<span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="{% url 'about' %}"><i class="fa fa-info-circle"></i> {% trans "About" %}</a></li> <li><a href="{% url 'about' %}"><i class="fa fa-info-circle"></i> {% trans "About" %}</a></li>
<li><a href="{% url 'contact' %}"><i class="fa fa-at"></i> {% trans "Contact" %}</a></li> <li><a href="{% url 'contact' %}"><i class="fa fa-at"></i> {% trans "Contact" %}</a></li>
{% comment %}
<li><a href="{% url 'tickets:new-ticket' %}"><i class="fa fa-ticket"></i> {% trans "Ouvrir un ticket" %}</a><li>
{% endcomment %}
</ul> </ul>
</li> </li>
{% if not request.user.is_authenticated %} {% if not request.user.is_authenticated %}
{% for template in optionnal_templates_navbar_logout_list %}
{{ template }}
{% endfor %}
{% if var_sa %} {% if var_sa %}
<li> <li>
<a href="{% url 'users:new-user' %}"> <a href="{% url 'users:new-user' %}">

0
tickets/__init__.py Normal file
View file

4
tickets/admin.py Normal file
View file

@ -0,0 +1,4 @@
from django.contrib import admin
from .models import Ticket
admin.site.register(Ticket)
# Register your models here.

5
tickets/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class TicketsConfig(AppConfig):
name = 'tickets'

24
tickets/forms.py Normal file
View file

@ -0,0 +1,24 @@
from django import forms
from django.forms import ModelForm, Form
from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin
from django.utils.translation import ugettext_lazy as _
from .models import(
Ticket,
)
class NewTicketForm(ModelForm):
""" Creation of a ticket"""
email = forms.EmailField(required=False)
class Meta:
model = Ticket
fields = ['title', 'description', 'email']
class ChangeStatusTicketForm(ModelForm):
""" Change ticket status"""
class Meta:
model = Ticket
fields = []

View file

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-08-19 08:19
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import re2o.mixins
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Preferences',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('publish_address', models.EmailField(help_text='Email address to publish the new tickets (leave empty for no publications)', max_length=1000, null=True)),
('mail_language', models.IntegerField(choices=[(0, 'Français'), (1, 'English')], default=0)),
],
options={
'verbose_name': "Ticket's settings",
},
),
migrations.CreateModel(
name='Ticket',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(help_text='Title of the ticket', max_length=255)),
('description', models.TextField(help_text='Description of the ticket', max_length=3000)),
('date', models.DateTimeField(auto_now_add=True)),
('email', models.EmailField(help_text='An email address to get back to you', max_length=100, null=True)),
('solved', models.BooleanField(default=False)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Ticket',
'verbose_name_plural': 'Tickets',
},
bases=(re2o.mixins.AclMixin, models.Model),
),
]

View file

97
tickets/models.py Normal file
View file

@ -0,0 +1,97 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.mail import send_mail
from django.template import Context, loader
from django.db.models.signals import post_save
from django.dispatch import receiver
from re2o.mixins import AclMixin
from preferences.models import GeneralOption
import users.models
from .preferences.models import Preferences
class Ticket(AclMixin, models.Model):
"""Model of a ticket"""
user = models.ForeignKey(
'users.User',
on_delete=models.CASCADE,
related_name="tickets",
blank=True,
null=True)
title = models.CharField(
max_length=255,
help_text=_("Title of the ticket"),
blank=False,
null=False,)
description = models.TextField(
max_length=3000,
help_text=_("Description of the ticket"),
blank=False,
null=False)
date = models.DateTimeField(auto_now_add=True)
email = models.EmailField(
help_text = _("An email address to get back to you"),
max_length=100,
null=True)
solved = models.BooleanField(default=False)
class Meta:
verbose_name = _("Ticket")
verbose_name_plural = _("Tickets")
def __str__(self):
if self.user:
return "Ticket from {}. Date: {}".format(self.user.surname,self.date)
else:
return "Anonymous Ticket. Date: {}".format(self.date)
def publish_mail(self):
site_url = GeneralOption.objects.first().main_site_url
to_addr = Preferences.objects.first().publish_address
context = Context({'ticket':self,'site_url':site_url})
lang = Preferences.objects.first().mail_language
if(lang == 0):
obj = 'Nouvelle ouverture de ticket'
template = loader.get_template('tickets/publication_mail_fr')
else:
obj = 'New ticket opened'
template = loader.get_template('tickets/publication_mail_en')
send_mail(
obj,
template.render(context),
GeneralOption.get_cached_value('email_from'),
[to_addr],
fail_silently = False)
def can_view(self, user_request, *_args, **_kwargs):
""" Check that the user has the right to view the ticket
or that it is the author"""
if (not user_request.has_perm('tickets.view_ticket') and self.user != user_request):
return False, _("You don't have the right to view other tickets than yours.")
else:
return True, None
@staticmethod
def can_view_all(user_request, *_args, **_kwargs):
""" Check that the user has access to the list of all tickets"""
return(
user_request.has_perm('tickets.view_tickets'),
_("You don't have the right to view the list of tickets.")
)
def can_create(user_request,*_args, **_kwargs):
""" Authorise all users to open tickets """
return True,None
@receiver(post_save, sender=Ticket)
def ticket_post_save(**kwargs):
""" Send the mail to publish the new ticket """
if kwargs['created']:
if Preferences.objects.first().publish_address:
ticket = kwargs['instance']
ticket.publish_mail()

View file

@ -0,0 +1,11 @@
from django import forms
from django.forms import ModelForm, Form
from django.utils.translation import ugettext_lazy as _
from .models import Preferences
class EditPreferencesForm(ModelForm):
""" Edit the ticket's settings"""
class Meta:
model = Preferences
fields = '__all__'

View file

@ -0,0 +1,19 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Preferences(models.Model):
""" Definition of the ticket's settings"""
publish_address = models.EmailField(
help_text = _("Email address to publish the new tickets (leave empty for no publications)"),
max_length = 1000,
null = True)
LANG_FR = 0
LANG_EN = 1
LANGUES = (
(0,_("Français")),
(1,_("English")),
)
mail_language = models.IntegerField(choices=LANGUES,default = LANG_FR)
class Meta:
verbose_name = _("Ticket's settings")

View file

@ -0,0 +1,82 @@
{% extends 'users/sidebar.html' %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load i18n %}
{% load humanize %}
{% block title %}{% trans "Tickets" %}{% endblock %}
{% block content %}
<h2> Ticket #{{ticket.id}}
{% if ticket.solved %}
<span class="badge badge-success">{% trans "Solved" %}</span>
{% else %}
<span class="badge badge-danger">{% trans "Not Solved" %}</span>
{% endif %}
</h2>
<div class="panel panel-default">
<div class="panel-heading">
{% trans "Opened by" %}
{% if ticket.user %}
<a href="{% url 'users:profil' ticket.user.id%}">
{{ ticket.user.get_full_name }}
</a>
{% else %}
{% trans "Anonymous User" %}
{% endif %}
{{ ticket.date | naturalday}}.
{% if not ticket.user %}
{% trans "Response address: " %}<A HREF="mailto:{{ticket.email}}?subject={% trans "Response to your ticket"%}">{{ticket.email}}</A>
{% endif %}
</div>
<div class="panel-body">
<p><b>{% trans "Title:" %}</b> {{ticket.title}}</p>
<p><b>{% trans "Description" %}</b> {{ ticket.description }}</p>
<div class="text-right">
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form changestatusform %}
{% if not ticket.solved %}
{% bootstrap_button "Mark as Solved" button_type="submit" button_class='btn-info' %}
{% else %}
{% bootstrap_button "Mark as not Solved" button_type="submit" button_class='btn-warning' %}
{% endif %}
</form>
</div>
</div>
</div>
<div class="text-right">
<a type="button" href="{% url 'tickets:aff-tickets' %}" class="btn btn-primary"><p>{% trans "Tous les tickets" %}</p></a>
</div>
{% endblock %}

View file

@ -0,0 +1,89 @@
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load i18n %}
{% block content %}
<div class="container-fluid">
<hr class="col-sm-12">
<div class="row justify-content-start">
<div class="col-sm-4">
<span class="badge badge-light">{{ nbr_tickets }}</span> {% trans "Tickets" %}
</div>
<div class="col-sm-4">
<span class="badge badge-light"> {{ nbr_tickets_unsolved }}</span>{% trans "Not Solved Tickets" %}
</div>
<div class="col-sm-4">
<span>{% trans "Last Ticket:" %} {{ last_ticket_date }}</span>
</div>
</div>
<hr class="col-sm-12">
</div>
<div class="table-responsiv">
<table class="table">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">User</th>
<th scope="col">Titre</th>
<th scope="col">Date</th>
<th scope="col">Résolu</th>
</tr>
{% for ticket in tickets_list %}
<tr>
<td>
<a href="{% url 'tickets:aff-ticket' ticket.id%}" class="btn btn-primary btn-sm" role="button">
<i class="fa fa-ticket"></i>
</a>
</td>
{% if ticket.user %}
<td><a href="{% url 'users:profil' ticket.user.id%}" role="button">{{ ticket.user.get_short_name }}</a></td>
{% else %}
<td> Anonyme </td>
{% endif %}
<td>{{ ticket.title }}</td>
<td>{{ ticket.date }}</td>
{% if ticket.solved %}
<td><i class="fa fa-check" style="color:green"></i></td>
{% else %}
<td><i class="fa fa-times" style="color:red"></i></td>
{% endif %}
</tr>
{% endfor %}
</thead>
</table>
{% if tickets_list.paginator %}
{% include 'pagination.html' with list=tickets_list go_to_id="tickets" %}
{% endif %}
</div>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% load i18n %}
<div class="panel panel-info">
<div class="panel-heading"><h4>Tickets</h4></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-9">
{% blocktrans %}If you are experiencing issues with the services offered by {{asso_name}}, you can open a ticket that will be taken care of. If you want to contact us on any other topic, please choose one address below.{% endblocktrans %}
</div>
<div class="col-sm-3"><a class="btn btn-primary" href="{% url 'tickets:new-ticket' %}"><i class="fa fa-ticket"></i> {% trans "Ouvrir un ticket" %}</a></div>
</div>
</div>
</div>

View file

@ -0,0 +1,48 @@
{% extends 'machines/sidebar.html' %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
Copyright © 2017 Maël Kervella
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load i18n %}
{% block title %}{% trans "Ticket" %}{% endblock %}
{% block content %}
<h2> {% trans "Tickets settings modification" %}</h2>
{% for message in messages %}
<div class="{{ message| bootstrap_message_classes }} alert-dismissable">
<button type="button" class="close" data_dismiss="alert" aria-hidden="true">&#125;</button>
{{ message | safe }}
</div>
{% endfor %}
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_field preferencesform.publish_address %}
{% bootstrap_field preferencesform.mail_language %}
{% bootstrap_button "Editer" button_type="submit" icon='ok' button_class='btn-success' %}
</form>
{% endblock %}

View file

@ -0,0 +1,58 @@
{% extends 'machines/sidebar.html' %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
Copyright © 2017 Maël Kervella
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load massive_bootstrap_form %}
{% load i18n %}
{% block title %}{% trans "Ticket" %}{% endblock %}
{% block content %}
<h2> Ouverture d'un Ticket </h2>
<form class="form" method="post">
{% csrf_token %}
{% if not user.is_authenticated %}
<p>{% trans "Vous n'êtes pas authentifié. Veuillez fournir une adresse mail afin que nous puissions vous recontacter." %}</p>
{% bootstrap_field ticketform.email %}
{% endif %}
{% bootstrap_field ticketform.title %}
<br>
<p>{% trans "Description de votre problème. Veuillez fournir le plus d'informations possible afin de faciliter la recherche de solution. Voici quelques informations dont nous pourions avoir besoin:" %}</p>
<ul class="list">
<li>
<p> {% trans "Le type de votre problème (adhesion, connexion, paiement ou autre)." %}</p>
</li>
<li>
<p> {% trans "Les conditions dans lesquelles vous rencontrez le problème (Wifi/filaire, sur tout les apareils ou sur un seul. Est-ce une nouvelle machine ?" %}</p>
</li>
<li>
<p> {% trans "Les endroits dans lequels le problème survient (chez vous, dans une partie commune, dans un batiment en particulier)." %}</p>
</ul>
{% bootstrap_field ticketform.description %}
{% bootstrap_button "Ouvrir le Ticket" button_type="submit" icon='ok' button_class='btn-success' %}
</form>
{% endblock %}

View file

@ -0,0 +1,34 @@
{% extends 'users/sidebar.html' %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load i18n %}
{% block title%}{% trans "Tickets" %}{% endblock %}
{% block content %}
<h2>{% trans "Tickets" %}</h2>
{% include 'tickets/aff_tickets.html' with tickets_list=tickets_list %}
{% endblock %}

View file

@ -0,0 +1,2 @@
{% load i18n %}
<li><a href="{% url 'tickets:aff-tickets' %}"><i class="fa fa-ticket"></i>{% trans "Tickets" %}</a></li>

View file

@ -0,0 +1,6 @@
{% load i18n %}
<li>
<a href="{% url 'tickets:new-ticket' %}">
<i class="fa fa-ticket"></i> {% trans "Ouvrir un ticket" %}
</a>
</li>

View file

@ -0,0 +1,36 @@
{% load i18n %}
<div class="panel panel-default" id="tickets">
<div class="panel-heading" data-toggle="collapse" href="#collapse_tickets">
<h4 class="panel-title">
<a><i class="fa fa-ticket"></i> {% trans "Tickets" %}</a>
</h4>
</div>
<div id="collapse_tickets" class="panel-collapse panel-body collapse">
<a class="btn btn-primary btn-sm" role="button" href="{% url 'tickets:edit-preferences-tickets' %}">
<i class="fa fa-edit"></i>
{% trans "Edit" %}
</a>
<p></p>
<div class="table-responsive">
<table class="table">
<tr>
<th><p>{% trans "Publication email address"%}</p></th>
{% if preferences.publish_address %}
<td><p>{{ preferences.publish_address }}</p></td>
{% else %}
<td><p>{% trans "Pas d'adresse, les tickets ne sont pas annoncés" %}</p></td>
{% endif %}
</tr>
<tr>
<th><p>{% trans "Email language" %}</p></th>
<td><p>{{ language }}</p></th>
</tr>
<table class="table">
</table>
</div>
</div>
</div>

View file

@ -0,0 +1,23 @@
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading clearfix profil" data-parent="#accordion" data-toggle="collapse" data-target="#ticket">
<h3 class="panel-title pull-left">
<i class="fa fa-ticket"></i>{% trans " Tickets" %}
</h3>
</div>
<div id="ticket" class="panel-collapse collapse">
<div class="panel-body">
<a class="btn btn-primary btn-sm" role="button" href="{% url 'tickets:new-ticket' %}">
<i class="fa fa-ticket"></i>{% trans " Open a Ticket" %}
</a>
</div>
<div class="panel-body">
{% if tickets_list %}
{% include 'tickets/aff_tickets.html' with tickets_list=tickets_list %}
{% else %}
<p>{% trans "No tickets" %}</p>
{% endif %}
</div>
</div>
</div>

View file

@ -0,0 +1,12 @@
{% if ticket.user %} {{ ticket.user.get_full_name }} opened a ticket.
Profil: {{site_url}}{% url 'users:profil' ticket.user.id%}
Answer to the address: {{ticket.user.get_mail}}.
{% else %}
An anonymous user (not authenticated) opened a ticket
Answer to the address:{{ticket.email}}.
{% endif %}
Title: {{ticket.title}}
Description: {{ticket.description}}

View file

@ -0,0 +1,12 @@
{% if ticket.user %} {{ ticket.user.get_full_name }} à ouvert un ticket.
Profile: {{site_url}}{% url 'users:profil' ticket.user.id%}
Répondre à l'adresse: {{ticket.user.get_mail}}.
{% else %}
Un utilisateur anonyme (non connecté) à ouvert un ticket.
Répondre à l'adresse: {{ticket.email}}.
{% endif %}
Titre: {{ticket.title}}
Description: {{ticket.description}}

3
tickets/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

10
tickets/urls.py Normal file
View file

@ -0,0 +1,10 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.aff_tickets, name='aff-tickets'),
url(r'^ticket/(?P<ticketid>[0-9]+)$', views.aff_ticket, name='aff-ticket'),
url(r'^ticket/edit-preferences-tickets$', views.edit_preferences, name='edit-preferences-tickets'),
url(r'^new_ticket/$',views.new_ticket,name='new-ticket'),
]

197
tickets/views.py Normal file
View file

@ -0,0 +1,197 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# App de gestion des users pour re2o
# Goulven Kermarec, Gabriel Détraz, Lemesle Augustin
# Gplv2
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from django.template.loader import render_to_string
from django.views.decorators.cache import cache_page
from django.utils.translation import ugettext as _
from django.urls import reverse
from django.forms import modelformset_factory
from re2o.views import form
from re2o.base import (
re2o_paginator,
)
from re2o.acl import(
can_view,
can_view_all,
can_edit,
can_create,
)
from preferences.models import GeneralOption
from .models import(
Ticket,
)
from .preferences.models import(
Preferences,
)
from .forms import (
NewTicketForm,
ChangeStatusTicketForm,
)
from .preferences.forms import (
EditPreferencesForm,
)
def new_ticket(request):
""" Ticket creation view"""
ticketform = NewTicketForm(request.POST or None)
if request.method == 'POST':
ticketform = NewTicketForm(request.POST)
if ticketform.is_valid():
email = ticketform.cleaned_data.get('email')
ticket = ticketform.save(commit=False)
if request.user.is_authenticated:
ticket.user = request.user
ticket.save()
messages.success(request,_('Your ticket has been succesfully open. We will take care of it as soon as possible.'))
return redirect(reverse('users:profil',kwargs={'userid':str(request.user.id)}))
if not request.user.is_authenticated and email != "":
ticket.save()
messages.success(request,_('Your ticket has been succesfully open. We will take care of it as soon as possible.'))
return redirect(reverse('index'))
else:
messages.error(request,_("You are not authenticated. Please login or provide an email address so we can get back to you."))
return form({'ticketform':ticketform,},'tickets/form_ticket.html',request)
else:
ticketform = NewTicketForm
return form({'ticketform':ticketform,},'tickets/form_ticket.html',request)
@login_required
@can_view(Ticket)
def aff_ticket(request, ticket, ticketid):
"""View to display only one ticket"""
changestatusform = ChangeStatusTicketForm(request.POST)
if request.method == 'POST':
ticket.solved = not ticket.solved
ticket.save()
return render(request,'tickets/aff_ticket.html',{'ticket':ticket,'changestatusform':changestatusform})
@login_required
@can_view_all(Ticket)
def aff_tickets(request):
""" View to display all the tickets """
tickets_list = Ticket.objects.all().order_by('-date')
nbr_tickets = tickets_list.count()
nbr_tickets_unsolved = tickets_list.filter(solved=False).count()
if nbr_tickets:
last_ticket_date = tickets_list.first().date
else:
last_ticket_date = _("Never")
pagination_number = (GeneralOption
.get_cached_value('pagination_number'))
tickets = re2o_paginator(
request,
tickets_list,
pagination_number,
)
context = {'tickets_list':tickets,
'last_ticket_date':last_ticket_date,
'nbr_tickets':nbr_tickets,
'nbr_tickets_unsolved':nbr_tickets_unsolved}
return render(request,'tickets/index.html',context=context)
def edit_preferences(request):
""" View to edit the settings of the tickets """
preferences_instance, created = Preferences.objects.get_or_create(id=1)
preferencesform = EditPreferencesForm(
request.POST or None,
instance = preferences_instance,)
if preferencesform.is_valid():
if preferencesform.changed_data:
preferencesform.save()
messages.success(request,'Préférences des Tickets mises à jour')
return redirect(reverse('preferences:display-options',))
else:
messages.error(request,'Formulaire Invalide')
return form({'preferencesform':preferencesform,},'tickets/form_preferences.html',request)
return form({'preferencesform':preferencesform,},'tickets/form_preferences.html',request)
# views cannoniques des apps optionnels
def profil(request,user):
""" View to display the ticket's module on the profil"""
tickets_list = Ticket.objects.filter(user=user).all().order_by('-date')
nbr_tickets = tickets_list.count()
nbr_tickets_unsolved = tickets_list.filter(solved=False).count()
if nbr_tickets:
last_ticket_date = tickets_list.first().date
else:
last_ticket_date = _("Never")
pagination_number = (GeneralOption
.get_cached_value('pagination_large_number'))
tickets = re2o_paginator(
request,
tickets_list,
pagination_number,
)
context = {'tickets_list':tickets,
'last_ticket_date':last_ticket_date,
'nbr_tickets':nbr_tickets,
'nbr_tickets_unsolved':nbr_tickets_unsolved}
return render_to_string('tickets/profil.html', context=context, request=request, using=None)
def preferences(request):
""" View to display the settings of the tickets in the preferences page"""
pref, created = Preferences.objects.get_or_create(id=1)
context = {'preferences':pref,'language':str(pref.LANGUES[pref.mail_language][1])}
return render_to_string('tickets/preferences.html', context=context, request=request, using=None)
def contact(request):
"""View to display a contact address on the contact page
used here to display a link to open a ticket"""
return render_to_string('tickets/contact.html')
def navbar_user(request):
"""View to display the ticket link in thet user's dropdown in the navbar"""
return render_to_string('tickets/navbar.html')
def navbar_logout(request):
"""View to display the ticket link to log out users"""
return render_to_string('tickets/navbar_logout.html')

View file

@ -119,6 +119,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</div> </div>
</div> </div>
<div class="panel-group" id="accordion"> <div class="panel-group" id="accordion">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading clearfix profil" data-parent="#accordion" data-toggle="collapse" <div class="panel-heading clearfix profil" data-parent="#accordion" data-toggle="collapse"
@ -527,6 +528,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</div> </div>
</div> </div>
</div> </div>
{% for template in optionnal_templates_list %}
{{ template }}
{% endfor %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -47,13 +47,17 @@ from django.http import HttpResponse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.template import loader
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from reversion import revisions as reversion from reversion import revisions as reversion
from cotisations.models import Facture, Paiement from cotisations.models import Facture, Paiement
from machines.models import Machine from machines.models import Machine
from preferences.models import OptionalUser, GeneralOption, AssoOption from preferences.models import OptionalUser, GeneralOption, AssoOption
from importlib import import_module
from re2o.settings_local import OPTIONNAL_APPS_RE2O
from re2o.views import form from re2o.views import form
from re2o.utils import ( from re2o.utils import (
all_has_access, all_has_access,
@ -974,6 +978,10 @@ def profil(request, users, **_kwargs):
request.GET.get('order'), request.GET.get('order'),
SortTable.MACHINES_INDEX SortTable.MACHINES_INDEX
) )
optionnal_apps = [import_module(app) for app in OPTIONNAL_APPS_RE2O]
optionnal_templates_list = [app.views.profil(request,users) for app in optionnal_apps]
pagination_large_number = GeneralOption.get_cached_value( pagination_large_number = GeneralOption.get_cached_value(
'pagination_large_number' 'pagination_large_number'
) )
@ -1016,6 +1024,7 @@ def profil(request, users, **_kwargs):
'users': users, 'users': users,
'machines_list': machines, 'machines_list': machines,
'nb_machines': nb_machines, 'nb_machines': nb_machines,
'optionnal_templates_list': optionnal_templates_list,
'facture_list': factures, 'facture_list': factures,
'ban_list': bans, 'ban_list': bans,
'white_list': whitelists, 'white_list': whitelists,