# GreenTech Mobility

CMS Django pour le programme Green Tech Mobility.

## Stack Technique

- **Backend:** Django 4.2.16, Python 3.13
- **Database:** PostgreSQL (production), SQLite (dev)
- **Admin:** Jazzmin theme
- **Frontend:** Tailwind CSS, CKEditor 5
- **Traductions:** Systeme custom via ContentType
- **HTMX:** django-htmx pour interactions dynamiques

## Structure du Projet

```text
greentech/
├── apps/
│   ├── core/          # Config site, carousel, statistiques, partenaires
│   ├── universities/  # Universites partenaires, departements
│   ├── program/       # Cours, semestres, specialisations, FAQ
│   ├── team/          # Professeurs, doctorants, etudiants master
│   ├── research/      # Recherche, projets, publications
│   ├── news/          # Actualites, evenements, types d'evenements
│   └── contact/       # Formulaires contact, newsletter
├── cms/               # Systeme CMS (pages, sections, menus, settings)
├── config/            # Countries, Icons, Genres + commandes management
├── traduction/        # Systeme de traduction multi-langue
├── history/           # Audit trails (django-simple-history)
├── image_factory/     # Mixin Imageable pour gestion d'images
├── templates/         # Templates Django
├── static/            # Fichiers statiques
└── greentech/            # Configuration Django (settings, urls)
```

## Application CMS

### Modeles

| Modele | Description |
|--------|-------------|
| `Template` | Definition de template (repertoire, fichier de base) |
| `Section` | Section reutilisable avec fichier de template |
| `SectionField` | Definition de champ personnalise pour une Section |
| `Page` | Page composee de sections ordonnees |
| `PageSection` | Relation M2M Page-Section avec ordre |
| `PageSectionFieldValue` | Valeur d'un champ personnalise par PageSection |
| `Menu` | Menu de navigation (header, footer, sidebar, topbar) |
| `MenuItem` | Element de menu (URL interne, externe, ou page CMS) |
| `SettingType` | Types de parametres (texte, email, telephone, etc.) |
| `Setting` | Parametre cle-valeur avec support image |
| `Keyword` | Mot-cle unique (relation N x N polymorphique) |
| `KeywordAssignment` | Association mot-cle a n'importe quel objet |

### Structure Templates CMS

```text
templates/{template_dir}/
├── index.html              # Template principal
├── sections/               # Fichiers de sections
│   ├── hero.html
│   ├── statistics.html
│   └── ...
└── partials/               # Composants reutilisables
```

### Template Tags CMS

```django
{% load cms_tags %}

{# Settings #}
{% get_setting 'site_email' as email %}
{% setting_value 'phone' as phone %}
{% setting_image 'logo' 'logo-class' 'Alt text' %}

{# Menus #}
{% get_menu 'header' as nav %}
{% render_menu nav 'nav-class' 'item-class' 'link-class' %}
{% render_menu_items items 'item-class' 'link-class' %}
{% is_active_menu_item item as is_active %}
{% menu_item_class item 'active' 'has-dropdown' %}

{# Pages et Sections #}
{% get_page 'about' as about_page %}
{% get_homepage as home %}
{% render_section section 'extra-class' %}
{% include_section section %}

{# Champs personnalises de Section #}
{% get_section_fields page_section as fields %}
{% get_section_field page_section 'button_text' 'Default' as btn %}
{% section_field_image page_section 'bg_image' 'css-class' 'Alt' %}
{% include_section_with_fields page_section %}

{# Keywords #}
{% get_keywords page as keywords %}
{% render_keywords page 'keyword-list' %}
{% get_objects_with_keyword 'python' as python_objects %}
```

### Context Processors

Disponibles globalement dans tous les templates (y compris les pages CMS generiques):

#### Configuration et Settings

| Variable | Description |
|----------|-------------|
| `settings` | Proxy vers les Settings CMS (ex: `settings.site_name`, `settings.email`) |
| `site_config` | Configuration site (ancien systeme, garde pour compatibilite) |
| `social_links` | Liens reseaux sociaux |

