<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/env" />
</content>
- <orderEntry type="jdk" jdkName="Python 3.7 (socoin_atlas)" jdkType="Python SDK" />
+ <orderEntry type="jdk" jdkName="Python 3.11 (pythonProject3)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
--- /dev/null
+{% extends 'base.html' %}
+{% block content %}
+<div class="container">
+ <h1>Checkout per {{ percorso.nome }}</h1>
+ <p>Prezzo: {{ percorso.prezzo|floatformat:2 }} €</p>
+ <form method="POST">
+ {% csrf_token %}
+ <button type="submit" class="btn btn-success">Procedi al pagamento</button>
+ </form>
+</div>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% block content %}
+<div class="container">
+ <h1>Pagamento riuscito!</h1>
+ <p>Grazie per il tuo acquisto.</p>
+</div>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+from django.urls import path
+from .views import CheckoutView, PaymentSuccessView, PaymentCancelView
+
+app_name = 'pagamenti'
+
+urlpatterns = [
+ path('checkout/<int:percorso_id>/', CheckoutView.as_view(), name='checkout'), # Checkout page
+ path('success/', PaymentSuccessView.as_view(), name='payment_success'), # Payment success
+ path('cancel/', PaymentCancelView.as_view(), name='payment_cancel'), # Payment canceled
+]
is_active = models.BooleanField(default=True)
+class Pagamenti(models.Model):
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ percorso = models.ForeignKey(Percorso, on_delete=models.CASCADE)
+ data_acquisto = models.DateTimeField(auto_now_add=True)
+
+
class Tappa(models.Model):
percorso = models.ForeignKey(Percorso, on_delete=models.DO_NOTHING)
poi = models.ForeignKey(PointOfInterest, on_delete=models.DO_NOTHING)
<i class="fas fa-route" style="padding: 6px 28px 7px 6px;"></i><span>Percorsi</span></a>
</li>
+ <li class="dropdown voce_menu"> <!-- id="tuoi-percorsi" -->
+ <a href="{% url 'sistema:tuoi_percorsi_list' %}" class="nav-link">
+ <i class="fas fa-route" style="padding: 6px 28px 7px 6px;"></i><span>Tuoi Percorsi</span></a>
+ </li>
+
+
</ul>
</aside>
</div>
<div class="row">
<div class="col-12 col-md-12 col-lg-12">
<h1 class="line-listini"><i class="fa-solid fa-route clr-listini mr-2" aria-hidden="true"></i> Dettaglio percorso </h1>
+
+ <!-- Campo di ricerca -->
+ <div class="form-group">
+ <input type="text" id="search-filter" class="form-control" placeholder="Cerca tra i percorsi">
+ </div>
+
<div class="card">
<div class="card-header">
<h4 class="clr-config">I tuoi percorsi</h4>
</div>
<div class="card-body">
- <div class="row">
-
+ <div class="row" id="paid-routes">
+ {% for percorso_pagato in list_pagati %}
+ <div class="card p-3 route__filter" style="width: 18rem;">
+ <img class="card-img-top" src="{% static 'assets/img/logo.png' %}">
+ <div class="card-body">
+ <h5 class="card-title route__text">{{ percorso_pagato.nome}}</h5>
+ <p class="card-text route__text">{{ percorso_pagato.descrizione }}</p>
+ </div>
+ <div class="card-footer">
+ <div class="d-flex justify-content-between">
+ <h4>{{ percorso_pagato.prezzo|floatformat:2 }} €</h4>
+ </div>
+ </div>
+ <a href="{% url 'sistema:percorso_info' percorso_pagato.pk %}" class="btn btn-warning rounded-4">Dettaglio percorso</a>
+ </div>
+ {% empty %}
+ <p>Non possiedi ancora alcun Percorso.</p>
+ {% endfor %}
</div>
</div>
</div>
<h4 class="clr-config">Percorsi che puoi acquistare</h4>
</div>
<div class="card-body">
- <div class="row">
+ <div class="row" id="available-routes">
{% for percorso in list_percorsi %}
- <div class="card" style="width: 18rem;">
+ <div class="card p-3 route__filter" style="width: 18rem;">
<img class="card-img-top" src="{% static 'assets/img/logo.png' %}">
<div class="card-body">
- <h5 class="card-title">{{ percorso.nome}}</h5>
- <p class="card-text">{{ percorso.descrizione }}</p>
+ <h5 class="card-title route__text">{{ percorso.nome}}</h5>
+ <p class="card-text route__text">{{ percorso.descrizione }}</p>
</div>
<div class="card-footer">
<div class="d-flex justify-content-between">
<h4>{{ percorso.prezzo|floatformat:2 }} €</h4>
- <a href="#" class="btn btn-success">Compralo subito</a>
+ <a href="{% url 'pagamenti:checkout' percorso.pk %}" class="btn btn-success">Acquista ora</a>
</div>
</div>
- <a href="{% url 'sistema:percorso_info' percorso.pk %}" class="stretched-link"></a>
+ <a href="{% url 'sistema:percorso_info' percorso.pk %}" class="btn btn-warning rounded-4">Dettaglio percorso</a>
</div>
+ {% empty %}
+ <p>Non ci sono percorsi disponibili per l'acquisto.</p>
{% endfor %}
</div>
</div>
</div>
</div>
+
+
+
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ // Assicurati che il DOM sia completamente caricato prima di eseguire il codice
+ const input = document.getElementById('search-filter');
+
+ // Verifica che l'elemento di input esista nel DOM
+ if (input) {
+ // Aggiungi un listener sull'input per avviare la ricerca durante la digitazione
+ input.addEventListener('input', search);
+ } else {
+ // Se non viene trovato, segnala in console
+ console.error("Input non trovato");
+ }
+});
+
+const search = (e) => {
+ // Ottieni il valore attuale dell'input e convertilo in minuscolo
+ const inputValue = e.target.value.toLowerCase();
+
+ // Seleziona i contenitori delle card per i percorsi pagati e disponibili
+ const paidRoutesContainer = document.getElementById('paid-routes');
+ const availableRoutesContainer = document.getElementById('available-routes');
+
+ // Verifica che i contenitori esistano prima di procedere
+ if (!paidRoutesContainer || !availableRoutesContainer) {
+ console.error("Contenitori di percorsi non trovati");
+ return;
+ }
+
+ // Seleziona le card individuali delle due sezioni
+ const paidRoutes = [...paidRoutesContainer.getElementsByClassName('route__filter')];
+ const availableRoutes = [...availableRoutesContainer.getElementsByClassName('route__filter')];
+
+ // Funzione per filtrare le card in base al testo di ricerca
+ const filterCards = (routes) => {
+ for (let i = 0; i < routes.length; i++) {
+ // Prendi il testo da h5 (titolo) e p (descrizione)
+ let searchTerm = [...routes[i].querySelectorAll("h5.card-title, p.card-text")];
+ searchTerm = searchTerm.map(item => item.innerHTML.toLowerCase());
+
+ // Verifica se l'input di ricerca è contenuto nel titolo o nella descrizione della card
+ if (searchTerm.map(text => text.includes(inputValue)).some(val => val)) {
+ // Mostra la card se il testo combacia
+ routes[i].style.display = 'block';
+ } else {
+ // Nascondi la card se il testo non combacia
+ routes[i].style.display = 'none';
+ }
+ }
+ }
+
+ // Applica il filtro alle due sezioni
+ filterCards(paidRoutes);
+ filterCards(availableRoutes);
+}
+</script>
+
+
+
{% endblock %}
\ No newline at end of file
--- /dev/null
+{% extends 'base.html' %}
+{% load static %}
+{% block content %}
+
+<div class="row">
+ <div class="col-12 col-md-12 col-lg-12">
+ <h1 class="line-listini"><i class="fa-solid fa-route clr-listini mr-2" aria-hidden="true"></i> Dettaglio percorsi acquistati </h1>
+
+ <div class="form-group">
+ <input type="text" id="search-filter" class="form-control" placeholder="Cerca tra i percorsi">
+ </div>
+
+ <div class="card">
+ <div class="card-header">
+ <h4 class="clr-config">I tuoi percorsi</h4>
+ </div>
+
+ <div class="card-body m-3">
+ <div class="row">
+ {% for percorso in list_percorsi_pagati %}
+ <div class="card p-3 m-2" style="width: 18rem;">
+ <img class="card-img-top" src="{% static 'assets/img/logo.png' %}">
+ <div class="card-body">
+ <h5 class="card-title">{{ percorso.nome}}</h5>
+ <p class="card-text">{{ percorso.descrizione }}</p>
+ </div>
+ <div class="card-footer">
+ <div class="d-flex justify-content-between">
+ <h4>{{ percorso.prezzo|floatformat:2 }} €</h4>
+<!-- <a href="{% url 'pagamenti:checkout' percorso.pk %}" class="btn btn-success">Acquista ora</a> -->
+ </div>
+ </div>
+ <a href="{% url 'sistema:percorso_info' percorso.pk %}" class="btn btn-warning rounded-4">Dettaglio percorso</a>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ </div>
+
+ </div>
+</div>
+
+
+<script>
+ document.addEventListener('DOMContentLoaded', () => {
+ // Assicurati che il DOM sia completamente caricato prima di eseguire il codice
+ const input = document.getElementById('search-filter');
+
+ // Verifica che l'elemento di input esista nel DOM
+ if (input) {
+ // Aggiungi un listener sull'input per avviare la ricerca durante la digitazione
+ input.addEventListener('input', search);
+ } else {
+ console.error("Input non trovato");
+ }
+});
+
+const search = (e) => {
+ // Ottieni il valore attuale dell'input e convertilo in minuscolo
+ const inputValue = e.target.value.toLowerCase();
+
+ // Seleziona il contenitore delle card per i percorsi pagati
+ const paidRoutesContainer = document.querySelector('.card-body .row');
+
+ // Verifica che il contenitore esista prima di procedere
+ if (!paidRoutesContainer) {
+ console.error("Contenitore di percorsi non trovato");
+ return;
+ }
+
+ // Seleziona le card individuali della sezione
+ const paidRoutes = [...paidRoutesContainer.getElementsByClassName('card')];
+
+ // Funzione per filtrare le card in base al testo di ricerca
+ const filterCards = (routes) => {
+ for (let i = 0; i < routes.length; i++) {
+ // Prendi il testo da h5 (titolo) e p (descrizione)
+ let searchTerm = [
+ routes[i].querySelector("h5.card-title"),
+ routes[i].querySelector("p.card-text")
+ ];
+ searchTerm = searchTerm.map(item => item ? item.innerHTML.toLowerCase() : '');
+
+ // Verifica se l'input di ricerca è contenuto nel titolo o nella descrizione della card
+ if (searchTerm.map(text => text.includes(inputValue)).some(val => val)) {
+ // Mostra la card se il testo combacia
+ routes[i].style.display = 'block';
+ } else {
+ // Nascondi la card se il testo non combacia
+ routes[i].style.display = 'none';
+ }
+ }
+ }
+
+ // Applica il filtro alle card
+ filterCards(paidRoutes);
+}
+</script>
+
+
+{% endblock %}
\ No newline at end of file
from sistema.datatables import LocalitaDatatables, MultimediaDatatables, TipoMultimediaDatatables, PercorsoDatatables, \
PoiDatatables, FeedbackDatatables, GestisceLocalitaDatatables
from sistema.views import Home, LocalitaListView, MultimediaListView, PuntiInteresseListView, \
- TipologiaMultimediaListView, PercorsiListView, LocalitaView, TipoMultimediaView, PoiView, PercorsoView, FeedbackView, PercorsoInfo
+ TipologiaMultimediaListView, PercorsiListView, TuoiPercorsiListView, LocalitaView, TipoMultimediaView, PoiView, PercorsoView, FeedbackView, PercorsoInfo
urlpatterns = [
path('', Home.as_view(), name='home'),
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'),
+ path('tuoi_percorsi_list/', TuoiPercorsiListView.as_view(), name='tuoi_percorsi_list'),
## DATATABLES ##
path('localita_datatables/', LocalitaDatatables.as_view(), name='localita_datatables'),
from django.views.generic import TemplateView
from rest_framework import status
+from pagamenti.views import CheckoutView, GetPayPalToken
from sistema.forms import LocalitaForm, TipoMultimediaForm, PoiForm, PercorsoForm, MultimediaForm
-from sistema.models import Localita, TipologiaMultimedia, PointOfInterest, Percorso, Tappa, TappaSerializer, Multimedia, \
+from sistema.models import Localita, TipologiaMultimedia, Pagamenti, PointOfInterest, Percorso, Tappa, TappaSerializer, Multimedia, \
Feedback, FeedbackSerializer
from socoin_atlas import settings
from socoin_atlas.settings import MEDIA_ROOT
class PercorsiListView(View):
def get(self, request):
if request.session['roles'] == settings.CLIENTI_GROUPS:
- list_percorsi = Percorso.objects.filter(is_active=True)
- return render(request, 'percorsi_cliente_home.html', {'list_percorsi': list_percorsi})
+ # Ottieni gli ID dei percorsi già pagati
+ percorsi_pagati_id = Pagamenti.objects.filter(user=request.user).values_list('percorso_id', flat=True)
+
+ # Ottieni i percorsi pagati
+ list_pagati = Percorso.objects.filter(id__in=percorsi_pagati_id)
+
+ # Ottieni i percorsi disponibili (non pagati)
+ list_percorsi = Percorso.objects.exclude(id__in=percorsi_pagati_id)
+
+ return render(request, 'percorsi_cliente_home.html', {
+ 'list_pagati': list_pagati,
+ 'list_percorsi': list_percorsi,
+ })
+ else:
+ percorsi_disponibili = Percorso.objects.all()
+ return render(request, 'percorsi_list.html', {'percorsi': percorsi_disponibili})
+
+
+class TuoiPercorsiListView(View):
+ def get(self, request):
+ if request.session['roles'] == settings.CLIENTI_GROUPS:
+ user_id = request.user.id
+ pagamenti_utente = Pagamenti.objects.filter(user_id=user_id)
+ id_percorsi_pagati = pagamenti_utente.values_list('percorso_id', flat=True)
+ list_percorsi_pagati = Percorso.objects.filter(id__in=id_percorsi_pagati, is_active=True)
+ return render(request, 'tuoi_percorsi_cliente.html', {'list_percorsi_pagati': list_percorsi_pagati})
else:
- return render(request, 'percorsi_list.html', {})
+ return render(request, 'percorsi_cliente_home.html', {})
class Home(CustomLoginRequiredMixin, View): # CustomLoginRequiredMixin
import datetime
import os
from pathlib import Path
+from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
'sistema',
'utenti',
'api',
- 'rest_framework'
+ 'rest_framework',
+ 'pagamenti',
]
'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,
'''
PER INTEGRARE I PAGAMENTI CON PAYPAL
https://www.youtube.com/watch?v=8rMfW4wO-vU&ab_channel=DennisIvy
-'''
\ No newline at end of file
+
+'''
+
+# Credenziali PayPal
+PAYPAL_ID='Af2rjE-cXvWU6Wv0vKVkP_vtl2K3d4Te-geFeHkybXqbISX8VKeLZ4to2M-1xo5UR9FbqUxX81olDJAg'
+PAYPAL_SECRET='EJ3lZQRK44HWdo2HprwUjm0wov-8Pc4IQZW3hFb_z647qNC-dtLpLkEUrvYXCA2WVHB8LkMfgT7QJDqH'
+PAYPAL_BASE_URL='https://sandbox.paypal.com'
\ No newline at end of file
path('', include(('sistema.urls', 'sistema'), namespace='sistema')),
path('utenti/', include(('utenti.urls', 'utenti'), namespace='utenti')),
path('api/', include(('api.urls', 'api'), namespace='api')),
+ path('pagamenti/', include(('pagamenti.urls', 'pagamenti'), namespace='pagamenti')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + staticfiles_urlpatterns()