<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
- <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (socoin_atlas)" project-jdk-type="Python SDK" />
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (pia_atlas)" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/env" />
</content>
- <orderEntry type="inheritedJdk" />
+ <orderEntry type="jdk" jdkName="Python 3.7 (pia_atlas)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
django-crispy-forms==1.14.0
djangorestframework==3.14.0
djangorestframework-simplejwt==5.2.0
-mysqlclient==2.1.1
+#mysqlclient==2.1.1
PyJWT==2.5.0
pytz==2022.2.1
sqlparse==0.4.3
--- /dev/null
+from django_datatables_view.base_datatable_view import BaseDatatableView
+from django.utils.html import escape
+from django.db.models import Q
+
+from sistema.models import Localita, Multimedia, TipologiaMultimedia, Percorso, PointOfInterest, Feedback
+
+
+class LocalitaDatatables(BaseDatatableView):
+ model = Localita
+ columns = ['id', 'descrizione', 'provincia', 'cap', 'regione', 'mod', 'del']
+ order_columns = ['id', 'descrizione', 'provincia', 'cap', 'regione', '', '']
+
+ def get_initial_queryset(self):
+ return self.model.objects.filter(is_active=True)
+
+ def render_column(self, row, column):
+ if column == 'id':
+ return escape('{0}'.format(row.pk))
+ if column == 'descrizione':
+ return escape('{0}'.format(row.descrizione))
+ if column == 'provincia':
+ return escape('{0}'.format(row.provincia))
+ if column == 'cap':
+ return escape('{0}'.format(row.cap))
+ if column == 'regione':
+ return escape('{0}'.format(row.regione))
+ if column == 'mod':
+ return '<a class="btn btn-sm btn-primary" href="/mod_localita/%s/" ><i class="fas fa-edit"></i></a>' % row.pk
+ if column == 'del':
+ return '<button class="btn btn-sm btn-danger" onclick="DeleteLocalita(%s)"><i class="fas fa-trash"></i></button>' % row.pk
+ else:
+ return super(LocalitaDatatables, self).render_column(row, column)
+
+ def filter_queryset(self, qs):
+ search = self.request.GET.get('search[value]', None)
+ if search:
+ qs = qs.filter(Q(descrizione__icontains=search) | Q(provincia__icontains=search)
+ | Q(cap__icontains=search) | Q(regione__icontains=search))
+ return qs
+
+
+class MultimediaDatatables(BaseDatatableView):
+ model = Multimedia
+ columns = ['id', 'tipologia', 'nome', 'descrizione', 'testo', 'mod', 'del']
+ order_columns = ['id', 'tipologia', 'nome', 'descrizione', 'testo', 'mod', 'del']
+
+ def get_initial_queryset(self):
+ return self.model.objects.filter(is_active=True)
+
+ def render_column(self, row, column):
+ if column == 'id':
+ return escape('{0}'.format(row.pk))
+ if column == 'tipologia':
+ return escape('{0}'.format(row.tipologia.nome))
+ if column == 'nome':
+ return escape('{0}'.format(row.provincia))
+ if column == 'descrizione':
+ return escape('{0}'.format(row.descrizione))
+ if column == 'testo':
+ return escape('{0}'.format(row.testo))
+ if column == 'mod':
+ return '<a class="btn btn-sm btn-primary" href="#" ><i class="fas fa-edit"></i></a>' #% row.pk
+ if column == 'del':
+ return '<a class="btn btn-sm btn-danger" href="#" ><i class="fas fa-trash"></i></a>' #% row.pk
+ else:
+ return super(MultimediaDatatables, self).render_column(row, column)
+
+ def filter_queryset(self, qs):
+ search = self.request.GET.get('search[value]', None)
+ if search:
+ qs = qs.filter(Q(descrizione__icontains=search) | Q(nome__icontains=search)
+ | Q(tipologia__nome__icontains=search))
+ return qs
+
+
+class TipoMultimediaDatatables(BaseDatatableView):
+ model = TipologiaMultimedia
+ columns = ['id', 'nome', 'mod', 'del']
+ order_columns = ['id', 'nome', 'mod', 'del']
+
+ def get_initial_queryset(self):
+ return self.model.objects.filter(is_active=True)
+
+ def render_column(self, row, column):
+ if column == 'id':
+ return escape('{0}'.format(row.pk))
+ if column == 'nome':
+ return escape('{0}'.format(row.nome))
+ if column == 'mod':
+ return '<a class="btn btn-sm btn-primary" href="/mod_tipo_multimedia/%s/" ><i class="fas fa-edit"></i></a>' % row.pk
+ if column == 'del':
+ return '<button class="btn btn-sm btn-danger" onclick="DeleteTipologia(%s)"><i class="fas fa-trash"></i></button>' % row.pk
+ else:
+ return super(TipoMultimediaDatatables, self).render_column(row, column)
+
+ def filter_queryset(self, qs):
+ search = self.request.GET.get('search[value]', None)
+ if search:
+ qs = qs.filter(Q(nome__icontains=search))
+ return qs
+
+
+class PercorsoDatatables(BaseDatatableView):
+ model = Percorso
+ columns = ['id', 'nome', 'descrizione', 'testo', 'mod', 'del']
+ order_columns = ['id', 'nome', 'descrizione', 'testo', 'mod', 'del']
+
+ def get_initial_queryset(self):
+ return self.model.objects.filter(is_active=True)
+
+ def render_column(self, row, column):
+ if column == 'id':
+ return escape('{0}'.format(row.pk))
+ if column == 'nome':
+ return escape('{0}'.format(row.nome))
+ if column == 'descrizione':
+ return escape('{0}'.format(row.descrizione))
+ if column == 'testo':
+ return escape('{0}'.format(row.testo))
+ if column == 'mod':
+ return '<a class="btn btn-sm btn-primary" href="#" ><i class="fas fa-edit"></i></a>' #% row.pk
+ if column == 'del':
+ return '<a class="btn btn-sm btn-danger" href="#" ><i class="fas fa-trash"></i></a>' #% row.pk
+ else:
+ return super(PercorsoDatatables, self).render_column(row, column)
+
+ def filter_queryset(self, qs):
+ search = self.request.GET.get('search[value]', None)
+ if search:
+ qs = qs.filter(Q(nome__icontains=search) | Q(descrizione__icontains=search) | Q(testo__icontains=search))
+ return qs
+
+
+class PoiDatatables(BaseDatatableView):
+ model = PointOfInterest
+ columns = ['id', 'nome', 'lat', 'long', 'mod', 'del']
+ order_columns = ['id', 'nome', 'lat', 'long', 'mod', 'del']
+
+ def get_initial_queryset(self):
+ return self.model.objects.filter(is_active=True)
+
+ def render_column(self, row, column):
+ if column == 'id':
+ return escape('{0}'.format(row.pk))
+ if column == 'nome':
+ return escape('{0}'.format(row.nome))
+ if column == 'lat':
+ return escape('{0}'.format(row.lat))
+ if column == 'long':
+ return escape('{0}'.format(row.long))
+ if column == 'mod':
+ return '<a class="btn btn-sm btn-primary" href="/mod_poi/%s/" ><i class="fas fa-edit"></i></a>' #% row.pk
+ if column == 'del':
+ return '<a class="btn btn-sm btn-danger" href="DeletePoi(%s)" ><i class="fas fa-trash"></i></a>' #% row.pk
+ else:
+ return super(PoiDatatables, self).render_column(row, column)
+
+ def filter_queryset(self, qs):
+ search = self.request.GET.get('search[value]', None)
+ if search:
+ qs = qs.filter(Q(nome__icontains=search) | Q(lat__icontains=search) | Q(long__icontains=search))
+ return qs
+
+
+class FeedbackDatatables(BaseDatatableView):
+ model = Feedback
+ columns = ['id', 'utente', 'valutazione', 'commento', 'mod', 'del']
+ order_columns = ['id', 'utente', 'valutazione', 'commento', 'mod', 'del']
+
+ def get_initial_queryset(self):
+ return self.model.objects.filter(is_active=True)
+
+ def render_column(self, row, column):
+ if column == 'id':
+ return escape('{0}'.format(row.pk))
+ if column == 'utente':
+ return escape('{0}'.format(row.utente.username))
+ if column == 'valutazione':
+ return escape('{0}'.format(row.valutazione))
+ if column == 'commento':
+ return escape('{0}'.format(row.commento))
+ if column == 'mod':
+ return '<a class="btn btn-sm btn-primary" href="#" ><i class="fas fa-edit"></i></a>' #% row.pk
+ if column == 'del':
+ return '<a class="btn btn-sm btn-danger" href="#" ><i class="fas fa-trash"></i></a>' #% row.pk
+ else:
+ return super(FeedbackDatatables, self).render_column(row, column)
+
+ def filter_queryset(self, qs):
+ search = self.request.GET.get('search[value]', None)
+ if search:
+ qs = qs.filter(Q(utente__icontains=search) | Q(valutazione__icontains=search))
+ return qs
\ No newline at end of file
+from django.contrib.auth.forms import *
+
+from sistema.models import Localita, TipologiaMultimedia
+
+
+class LocalitaForm(forms.ModelForm):
+ class Meta:
+ model = Localita
+ exclude = ('is_active',)
+
+ descrizione = forms.CharField(
+ label="Nome",
+ max_length=255,
+ widget=forms.TextInput(attrs={'class': 'form-control', 'form': 'localita_form'})
+ )
+
+ provincia = forms.CharField(
+ label="Provincia",
+ max_length=20,
+ widget=forms.TextInput(attrs={'class': 'form-control', 'form': 'localita_form'})
+ )
+
+ cap = forms.CharField(
+ label="Cap",
+ max_length=10,
+ widget=forms.TextInput(attrs={'class': 'form-control', 'form': 'localita_form'})
+ )
+
+ regione = forms.CharField(
+ label="Regione",
+ max_length=50,
+ widget=forms.TextInput(attrs={'class': 'form-control', 'form': 'localita_form'})
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+
+class TipoMultimediaForm(forms.ModelForm):
+ class Meta:
+ model = TipologiaMultimedia
+ exclude = ('is_active',)
+
+ nome = forms.CharField(
+ label="Nome tipologia",
+ max_length=255,
+ widget=forms.TextInput(attrs={'class': 'form-control', 'form': 'tipo_multimedia_form'})
+ )
\ No newline at end of file
class Localita(models.Model):
descrizione = models.CharField(max_length=255, null=False)
- provincia = models.CharField(max_length=5, null=True, blank=True)
- cap = models.CharField(max_length=5, null=True, blank=True)
- regione = models.CharField(max_length=5, null=True, blank=True)
+ provincia = models.CharField(max_length=50, null=True, blank=True)
+ cap = models.CharField(max_length=10, null=True, blank=True)
+ regione = models.CharField(max_length=50, null=True, blank=True)
is_active = models.BooleanField(default=True)
localita = models.ForeignKey(Localita, on_delete=models.DO_NOTHING)
data_inizio = models.DateTimeField(auto_now_add=False, default=timezone.now, null=True, blank=True)
data_fine = models.DateTimeField(null=True, blank=True)
+ is_active = models.BooleanField(default=True)
class Percorso(models.Model):
nome = models.CharField(max_length=255, null=False)
descrizione = models.CharField(max_length=255, null=False)
testo = models.TextField(max_length=255, null=False)
+ is_active = models.BooleanField(default=True)
class PointOfInterest(models.Model):
nome = models.CharField(max_length=255, null=False)
lat = models.FloatField()
long = models.FloatField()
+ is_active = models.BooleanField(default=True)
class Tappa(models.Model):
percorso = models.ForeignKey(Percorso, on_delete=models.DO_NOTHING)
poi = models.ForeignKey(PointOfInterest, on_delete=models.DO_NOTHING)
ordine = models.IntegerField()
+ is_active = models.BooleanField(default=True)
class TipologiaMultimedia(models.Model):
nome = models.CharField(max_length=255, null=False)
+ is_active = models.BooleanField(default=True)
class Multimedia(models.Model):
nome = models.CharField(max_length=255, null=False)
descrizione = models.CharField(max_length=255, null=False)
testo = models.TextField(max_length=255, null=False)
+ is_active = models.BooleanField(default=True)
class Feedback(models.Model):
utente = models.ForeignKey(User, on_delete=models.DO_NOTHING)
percorso = models.ForeignKey(Percorso, on_delete=models.DO_NOTHING)
valutazione = models.IntegerField()
+ commento = models.TextField(max_length=255, null=True, blank=True)
+ is_active = models.BooleanField(default=True)
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% load crispy_forms_tags %}
+{% block content %}
+
+ <div class="row">
+ <div class="col-12 col-md-12 col-lg-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-tree-city clr-bolle mr-2" aria-hidden="true"></i> Gestione località </h1>
+ <div class="card">
+ <div class="card-header">
+ {% if form.instance.pk %}
+ <h4 class="clr-config">Modifica la località #{{ form.instance.pk }}</h4>
+ {% else %}
+ <h4 class="clr-config">Registra una nuova località nel sistema</h4>
+ {% endif %}
+ </div>
+
+ <div class="card-body">
+ <form method="POST" id="localita_form" action="{% url 'sistema:localita' %}">{% csrf_token %}
+ {% if form.instance.pk %}
+ <input type="hidden" name="mod" form="localita_form">
+ <input type="hidden" name="pk" value="{{ form.instance.pk }}" form="localita_form">
+ {% endif %}
+ {{form | crispy }}
+ <br>
+ <div class="card-footer text-right">
+ <button class="btn btn-primary" type="button" onclick="javascript:this.form.submit();"><i class="fas fa-save" aria-hidden="true"></i> Salva
+ </button>
+ </div>
+
+ </form>
+ </div>
+
+ </div>
+ </div>
+ </div>
+
+
+ <script>
+
+ </script>
+
+
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% load crispy_forms_tags %}
+{% block content %}
+
+ <div class="row">
+ <div class="col-12 col-md-12 col-lg-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-cogs clr-bolle mr-2" aria-hidden="true"></i> Tipologia multimedia </h1>
+ <div class="card">
+ <div class="card-header">
+ {% if form.instance.pk %}
+ <h4 class="clr-config">Modifica la tipologia #{{ form.instance.pk }}</h4>
+ {% else %}
+ <h4 class="clr-config">Registra una nuova tipologia di multimedia nel sistema</h4>
+ {% endif %}
+ </div>
+
+ <div class="card-body">
+ <form method="POST" id="tipo_multimedia_form" action="{% url 'sistema:tipo_multimedia' %}">{% csrf_token %}
+ {% if form.instance.pk %}
+ <input type="hidden" name="mod" form="tipo_multimedia_form">
+ <input type="hidden" name="pk" value="{{ form.instance.pk }}" form="tipo_multimedia_form">
+ {% endif %}
+ {{form | crispy }}
+ <br>
+ <div class="card-footer text-right">
+ <button class="btn btn-primary" type="button" onclick="javascript:this.form.submit();"><i class="fas fa-save" aria-hidden="true"></i> Salva
+ </button>
+ </div>
+
+ </form>
+ </div>
+
+ </div>
+ </div>
+ </div>
+
+
+ <script>
+
+ </script>
+
+
+{% endblock %}
\ No newline at end of file
</div>
<ul class="sidebar-menu mt-3">
<li class="dropdown voce_menu active" id="home">
- <a href="#" class="nav-link color-home">
+ <a href="{% url 'sistema:home' %}" class="nav-link color-home">
<i class="fa-solid fa-house"></i><span>Home</span></a>
</li>
- <li class="menu-header">Piattaforma</li>
- <li class="dropdown voce_menu" id="clienti-fornitori-menu">
- <a href="#" class="nav-link color-clients">
- <i class="fa-solid fa-people-carry-box"></i><span>Clienti / Fornitori</span></a>
+ <li class="menu-header">Sistema</li>
+
+ <li class="dropdown">
+ <a href="#" class="menu-toggle nav-link has-dropdown color-clients">
+ <i class="fa-solid fa-users"></i><span>Utenti</span></a>
+
+ <ul class="dropdown-menu" id="menu-drop">
+ <li><a class="nav-link" href="{% url 'utenti:admin_list' %}">Amministratori</a></li>
+ <li><a class="nav-link" href="{% url 'utenti:touroperator_list' %}">Tour operator</a></li>
+ <li><a class="nav-link" href="{% url 'utenti:clienti_list' %}">Clienti</a></li>
+ </ul>
+
</li>
+
<li class="dropdown voce_menu" id="dipendenti-menu">
- <a href="#" class="nav-link color-dipendenti">
- <i class="fa-solid fa-users"></i><span>Dipendenti</span></a>
+ <a href="{% url 'sistema:localita_list' %}" class="nav-link color-dipendenti">
+ <i class="fa-solid fa-tree-city"></i><span>Località</span></a>
+ </li>
+
+ <li class="dropdown">
+ <a href="#" class="menu-toggle nav-link has-dropdown color-config">
+ <i class="fa-solid fa-location-dot"></i><span>Punti di interesse</span></a>
+
+ <ul class="dropdown-menu" id="menu-drop">
+ <li><a class="nav-link" href="{% url 'sistema:poi_list' %}">Lista punti di interesse</a></li>
+ <li><a class="nav-link" href="{% url 'sistema:tipo_multimedia_list' %}">Tipologia multimedia</a></li>
+ <li><a class="nav-link" href="#">Multimedia</a></li>
+ </ul>
+ </li>
+
+ <li class="dropdown voce_menu" id="poi-menu">
+ <a href="#" class="nav-link color-listini">
+ <i class="fa-solid fa-route"></i><span>Percorsi</span></a>
</li>
</ul>
</aside>
{% extends 'base.html' %}
{% load static %}
{% block content %}
- CIAO
+ CIAO, {{ request.session.roles }}
{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% block content %}
+ <div class="row">
+ <div class="col-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-tree-city clr-bolle mr-2" aria-hidden="true"></i> Gestione località </h1>
+
+ <div class="card">
+ <div class="card-header justify-content-between">
+ <h4 class="clr-bolle">Lista località</h4>
+ <a href="{% url 'sistema:localita' %}">
+ <button class="btn-sm btn-primary"><i class="fa-solid fa-plus"></i> Aggiungi una nuova località</button>
+ </a>
+ </div>
+
+ <div class="card-body">
+ <div class="row">
+ <div class="table-responsive">
+ <table id="localita-table"
+ class="table table-sm table-bordered table-striped dataTables_wrapper dt-bootstrap4 no-footer" style="width: 100% !important;">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Nome</th>
+ <th>Provincia</th>
+ <th>Cap</th>
+ <th>Regione</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <script>
+ $(document).ready(function () {
+ $('#localita-table').dataTable({
+ processing: true,
+ serverSide: true,
+ ajax: {
+ "url": "{% url 'sistema:localita_datatables' %}",
+ "type": 'GET',
+ },
+ language: {
+ url: "//cdn.datatables.net/plug-ins/1.10.20/i18n/Italian.json"
+ },
+ columnDefs: [
+ {"targets": 5, "orderable": false, "width": "5%"},
+ {"targets": 6, "orderable": false, "width": "5%"},
+ ],
+ "order": []
+ });
+ });
+
+ function DeleteLocalita(pk){
+ Swal.fire({
+ title: "Sei sicuro?",
+ text: "Una volta eliminata non sarà più possibile accedere all'informazione!",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ confirmButtonText: 'Conferma',
+ cancelButtonText: 'Annulla',
+ }).then((willDelete) => {
+ if (willDelete.value) {
+ $.ajax({
+ type: 'GET',
+ data: {
+ 'pk': pk,
+ 'method': 'del',
+ },
+ url: '{% url 'sistema:localita' %}',
+ success: function (response) {
+ Swal.fire("Fatto!", response.response, "success")
+ .then((value) => {
+ location.reload();
+ });
+ },
+ error: () => {
+ Swal.fire("Attenzione!", response.response, "error")
+ }
+ })
+ } else if (
+ willDelete.dismiss === Swal.DismissReason.cancel
+ ) {
+ Swal.fire(
+ 'Annullato',
+ 'Rimozione annullata.',
+ 'error'
+ )
+ }
+ });
+ }
+
+ </script>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% block content %}
+ <div class="row">
+ <div class="col-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-photo-film clr-bolle mr-2" aria-hidden="true"></i> Gestione multimedia </h1>
+
+ <div class="card">
+ <div class="card-header justify-content-between">
+ <h4 class="clr-bolle">Lista multimedia</h4>
+ <a href="#">
+ <button class="btn-sm btn-primary"><i class="fa-solid fa-plus"></i> Aggiungi una multimedia</button>
+ </a>
+ </div>
+
+ <div class="card-body">
+ <div class="row">
+ <div class="table-responsive">
+ <table id="multimedia-table"
+ class="table table-sm table-bordered table-striped dataTables_wrapper dt-bootstrap4 no-footer" style="width: 100% !important;">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Tipologia</th>
+ <th>Nome</th>
+ <th>Descrizione</th>
+ <th>Testo</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <script>
+ $(document).ready(function () {
+ $('#multimedia-table').dataTable({
+ processing: true,
+ serverSide: true,
+ ajax: {
+ "url": "{% url 'utenti:user_datatables' %}",
+ "type": 'GET',
+ },
+ language: {
+ url: "//cdn.datatables.net/plug-ins/1.10.20/i18n/Italian.json"
+ },
+ columnDefs: [
+ {"targets": 5, "orderable": false},
+ {"targets": 6, "orderable": false},
+ ],
+ "order": []
+ });
+ });
+ </script>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Title</title>
+</head>
+<body>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% block content %}
+ <div class="row">
+ <div class="col-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-location-dot clr-bolle mr-2" aria-hidden="true"></i> Gestione punti di interesse </h1>
+
+ <div class="card">
+ <div class="card-header justify-content-between">
+ <h4 class="clr-bolle">Lista punti di interesse</h4>
+ <a href="{% url 'sistema:poi' %}">
+ <button class="btn-sm btn-primary"><i class="fa-solid fa-plus"></i> Aggiungi una nuovo punto di interesse</button>
+ </a>
+ </div>
+
+ <div class="card-body">
+ <div class="row">
+ <div class="table-responsive">
+ <table id="poi-table"
+ class="table table-sm table-bordered table-striped dataTables_wrapper dt-bootstrap4 no-footer" style="width: 100% !important;">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Nome</th>
+ <th>Latitudine</th>
+ <th>Longitudine</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <script>
+ $(document).ready(function () {
+ $('#poi-table').dataTable({
+ processing: true,
+ serverSide: true,
+ ajax: {
+ "url": "{% url 'sistema:poi_datatables' %}",
+ "type": 'GET',
+ },
+ language: {
+ url: "//cdn.datatables.net/plug-ins/1.10.20/i18n/Italian.json"
+ },
+ columnDefs: [
+ {"targets": 4, "orderable": false, "width": "5%"},
+ {"targets": 5, "orderable": false, "width": "5%"},
+ ],
+ "order": []
+ });
+ });
+
+ function DeletePoi(pk){
+ Swal.fire({
+ title: "Sei sicuro?",
+ text: "Una volta eliminata non sarà più possibile accedere all'informazione!",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ confirmButtonText: 'Conferma',
+ cancelButtonText: 'Annulla',
+ }).then((willDelete) => {
+ if (willDelete.value) {
+ $.ajax({
+ type: 'GET',
+ data: {
+ 'pk': pk,
+ 'method': 'del',
+ },
+ url: '{% url 'sistema:poi' %}',
+ success: function (response) {
+ Swal.fire("Fatto!", response.response, "success")
+ .then((value) => {
+ location.reload();
+ });
+ },
+ error: () => {
+ Swal.fire("Attenzione!", response.response, "error")
+ }
+ })
+ } else if (
+ willDelete.dismiss === Swal.DismissReason.cancel
+ ) {
+ Swal.fire(
+ 'Annullato',
+ 'Rimozione annullata.',
+ 'error'
+ )
+ }
+ });
+ }
+
+ </script>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% block content %}
+ <div class="row">
+ <div class="col-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-cogs clr-bolle mr-2" aria-hidden="true"></i> Gestione tipologia multimedia </h1>
+
+ <div class="card">
+ <div class="card-header justify-content-between">
+ <h4 class="clr-bolle">Lista tipologie di multimedia</h4>
+ <a href="{% url 'sistema:tipo_multimedia' %}">
+ <button class="btn-sm btn-primary"><i class="fa-solid fa-plus"></i> Aggiungi una nuova tipologia</button>
+ </a>
+ </div>
+
+ <div class="card-body">
+ <div class="row">
+ <div class="table-responsive">
+ <table id="tipo_multimedia-table"
+ class="table table-sm table-bordered table-striped dataTables_wrapper dt-bootstrap4 no-footer" style="width: 100% !important;">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Nome</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <script>
+ $(document).ready(function () {
+ $('#tipo_multimedia-table').dataTable({
+ processing: true,
+ serverSide: true,
+ ajax: {
+ "url": "{% url 'sistema:tipo_multimedia_datatables' %}",
+ "type": 'GET',
+ },
+ language: {
+ url: "//cdn.datatables.net/plug-ins/1.10.20/i18n/Italian.json"
+ },
+ columnDefs: [
+ {"targets": 2, "orderable": false, "width": "5%"},
+ {"targets": 3, "orderable": false, "width": "5%"},
+ ],
+ "order": []
+ });
+ });
+
+ function DeleteTipologia(pk){
+ Swal.fire({
+ title: "Sei sicuro?",
+ text: "Una volta eliminata non sarà più possibile accedere all'informazione!",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ confirmButtonText: 'Conferma',
+ cancelButtonText: 'Annulla',
+ }).then((willDelete) => {
+ if (willDelete.value) {
+ $.ajax({
+ type: 'GET',
+ data: {
+ 'pk': pk,
+ 'method': 'del',
+ },
+ url: '{% url 'sistema:tipo_multimedia' %}',
+ success: function (response) {
+ Swal.fire("Fatto!", response.response, "success")
+ .then((value) => {
+ location.reload();
+ });
+ },
+ error: () => {
+ Swal.fire("Attenzione!", response.response, "error")
+ }
+ })
+ } else if (
+ willDelete.dismiss === Swal.DismissReason.cancel
+ ) {
+ Swal.fire(
+ 'Annullato',
+ 'Rimozione annullata.',
+ 'error'
+ )
+ }
+ });
+ }
+
+ </script>
+{% endblock %}
\ No newline at end of file
from django.urls import path
-from sistema.views import Home
+from sistema.datatables import LocalitaDatatables, MultimediaDatatables, TipoMultimediaDatatables, PercorsoDatatables, \
+ PoiDatatables, FeedbackDatatables
+from sistema.views import Home, LocalitaListView, MultimediaListView, PuntiInteresseListView, \
+ TipologiaMultimediaListView, PercorsiListView, LocalitaView, TipoMultimediaView, PoiView
urlpatterns = [
- path('home/', Home.as_view(), name='home'),
+ path('', Home.as_view(), name='home'),
+
+ ## TEMPLATEVIEWS ##
+ path('localita_list/', LocalitaListView.as_view(), name='localita_list'),
+ path('multimedia_list/', MultimediaListView.as_view(), name='multimedia_list'),
+ path('poi_list/', PuntiInteresseListView.as_view(), name='poi_list'),
+ path('tipo_multimedia_list/', TipologiaMultimediaListView.as_view(), name='tipo_multimedia_list'),
+ path('percorsi_list/', PercorsiListView.as_view(), name='percorsi_list'),
+
+ ## DATATABLES ##
+ path('localita_datatables/', LocalitaDatatables.as_view(), name='localita_datatables'),
+ path('multimedia_datatables/', MultimediaDatatables.as_view(), name='multimedia_datatables'),
+ path('tipo_multimedia_datatables/', TipoMultimediaDatatables.as_view(), name='tipo_multimedia_datatables'),
+ path('percorso_datatables/', PercorsoDatatables.as_view(), name='percorso_datatables'),
+ path('poi_datatables/', PoiDatatables.as_view(), name='poi_datatables'),
+ path('feedback_datatables/', FeedbackDatatables.as_view(), name='feedback_datatables'),
+
+ ## LOCALITA ##
+ path('localita/', LocalitaView.as_view(), name='localita'),
+ path('mod_localita/<int:pk>/', LocalitaView.as_view(), name='mod_localita'),
+
+ ## TIPOLOGIA MODALITA ##
+ path('tipo_multimedia/', TipoMultimediaView.as_view(), name='tipo_multimedia'),
+ path('mod_tipo_multimedia/<int:pk>/', TipoMultimediaView.as_view(), name='mod_tipo_multimedia'),
+
+ ## PUNTO DI INTERESSE ##
+ path('poi/', PoiView.as_view(), name='poi'),
+ path('mod_poi/<int:pk>/', PoiView.as_view(), name='mod_poi'),
+
+
]
-from django.shortcuts import render
+from django.contrib import messages
+from django.http import JsonResponse
+from django.shortcuts import render, redirect
+from django.urls import reverse
from django.views import View
+from django.views.generic import TemplateView
+from rest_framework import status
+from sistema.forms import LocalitaForm, TipoMultimediaForm
+from sistema.models import Localita, TipologiaMultimedia
from utenti.mixins import CustomLoginRequiredMixin
-class Home(View):#CustomLoginRequiredMixin
+class LocalitaListView(TemplateView):
+ template_name = 'localita_list.html'
+
+
+class PuntiInteresseListView(TemplateView):
+ template_name = 'poi_list.html'
+
+
+class TipologiaMultimediaListView(TemplateView):
+ template_name = 'tipo_multimedia_list.html'
+
+
+class MultimediaListView(TemplateView):
+ template_name = 'multimedia_list.html'
+
+
+class PercorsiListView(TemplateView):
+ template_name = 'percorsi_list.html'
+
+
+class Home(View): # CustomLoginRequiredMixin
def get(self, request):
return render(request, 'home.html', {})
+
+
+class LocalitaView(View):
+ def add_localita(self, request):
+ form = LocalitaForm(request.POST)
+ if form.is_valid():
+ form.save()
+
+ messages.add_message(request, messages.INFO, 'Nuova località inserita con successo.')
+ else:
+ errors = ''
+ for err in form.errors.as_data():
+ errors += '<li>' + err + '</li>'
+ messages.add_message(request, messages.ERROR, 'Errore nell\'inserimento della località. Controlla il form.')
+
+ def mod_localita(self, request):
+ form = LocalitaForm(request.POST, instance=Localita.objects.get(pk=int(self.request.POST.get('pk'))))
+ if form.is_valid():
+ form.save()
+
+ messages.add_message(request, messages.INFO, 'Località aggiornata con successo.')
+ else:
+ errors = ''
+ for err in form.errors.as_data():
+ errors += '<li>' + err + '</li>'
+ messages.add_message(request, messages.ERROR, 'Errore nell\'aggiornamento della località. Controlla il form.')
+
+ def del_localita(self, request):
+ localita = Localita.objects.get(pk=int(request.GET.get('pk')))
+ localita.is_active = False
+ localita.save()
+
+ def get(self, request, *args, **kwargs):
+ if request.GET.get('method') == 'del':
+ self.del_localita(request)
+
+ return JsonResponse({'response': 'Località eliminata con successo'}, status=status.HTTP_200_OK)
+ elif 'pk' in kwargs:
+ return render(request, 'add_mod_localita.html', {'form': LocalitaForm(instance=Localita.objects.get(pk=int(self.kwargs['pk'])))})
+ else:
+ return render(request, 'add_mod_localita.html', {'form': LocalitaForm()})
+
+ def post(self, request, *args, **kwargs):
+ if 'mod' in request.POST:
+ self.mod_localita(request)
+ else:
+ self.add_localita(request)
+
+ return redirect(reverse('sistema:localita_list'))
+
+
+class TipoMultimediaView(View):
+ def add_tipologia(self, request):
+ form = TipoMultimediaForm(request.POST)
+ if form.is_valid():
+ form.save()
+
+ messages.add_message(request, messages.INFO, 'Nuova tipologia inserita con successo.')
+ else:
+ errors = ''
+ for err in form.errors.as_data():
+ errors += '<li>' + err + '</li>'
+ messages.add_message(request, messages.ERROR, 'Errore nell\'inserimento della tipologia. Controlla il form.')
+
+ def mod_tipologia(self, request):
+ form = TipoMultimediaForm(request.POST, instance=TipologiaMultimedia.objects.get(pk=int(self.request.POST.get('pk'))))
+ if form.is_valid():
+ form.save()
+
+ messages.add_message(request, messages.INFO, 'Tipologia aggiornata con successo.')
+ else:
+ errors = ''
+ for err in form.errors.as_data():
+ errors += '<li>' + err + '</li>'
+ messages.add_message(request, messages.ERROR, 'Errore nell\'aggiornamento della tipologia. Controlla il form.')
+
+ def del_tipologia(self, request):
+ tipologia = TipologiaMultimedia.objects.get(pk=int(request.GET.get('pk')))
+ tipologia.is_active = False
+ tipologia.save()
+
+ def get(self, request, *args, **kwargs):
+ if request.GET.get('method') == 'del':
+ self.del_tipologia(request)
+
+ return JsonResponse({'response': 'Tipologia eliminata con successo'}, status=status.HTTP_200_OK)
+ elif 'pk' in kwargs:
+ return render(request, 'add_mod_tipologia.html', {'form': TipoMultimediaForm(instance=TipologiaMultimedia.objects.get(pk=int(self.kwargs['pk'])))})
+ else:
+ return render(request, 'add_mod_tipologia.html', {'form': TipoMultimediaForm()})
+
+ def post(self, request, *args, **kwargs):
+ if 'mod' in request.POST:
+ self.mod_tipologia(request)
+ else:
+ self.add_tipologia(request)
+
+ return redirect(reverse('sistema:tipo_multimedia_list'))
+
+
+class PoiView(View):
+ def add_poi(self, request):
+ form = TipoMultimediaForm(request.POST)
+ if form.is_valid():
+ form.save()
+
+ messages.add_message(request, messages.INFO, 'Nuova punto di interesse inserito con successo.')
+ else:
+ errors = ''
+ for err in form.errors.as_data():
+ errors += '<li>' + err + '</li>'
+ messages.add_message(request, messages.ERROR, 'Errore nell\'inserimento del punto di interesse. Controlla il form.')
+
+ def mod_poi(self, request):
+ form = TipoMultimediaForm(request.POST, instance=TipologiaMultimedia.objects.get(pk=int(self.request.POST.get('pk'))))
+ if form.is_valid():
+ form.save()
+
+ messages.add_message(request, messages.INFO, 'Punto di interesse aggiornata con successo.')
+ else:
+ errors = ''
+ for err in form.errors.as_data():
+ errors += '<li>' + err + '</li>'
+ messages.add_message(request, messages.ERROR, 'Errore nell\'aggiornamento del punto di interesse. Controlla il form.')
+
+ def del_poi(self, request):
+ tipologia = TipologiaMultimedia.objects.get(pk=int(request.GET.get('pk')))
+ tipologia.is_active = False
+ tipologia.save()
+
+ def get(self, request, *args, **kwargs):
+ if request.GET.get('method') == 'del':
+ self.del_poi(request)
+
+ return JsonResponse({'response': 'Punto di interesse eliminato con successo'}, status=status.HTTP_200_OK)
+ elif 'pk' in kwargs:
+ return render(request, 'add_mod_tipologia.html', {'form': TipoMultimediaForm(instance=TipologiaMultimedia.objects.get(pk=int(self.kwargs['pk'])))})
+ else:
+ return render(request, 'add_mod_tipologia.html', {'form': TipoMultimediaForm()})
+
+ def post(self, request, *args, **kwargs):
+ if 'mod' in request.POST:
+ self.mod_poi(request)
+ else:
+ self.add_poi(request)
+
+ return redirect(reverse('sistema:tipo_multimedia_list'))
\ No newline at end of file
'ENGINE': 'django.db.backends.mysql',
'NAME': 'socoin_atlas',
'USER': 'root',
- 'PASSWORD': "password",
+ 'PASSWORD': "",
'HOST': '127.0.0.1', # Or an IP Address that your DB is hosted on
'PORT': '3306',
'AUTOCOMMIT': True,
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
-CLIENTI_GROUPS = 'CLIENTI'
+CLIENTI_GROUPS = 'CLIENTE'
TOUR_OPERATOR_GROUPS = 'TOUR_OPERATOR'
ADMIN_GROUPS = 'ADMIN'
\ No newline at end of file
--- /dev/null
+from django.contrib.auth.models import User
+from django_datatables_view.base_datatable_view import BaseDatatableView
+from django.utils.html import escape
+from django.db.models import Q
+
+from socoin_atlas import settings
+
+
+class UserDatatables(BaseDatatableView):
+ model = User
+ columns = ['id', 'first_name', 'last_name', 'username', 'email', 'last_login', 'mod', 'del']
+ order_columns = ['id', 'first_name', 'last_name', 'username', 'email', 'last_login', '', '']
+
+ def get_initial_queryset(self):
+ role = self.request.GET.get('role')
+
+ if role:
+ if role == settings.ADMIN_GROUPS:
+ return self.model.objects.filter(groups__name=settings.ADMIN_GROUPS, is_active=True)
+ elif role == settings.TOUR_OPERATOR_GROUPS:
+ return self.model.objects.filter(groups__name=settings.TOUR_OPERATOR_GROUPS, is_active=True)
+ elif role == settings.CLIENTI_GROUPS:
+ return self.model.objects.filter(groups__name=settings.CLIENTI_GROUPS, is_active=True)
+ else:
+ return self.model.objects.none()
+
+ def render_column(self, row, column):
+ if column == 'id':
+ return escape('{0}'.format(row.pk))
+ if column == 'first_name':
+ return escape('{0}'.format(row.first_name))
+ if column == 'last_name':
+ return escape('{0}'.format(row.last_name))
+ if column == 'username':
+ return escape('{0}'.format(row.username))
+ if column == 'email':
+ return escape('{0}'.format(row.email) or '')
+ if column == 'last_login':
+ return escape('{0}'.format(row.last_login or ''))
+ if column == 'mod':
+ return '<a class="btn btn-sm btn-primary" href="#" ><i class="fas fa-edit"></i></a>' #% row.pk
+ if column == 'del':
+ return '<a class="btn btn-sm btn-danger" href="#" ><i class="fas fa-trash"></i></a>' #% row.pk
+ else:
+ return super(UserDatatables, self).render_column(row, column)
+
+ def filter_queryset(self, qs):
+ search = self.request.GET.get('search[value]', None)
+ if search:
+ qs = qs.filter(Q(first_name__icontains=search) | Q(last_name__icontains=search)
+ | Q(username__icontains=search) | Q(email__icontains=search))
+ return qs
\ No newline at end of file
class RegistrationForm(UserCreationForm):
class Meta:
model = User
- fields = ['username', 'first_name', 'last_name', 'password1', 'password2']
+ fields = ['username', 'first_name', 'last_name', 'email', 'password1', 'password2']
def save(self, commit=True):
user = super().save(commit=False)
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% block content %}
+ <div class="row">
+ <div class="col-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-crown clr-bolle mr-2" aria-hidden="true"></i> Gestione amministratori</h1>
+
+ <div class="card">
+ <div class="card-header justify-content-between">
+ <h4 class="clr-bolle">Lista amministratori</h4>
+ <a href="#">
+ <button class="btn-sm btn-primary"><i class="fa-solid fa-plus"></i> Aggiungi un admin</button>
+ </a>
+ </div>
+
+ <div class="card-body">
+ <div class="row">
+ <div class="table-responsive">
+ <table id="admin-table"
+ class="table table-sm table-bordered table-striped dataTables_wrapper dt-bootstrap4 no-footer" style="width: 100% !important;">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Nome</th>
+ <th>Cognome</th>
+ <th>Username</th>
+ <th>Email</th>
+ <th>Ultimo accesso</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <script>
+ $(document).ready(function () {
+ $('#admin-table').dataTable({
+ processing: true,
+ serverSide: true,
+ ajax: {
+ "url": "{% url 'utenti:user_datatables' %}",
+ "type": 'GET',
+ "data": {'role': 'ADMIN' },
+ },
+ language: {
+ url: "//cdn.datatables.net/plug-ins/1.10.20/i18n/Italian.json"
+ },
+ columnDefs: [
+ {"targets": 6, "orderable": false},
+ {"targets": 7, "orderable": false},
+ ],
+ "order": []
+ });
+ });
+ </script>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% block content %}
+ <div class="row">
+ <div class="col-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-user clr-bolle mr-2" aria-hidden="true"></i> Gestione clienti</h1>
+
+ <div class="card">
+ <div class="card-header justify-content-between">
+ <h4 class="clr-bolle">Lista clienti</h4>
+ <a href="#">
+ <button class="btn-sm btn-primary"><i class="fa-solid fa-plus"></i> Aggiungi un nuovo cliente</button>
+ </a>
+ </div>
+
+ <div class="card-body">
+ <div class="row">
+ <div class="table-responsive">
+ <table id="cliente-table"
+ class="table table-sm table-bordered table-striped dataTables_wrapper dt-bootstrap4 no-footer" style="width: 100% !important;">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Nome</th>
+ <th>Cognome</th>
+ <th>Username</th>
+ <th>Email</th>
+ <th>Ultimo accesso</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <script>
+ $(document).ready(function () {
+ $('#cliente-table').dataTable({
+ processing: true,
+ serverSide: true,
+ ajax: {
+ "url": "{% url 'utenti:user_datatables' %}",
+ "type": 'GET',
+ "data": {'role': 'CLIENTE' },
+ },
+ language: {
+ url: "//cdn.datatables.net/plug-ins/1.10.20/i18n/Italian.json"
+ },
+ columnDefs: [
+ {"targets": 6, "orderable": false},
+ {"targets": 7, "orderable": false},
+ ],
+ "order": []
+ });
+ });
+ </script>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% block content %}
+ <div class="row">
+ <div class="col-12">
+ <h1 class="line-bolle"><i class="fa-solid fa-handshake-angle clr-bolle mr-2" aria-hidden="true"></i> Gestione tour operator</h1>
+
+ <div class="card">
+ <div class="card-header justify-content-between">
+ <h4 class="clr-bolle">Lista tour operator</h4>
+ <a href="#">
+ <button class="btn-sm btn-primary"><i class="fa-solid fa-plus"></i> Aggiungi un nuovo tour operator</button>
+ </a>
+ </div>
+
+ <div class="card-body">
+ <div class="row">
+ <div class="table-responsive">
+ <table id="tour_operator-table"
+ class="table table-sm table-bordered table-striped dataTables_wrapper dt-bootstrap4 no-footer" style="width: 100% !important;">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Nome</th>
+ <th>Cognome</th>
+ <th>Username</th>
+ <th>Email</th>
+ <th>Ultimo accesso</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <script>
+ $(document).ready(function () {
+ $('#tour_operator-table').dataTable({
+ processing: true,
+ serverSide: true,
+ ajax: {
+ "url": "{% url 'utenti:user_datatables' %}",
+ "type": 'GET',
+ "data": {'role': 'TOUR_OPERATOR' },
+ },
+ language: {
+ url: "//cdn.datatables.net/plug-ins/1.10.20/i18n/Italian.json"
+ },
+ columnDefs: [
+ {"targets": 6, "orderable": false},
+ {"targets": 7, "orderable": false},
+ ],
+ "order": []
+ });
+ });
+ </script>
+{% endblock %}
\ No newline at end of file
from django.contrib.auth import views as auth_views
-from utenti.views import Autentication, Logout, Register
+from utenti.datatables import UserDatatables
+from utenti.views import Autentication, Logout, Register, AdminListView, TourOperatorListView, ClientiListView
urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')),
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name='registration/custom_password_reset_complete.html'),name='password_reset_complete'),
path('register/', Register.as_view(), name='register'),
+ ## TEMPLATEVIEWS ##
+ path('admin_list/', AdminListView.as_view(), name='admin_list'),
+ path('touroperator_list/', TourOperatorListView.as_view(), name='touroperator_list'),
+ path('clienti_list/', ClientiListView.as_view(), name='clienti_list'),
+
+ ## DATATABLES ##
+ path('user_datatables/', UserDatatables.as_view(), name='user_datatables'),
+
+
]
\ No newline at end of file
from django.views import View
from django.contrib import messages
from django.contrib.auth import login, authenticate, logout
+from django.views.generic import TemplateView
from utenti.forms import FormLogin, RegistrationForm
form.save()
return redirect('utenti:login')
else:
- messages.error(request, 'Please correct form and try again')
+ errors = ''
+ for err in form.errors.as_data():
+ errors += '<li>' + err + '</li>'
+
+ messages.error(request, errors)
form = RegistrationForm()
+ return redirect('utenti:register')
+
+
+class AdminListView(TemplateView):
+ template_name = 'admin_list.html'
+
+
+class TourOperatorListView(TemplateView):
+ template_name = 'tour_operator_list.html'
+
+
+class ClientiListView(TemplateView):
+ template_name = 'clienti_list.html'
# from braces.views import GroupRequiredMixin
# class SomeProtectedView(GroupRequiredMixin, TemplateView):