Le proxy `settings` permet d'acceder aux parametres comme des attributs:
```django
{{ settings.site_name }}      {# -> Setting slug "site-name" #}
{{ settings.email }}          {# -> Setting slug "site-email" #}
{{ settings.phone }}          {# -> Setting slug "site-phone" #}
{{ settings.meta_description }} {# -> Setting slug "seo-meta-description" #}
```

#### Contenu Homepage

| Variable | Description |
|----------|-------------|
| `carousel_slides` | Slides du carousel |
| `program_stats` | Statistiques du programme |
| `main_partner` | Partenaire principal |
| `partners` | Liste des partenaires |

#### Universites

| Variable | Description |
|----------|-------------|
| `universities` | Liste des universites actives |

#### News et Evenements

| Variable | Description |
|----------|-------------|
| `news_categories` | Categories d'actualites |
| `latest_news` | 6 derniers articles publies |
| `featured_news` | 3 articles mis en avant |
| `upcoming_events` | 6 prochains evenements |
| `featured_events` | 3 evenements mis en avant |

#### Equipe

| Variable | Description |
|----------|-------------|
| `directors` | Directeurs (max 6) |
| `coordinators` | Coordinateurs |
| `professors` | Professeurs (max 8) |
| `phd_students` | Doctorants (max 8) |
| `master_students` | Etudiants Master (max 8) |
| `featured_team` | Membres mis en avant (max 6) |

#### Recherche

| Variable | Description |
|----------|-------------|
| `research_areas` | Domaines de recherche |
| `featured_projects` | Projets mis en avant (max 6) |
| `active_projects` | Projets actifs (max 6) |

#### Programme et Candidature

| Variable | Description |
|----------|-------------|
| `program` | Programme principal |
| `current_call` | Appel a candidature courant |
| `all_calls` | Tous les appels actifs |
| `scholarships` | Bourses actives |
| `requirements` | Conditions d'admission |
| `timeline` | Timeline de candidature (max 10) |
| `faqs` | Toutes les FAQs actives |
| `faqs_admission` | FAQs categorie admission |
| `faqs_financial` | FAQs categorie financier |
| `faqs_academic` | FAQs categorie academique |

#### Utilisation dans les Sections CMS

Ces variables sont disponibles dans toutes les sections CMS:

```django
{# Section documents requis #}
{% if current_call.documents_required %}
    {% for doc in current_call.get_documents_list %}
        <p>{{ doc }}</p>
    {% endfor %}
{% endif %}

{# Section bourses #}
{% for scholarship in scholarships %}
    <h3>{{ scholarship.name }}</h3>
    <p>{{ scholarship.description }}</p>
{% endfor %}

{# Section conditions d'admission #}
{% for req in requirements %}
    <h3>{{ req.title }}</h3>
    <p>{{ req.description }}</p>
{% endfor %}

{# Section timeline #}
{% for event in timeline %}
    <p>{{ event.title }} - {{ event.description }}</p>
{% endfor %}

{# Section FAQs #}
{% for faq in faqs_admission %}
    <details>
        <summary>{{ faq.question }}</summary>
        <p>{{ faq.answer }}</p>
    </details>
{% endfor %}
```

### Integration Vue avec CMS

#### CMSPageMixin - Pages statiques

Pour les pages avec un slug fixe (ex: /contact, /about):

```python
from apps.core.views import CMSPageMixin

class MyView(CMSPageMixin, TemplateView):
    template_name = 'fallback.html'
    cms_page_slug = 'my-page'  # Slug de la page CMS
```

Si une page CMS existe avec ce slug, son template sera utilise automatiquement.

#### CMSPageableMixin - Modeles avec pages CMS

Pour les modeles dont chaque instance peut avoir sa propre page CMS:

```python
from cms.mixins import CMSPageableMixin

class ApplicationCall(CMSPageableMixin, models.Model):
    CMS_PAGE_PREFIX = 'appel-candidature'  # Prefixe pour le slug CMS

    title = models.CharField(max_length=200)
    academic_year = models.CharField(max_length=9)

    def get_cms_page_identifier(self):
        return self.academic_year  # Identifiant unique

    def get_cms_page_title(self):
        return self.title
```

#### CMSObjectMixin - Vues detail avec CMS

Pour les vues detail qui utilisent la page CMS de l'objet:

