Initial commit
This commit is contained in:
0
apps/core/__init__.py
Normal file
0
apps/core/__init__.py
Normal file
15
apps/core/admin.py
Normal file
15
apps/core/admin.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.contrib import admin
|
||||
from .models import ContactRequest
|
||||
|
||||
|
||||
@admin.register(ContactRequest)
|
||||
class ContactRequestAdmin(admin.ModelAdmin):
|
||||
list_display = ['last_name', 'first_name', 'email', 'phone', 'created_at', 'is_read']
|
||||
list_filter = ['is_read', 'created_at']
|
||||
search_fields = ['last_name', 'first_name', 'email', 'message']
|
||||
list_editable = ['is_read']
|
||||
readonly_fields = ['last_name', 'first_name', 'email', 'phone', 'message', 'created_at']
|
||||
ordering = ['-created_at']
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
6
apps/core/apps.py
Normal file
6
apps/core/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.core'
|
||||
15
apps/core/context_processors.py
Normal file
15
apps/core/context_processors.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def site_context(request):
|
||||
careers_enabled = getattr(settings, 'CAREERS_ENABLED', False)
|
||||
open_jobs_count = 0
|
||||
|
||||
if careers_enabled:
|
||||
from apps.careers.models import JobOffer
|
||||
open_jobs_count = JobOffer.objects.filter(status=JobOffer.Status.PUBLISHED).count()
|
||||
|
||||
return {
|
||||
'careers_enabled': careers_enabled,
|
||||
'open_jobs_count': open_jobs_count,
|
||||
}
|
||||
29
apps/core/forms.py
Normal file
29
apps/core/forms.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import re
|
||||
from django import forms
|
||||
from .models import ContactRequest
|
||||
|
||||
|
||||
class ContactForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ContactRequest
|
||||
fields = ['last_name', 'first_name', 'email', 'phone', 'message']
|
||||
widgets = {
|
||||
'last_name': forms.TextInput(attrs={'placeholder': 'Nom', 'class': 'cta-input'}),
|
||||
'first_name': forms.TextInput(attrs={'placeholder': 'Prénom', 'class': 'cta-input'}),
|
||||
'email': forms.EmailInput(attrs={'placeholder': 'Adresse email', 'class': 'cta-input'}),
|
||||
'phone': forms.TextInput(attrs={'placeholder': 'Téléphone', 'class': 'cta-input', 'inputmode': 'numeric', 'pattern': '[0-9+ ]{6,20}'}),
|
||||
'message': forms.Textarea(attrs={'placeholder': 'Votre demande…', 'class': 'cta-input cta-textarea', 'rows': 4}),
|
||||
}
|
||||
labels = {
|
||||
'last_name': '',
|
||||
'first_name': '',
|
||||
'email': '',
|
||||
'phone': '',
|
||||
'message': '',
|
||||
}
|
||||
|
||||
def clean_phone(self):
|
||||
phone = self.cleaned_data.get('phone', '').strip()
|
||||
if phone and not re.fullmatch(r'[0-9+\s\-]{6,20}', phone):
|
||||
raise forms.ValidationError('Numéro invalide — chiffres uniquement.')
|
||||
return phone
|
||||
32
apps/core/migrations/0001_initial.py
Normal file
32
apps/core/migrations/0001_initial.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Generated by Django 3.2.25 on 2026-04-14 10:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ContactRequest',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100, verbose_name='Prénom')),
|
||||
('last_name', models.CharField(max_length=100, verbose_name='Nom')),
|
||||
('email', models.EmailField(max_length=254, verbose_name='Email')),
|
||||
('phone', models.CharField(blank=True, max_length=20, verbose_name='Téléphone')),
|
||||
('message', models.TextField(verbose_name='Demande')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Reçu le')),
|
||||
('is_read', models.BooleanField(default=False, verbose_name='Lu')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Demande de contact',
|
||||
'verbose_name_plural': 'Demandes de contact',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
apps/core/migrations/__init__.py
Normal file
0
apps/core/migrations/__init__.py
Normal file
19
apps/core/models.py
Normal file
19
apps/core/models.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class ContactRequest(models.Model):
|
||||
first_name = models.CharField('Prénom', max_length=100)
|
||||
last_name = models.CharField('Nom', max_length=100)
|
||||
email = models.EmailField('Email')
|
||||
phone = models.CharField('Téléphone', max_length=20, blank=True)
|
||||
message = models.TextField('Demande')
|
||||
created_at = models.DateTimeField('Reçu le', auto_now_add=True)
|
||||
is_read = models.BooleanField('Lu', default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Demande de contact'
|
||||
verbose_name_plural = 'Demandes de contact'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.first_name} {self.last_name} — {self.email}"
|
||||
3
apps/core/tests.py
Normal file
3
apps/core/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
17
apps/core/urls.py
Normal file
17
apps/core/urls.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'core'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.HomeView.as_view(), name='home'),
|
||||
path('contact/', views.ContactAjaxView.as_view(), name='contact_ajax'),
|
||||
path('a-propos/', views.AboutView.as_view(), name='about'),
|
||||
path('produits/kiriq/', views.KiriqView.as_view(), name='kiriq'),
|
||||
path('produits/monitor/', views.MonitorView.as_view(), name='monitor'),
|
||||
path('produits/joolid/', views.JoolidView.as_view(), name='joolid'),
|
||||
path('confidentialite/', views.PrivacyView.as_view(), name='privacy'),
|
||||
# SEO
|
||||
path('robots.txt', views.RobotsTxtView.as_view(), name='robots_txt'),
|
||||
path('sitemap.xml', views.SitemapXmlView.as_view(), name='sitemap_xml'),
|
||||
]
|
||||
75
apps/core/views.py
Normal file
75
apps/core/views.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import logging
|
||||
from django.views.generic import TemplateView
|
||||
from django.views import View
|
||||
from django.http import JsonResponse
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
from .forms import ContactForm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HomeView(TemplateView):
|
||||
template_name = 'core/home.html'
|
||||
|
||||
|
||||
class ContactAjaxView(View):
|
||||
"""Reçoit le formulaire en AJAX, répond en JSON — pas de rechargement."""
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = ContactForm(request.POST)
|
||||
if form.is_valid():
|
||||
contact = form.save()
|
||||
subject = f"Nouvelle demande de contact — {contact.first_name} {contact.last_name}"
|
||||
body = (
|
||||
f"Nom : {contact.last_name}\n"
|
||||
f"Prénom : {contact.first_name}\n"
|
||||
f"Email : {contact.email}\n"
|
||||
f"Téléphone : {contact.phone or '—'}\n\n"
|
||||
f"Demande :\n{contact.message}"
|
||||
)
|
||||
try:
|
||||
send_mail(
|
||||
subject,
|
||||
body,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
[settings.CONTACT_NOTIFY_EMAIL],
|
||||
fail_silently=False,
|
||||
)
|
||||
logger.info("Mail contact #%s envoyé → %s", contact.pk, settings.CONTACT_NOTIFY_EMAIL)
|
||||
except Exception as e:
|
||||
logger.error("Échec mail contact #%s : %s — %s", contact.pk, type(e).__name__, e)
|
||||
return JsonResponse({'ok': True})
|
||||
else:
|
||||
errors = {f: e.get_json_data() for f, e in form.errors.items()}
|
||||
return JsonResponse({'ok': False, 'errors': errors}, status=400)
|
||||
|
||||
|
||||
class AboutView(TemplateView):
|
||||
template_name = 'core/about.html'
|
||||
|
||||
|
||||
class KiriqView(TemplateView):
|
||||
template_name = 'core/products/kiriq.html'
|
||||
|
||||
|
||||
class MonitorView(TemplateView):
|
||||
template_name = 'core/products/monitor.html'
|
||||
|
||||
|
||||
class JoolidView(TemplateView):
|
||||
template_name = 'core/products/joolid.html'
|
||||
|
||||
|
||||
class PrivacyView(TemplateView):
|
||||
template_name = 'core/privacy.html'
|
||||
|
||||
|
||||
class RobotsTxtView(TemplateView):
|
||||
template_name = 'robots.txt'
|
||||
content_type = 'text/plain'
|
||||
|
||||
|
||||
class SitemapXmlView(TemplateView):
|
||||
template_name = 'sitemap.xml'
|
||||
content_type = 'application/xml'
|
||||
Reference in New Issue
Block a user