```python
from apps.core.views import CMSObjectMixin
from django.views.generic import DetailView

class ApplicationCallDetailView(CMSObjectMixin, DetailView):
    model = ApplicationCall
    template_name = 'program/call_detail.html'  # Fallback
    context_object_name = 'current_call'

    # Cree automatiquement la page CMS si absente
    cms_auto_create_page = False
    cms_default_template_slug = 'master'
```

#### Modeles utilisant CMSPageableMixin

| Modele | Prefixe CMS | Identifiant |
|--------|-------------|-------------|
| `ApplicationCall` | `appel-candidature` | `academic_year` |
| `NewsArticle` | `actualite` | `slug` |
| `Event` | `evenement` | `slug` |
| `Person` | `membre` | `slug` |
| `ResearchProject` | `projet-recherche` | `slug` |

### Gestion des Keywords

```python
from cms.models import KeywordAssignment

# Ajouter un keyword a un objet
KeywordAssignment.add_keyword_to_object(article, 'python')

# Recuperer les keywords d'un objet
keywords = KeywordAssignment.get_keywords_for_object(article)

# Recuperer tous les objets avec un keyword
objects = KeywordAssignment.get_objects_with_keyword('python')

# Definir tous les keywords (remplace les existants)
KeywordAssignment.set_keywords_for_object(article, ['python', 'django', 'web'])
```

### Champs Personnalises de Section

Permet de definir des champs supplementaires pour les Sections avec des valeurs differentes par Page.

#### Architecture

```text
Section (definit les champs disponibles)
    └── SectionField (definitions: nom, type, required, default, choices)
            └── PageSectionFieldValue (valeurs par PageSection)
                    └── PageSection (lien Page-Section existant)
```

#### Types de champs supportes

| Type | Description |
|------|-------------|
| `text` | Texte court (max 500 caracteres) |
| `textarea` | Texte long |
| `boolean` | Case a cocher |
| `integer` | Nombre entier |
| `decimal` | Nombre decimal |
| `url` | URL |
| `email` | Adresse email |
| `image` | Image uploadee |
| `select` | Liste de choix |

#### Definition des champs (Admin)

1. Aller dans **Admin > Sections > [Section]**
2. Ajouter des champs dans l'inline "Champs personnalises"
3. Pour le type `select`, definir les choix (un par ligne, format: `valeur|libelle`)

#### Edition des valeurs (Admin)

1. Aller dans **Admin > Pages > [Page]**
2. Dans les sections, cliquer sur **"Champs (X/Y)"** pour editer les valeurs
3. Les valeurs sont specifiques a cette section sur cette page

#### Utilisation en Python

```python
from cms.models import PageSection

# Recuperer un PageSection
ps = PageSection.objects.get(id=10)

# Recuperer une valeur
button_text = ps.get_field_value('button_text', default='En savoir plus')

# Recuperer toutes les valeurs (dict)
fields = ps.get_all_field_values()
# {'button_text': 'Decouvrir', 'show_image': True, 'columns': 3}

# Definir une valeur
ps.set_field_value('button_text', 'Nouveau texte')
```

#### Utilisation dans les Templates

```django
{% load cms_tags %}

{# Recuperer toutes les valeurs en dict #}
{% get_section_fields page_section as fields %}
<h2>{{ fields.title }}</h2>
<a href="{{ fields.button_url }}">{{ fields.button_text }}</a>

{# Recuperer une valeur specifique #}
{% get_section_field page_section 'columns' '3' as cols %}
<div class="grid-{{ cols }}">...</div>

{# Afficher une image #}
{% section_field_image page_section 'background' 'bg-cover' 'Background' %}

{# Inclure une section avec ses champs #}
{% include_section_with_fields page_section %}
```

#### Exemple de template de section

```django
{# sections/hero.html #}
{% load cms_tags %}
{% get_section_fields page_section as fields %}

<section class="hero" {% if fields.background_image %}
    style="background-image: url('{{ fields.background_image.url }}')"
{% endif %}>
    <div class="container">
        <h1>{{ section.title }}</h1>
        <p>{{ section.description }}</p>

        {% if fields.button_text %}
        <a href="{{ fields.button_url|default:'#' }}" class="btn">
            {{ fields.button_text }}
        </a>
        {% endif %}

        {% if fields.show_stats %}
        <div class="stats columns-{{ fields.columns|default:3 }}">
            ...
        </div>
        {% endif %}
    </div>
</section>
```

### Helpers de Formatage

```python
from cms.helpers import (
    format_setting, format_phone, format_email,
    format_url, format_currency, format_address,
    format_social_link, bulk_get_settings
)

# Formatage automatique selon le SettingType
formatted = format_setting('site_phone')
formatted = format_setting('price', custom_pattern='<b>{value}</b> EUR')

# Formatages specifiques
phone = format_phone('+33 1 23 45 67 89', link=True)
email = format_email('contact@example.com', subject='Info')
url = format_url('https://example.com', text='Visiter', new_tab=True)
price = format_currency('1500', currency='EUR')
address = format_address("123 Rue\nParis\nFrance")
social = format_social_link('twitter', 'username')

# Recuperation groupee (1 requete)
settings = bulk_get_settings('email', 'phone', 'address')
```

### Template Tags de Formatage

```django
{% load cms_tags %}

{% format_phone '+33 1 23 45 67 89' %}
{% format_email 'contact@example.com' subject='Question' %}
{% format_url 'https://example.com' text='Visiter' %}
{% format_currency '1500' currency='EUR' %}
{% format_address address_text %}
{% format_social 'linkedin' 'username' %}
{% setting_formatted 'price' pattern='<strong>{value}</strong> EUR' %}
```

### SettingType - Types de Valeurs

Le modele `SettingType` definit le type de donnees et le formatage des parametres:

| Type | Description | Pattern par defaut |
|------|-------------|-------------------|
| `text` | Texte court | `{value}` |
| `textarea` | Texte long multiligne | `{value}` |
| `number` | Nombre entier | `{value}` |
| `decimal` | Nombre decimal | `{value}` |
| `phone` | Telephone | `<a href="tel:{value}">{value}</a>` |
| `email` | Email | `<a href="mailto:{value}">{value}</a>` |
| `url` | URL | `<a href="{value}" target="_blank">{value}</a>` |
| `currency` | Devise | `{value} EUR` |
| `percentage` | Pourcentage | `{value}%` |
| `color` | Code couleur (#RRGGBB) | Apercu visuel |
| `boolean` | Oui/Non | `true/false` |
| `image` | Image | `<img src="{value}" />` |

### Setting - Categories

Les parametres sont organises par categories:

| Categorie | Description |
|-----------|-------------|
| `general` | Nom du site, slogan, etc. |
| `contact` | Email, telephone, adresse |
| `social` | Liens reseaux sociaux |
| `seo` | Meta description, keywords |
| `appearance` | Logo, couleurs, etc. |
| `other` | Autres parametres |

### Utilisation des Settings

#### Dans les Templates

```django
{# Acces direct via le proxy settings (recommande) #}
{{ settings.site_name }}
{{ settings.email }}
{{ settings.phone }}
{{ settings.tagline }}

{# Avec les template tags #}
{% load cms_tags %}
{% get_setting 'site-email' as email %}
{% setting_value 'site-phone' %}
{% setting_image 'logo' 'logo-class' 'Alt text' %}
{% setting_formatted 'price' pattern='<strong>{value}</strong> EUR' %}
```

#### En Python

```python
from cms.models import Setting

# Recuperer une valeur brute
email = Setting.get('site-email')
phone = Setting.get('site-phone', default='+33 1 00 00 00 00')

# Recuperer une valeur formatee (avec lien HTML, etc.)
phone_link = Setting.get_formatted('site-phone')

# Recuperer une image
logo_url = Setting.get_image('site-logo')

# Recuperer plusieurs valeurs en une requete
data = Setting.bulk_get('site-email', 'site-phone', 'site-name')
# {'site-email': 'contact@example.com', 'site-phone': '+33...', ...}

# Recuperer par categorie
social_settings = Setting.get_by_category('social')

# Recuperer tous les settings comme dict
all_settings = Setting.get_all_as_dict()
```

### Commandes Settings

```bash
# Creer les types de parametres par defaut
python manage.py seed_setting_types
python manage.py seed_setting_types --force  # Recree tous les types

# Migrer les donnees de SiteConfiguration vers Setting
python manage.py migrate_site_config --dry-run  # Previsualisation
python manage.py migrate_site_config            # Migration reelle
python manage.py migrate_site_config --force    # Ecrase les existants
```

### Mapping SiteConfiguration -> Setting

| Ancien (SiteConfiguration) | Nouveau (Setting slug) |
|---------------------------|----------------------|
| `site_name` | `site-name` |
| `tagline` | `site-tagline` |
| `phone` | `site-phone` |
| `email` | `site-email` |
| `address` | `site-address` |
| `facebook_url` | `social-facebook` |
| `twitter_url` | `social-twitter` |
| `linkedin_url` | `social-linkedin` |
| `youtube_url` | `social-youtube` |
| `meta_description` | `seo-meta-description` |
| `meta_keywords` | `seo-meta-keywords` |

## Commandes Utiles

```bash
# Serveur de developpement
python manage.py runserver

# Migrations
python manage.py makemigrations
python manage.py migrate

# Generer les traductions
python manage.py generate_translations

# Seeder les icones
python manage.py seed_icons
python manage.py clear_icons

# Seeder les types d'evenements
python manage.py seed_event_types                  # Types uniquement
python manage.py seed_event_types --with-translations  # Types + traductions EN
python manage.py seed_event_types --force-translations # Met a jour les traductions

# Seeder les types de parametres (Settings)
python manage.py seed_setting_types               # Types par defaut
python manage.py seed_setting_types --force       # Recree tous les types

# Migrer SiteConfiguration vers Setting
python manage.py migrate_site_config --dry-run    # Previsualisation
python manage.py migrate_site_config              # Migration reelle

# Collecter les fichiers statiques
python manage.py collectstatic
```

## Export/Import des Donnees (Fixtures)

Les donnees sont exportees dans le dossier `fixtures/`.

### Export des donnees

```bash
# Export complet (recommande)
python manage.py dumpdata --natural-foreign --natural-primary --indent 2 \
    --exclude contenttypes --exclude auth.permission --exclude sessions --exclude admin.logentry \
    -o fixtures/backup_complet.json

# Export par application
python manage.py dumpdata auth.user auth.group --natural-foreign --natural-primary --indent 2 -o fixtures/01_auth.json
python manage.py dumpdata config --natural-foreign --natural-primary --indent 2 -o fixtures/02_config.json
python manage.py dumpdata traduction --natural-foreign --natural-primary --indent 2 -o fixtures/03_traduction.json
python manage.py dumpdata cms --natural-foreign --natural-primary --indent 2 -o fixtures/04_cms.json
python manage.py dumpdata core --natural-foreign --natural-primary --indent 2 -o fixtures/05_core.json
python manage.py dumpdata universities --natural-foreign --natural-primary --indent 2 -o fixtures/06_universities.json
python manage.py dumpdata program --natural-foreign --natural-primary --indent 2 -o fixtures/07_program.json
python manage.py dumpdata team --natural-foreign --natural-primary --indent 2 -o fixtures/08_team.json
python manage.py dumpdata research --natural-foreign --natural-primary --indent 2 -o fixtures/09_research.json
python manage.py dumpdata news --natural-foreign --natural-primary --indent 2 -o fixtures/10_news.json
python manage.py dumpdata contact --natural-foreign --natural-primary --indent 2 -o fixtures/11_contact.json
```

### Import des donnees

```bash
# Import du fichier complet
python manage.py loaddata fixtures/backup_complet.json

# Import sequentiel (si problemes de dependances)
python manage.py loaddata fixtures/01_auth.json
python manage.py loaddata fixtures/02_config.json
python manage.py loaddata fixtures/03_traduction.json
python manage.py loaddata fixtures/04_cms.json
python manage.py loaddata fixtures/05_core.json
python manage.py loaddata fixtures/06_universities.json
python manage.py loaddata fixtures/07_program.json
python manage.py loaddata fixtures/08_team.json
python manage.py loaddata fixtures/09_research.json
python manage.py loaddata fixtures/10_news.json
python manage.py loaddata fixtures/11_contact.json
```

**Note:** Ne pas oublier de copier le dossier `media/` pour les fichiers uploades.

## Conventions

- **Nommage:** Labels/UI en francais, code technique en anglais
- **Modeles:** Utiliser les mixins (Orderable, Timestamped, Imageable, Activatable)
- **Champs traduisibles:** Enregistrer dans `traduction/registry.py`
- **Images:** Upload dans `media/[type]/...`
- **Slugs:** Auto-generes depuis les titres

## Mixins Disponibles

| Mixin | Champs | Fichier |
|-------|--------|---------|
| `Orderable` | `order` | `config/models.py` |
| `Timestamped` | `created_at`, `updated_at` | `config/models.py` |
| `Activatable` | `active` | `config/models.py` |
| `Imageable` | `image` | `image_factory/models.py` |
| `TranslatableMixin` | - | `traduction/helpers.py` |

### Mixin Orderable

Le mixin `Orderable` fournit un champ `order` et des methodes pour gerer l'ordre des elements.

```python
from config.models import Orderable

class MyModel(Orderable, models.Model):
    name = models.CharField(max_length=100)

# Reinitialiser l'ordre de tous les elements (1, 2, 3...)
total, updated = MyModel.resequence_all()

# Reinitialiser avec filtre
total, updated = Course.resequence_all(filter_kwargs={'semester_id': 1})

# Obtenir le prochain ordre disponible
next_order = MyModel.get_next_order()
next_order = Course.get_next_order(filter_kwargs={'semester_id': 1})
```

### Mixin OrderableAdminMixin

Ajoute un bouton et une action pour reinitialiser l'ordre dans l'admin Django.

```python
from config.admin_mixins import OrderableAdminMixin

@admin.register(MyModel)
class MyModelAdmin(OrderableAdminMixin, admin.ModelAdmin):
    list_display = ['name', 'order']
    list_editable = ['order']
```

**Fonctionnalites:**
- Bouton "Reinitialiser l'ordre" en haut de la liste
- Action groupee pour reordonner les elements selectionnes
- Alerte visuelle si doublons ou sauts dans l'ordre
- Detection automatique des problemes d'ordre

## Systeme de Traduction

Les champs traduisibles sont definis dans `traduction/registry.py`:

```python
TRANSLATABLE_MODELS = {
    'app.model': ['field1', 'field2'],
}
```

Langues supportees: FR (defaut), EN, DE, ES, IT

### Admin Mixin

```python
from traduction.admin_mixins import TranslatableAdminMixin

@admin.register(MyModel)
class MyModelAdmin(TranslatableAdminMixin, admin.ModelAdmin):
    pass
```

## Variables d'Environnement (.env)

```bash
SECRET_KEY=
DEBUG=
DATABASE_NAME=
DATABASE_USER=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
```

## Application News

### Modeles

| Modele | Description |
|--------|-------------|
| `NewsCategory` | Categories d'actualites avec icone et couleur |
| `NewsArticle` | Articles d'actualites avec auteur et relations |
| `EventType` | Types d'evenements (Conference, Seminaire, etc.) |
| `Event` | Evenements avec date, lieu et inscription |

### Types d'evenements par defaut

| Type | Couleur | Description |
|------|---------|-------------|
| Conference | #3B82F6 | Presentations academiques |
| Seminaire | #8B5CF6 | Sessions de travail recherche |
| Atelier | #10B981 | Sessions pratiques |
| Soutenance | #F59E0B | Defense de these/memoire |
| Cours magistral | #6366F1 | Cours ouvert au public |
| Webinaire | #EC4899 | Evenement en ligne |
| Journee portes ouvertes | #14B8A6 | Visite campus |
| Ceremonie | #EF4444 | Remise diplomes |
| Networking | #F97316 | Mise en relation pro |
| Colloque | #0EA5E9 | Debats scientifiques |
| Hackathon | #84CC16 | Competition dev |
| Summer School | #FBBF24 | Formation intensive |

Charger les types par defaut:
```bash
python manage.py seed_event_types --with-translations
```

## URLs Principales

- `/admin/` - Administration Django
- `/` - Accueil (core)
- `/cms/` - Pages CMS
- `/universites/` - Universites partenaires
- `/programme/` - Informations programme
- `/equipe/` - Equipe
- `/recherche/` - Recherche
- `/actualites/` - Actualites et evenements
- `/contact/` - Contact
- `/langue/` - Changement de langue
