From fbe2e7f59a5ad8d8b8ac0f75306d1f8a7db7c6b7 Mon Sep 17 00:00:00 2001
From: Alexandre Iooss <erdnaxe@crans.org>
Date: Tue, 16 Jul 2019 12:43:23 +0200
Subject: [PATCH] Not so atomic, sorry

---
 .coveragerc                                   |   9 +-
 {adherents => activity}/__init__.py           |   2 +-
 activity/admin.py                             |   3 +
 {adherents => activity}/apps.py               |   6 +-
 activity/locale/fr/LC_MESSAGES/django.po      |  54 +++++++
 activity/migrations/0001_initial.py           |  64 ++++++++
 .../migrations/__init__.py                    |   0
 activity/models.py                            |  77 ++++++++++
 {adherents => activity}/tests/__init__.py     |   0
 adherents/locale/fr/LC_MESSAGES/django.po     |  51 -------
 adherents/migrations/0001_initial.py          |  49 ------
 adherents/models.py                           | 105 -------------
 member/__init__.py                            |   5 +
 {adherents => member}/admin.py                |   0
 member/apps.py                                |  11 ++
 {adherents => member}/forms.py                |   0
 member/locale/fr/LC_MESSAGES/django.po        | 119 +++++++++++++++
 member/migrations/0001_initial.py             |  72 +++++++++
 member/migrations/__init__.py                 |   0
 member/models.py                              | 140 ++++++++++++++++++
 member/tests/__init__.py                      |   0
 note/admin.py                                 |   5 +-
 note/locale/fr/LC_MESSAGES/django.po          |  91 ++++++++++++
 note/migrations/0001_initial.py               |  84 ++++++++---
 note/models.py                                |  89 -----------
 note/models/__init__.py                       |   6 +
 note/models/notes.py                          |  90 +++++++++++
 note/models/transactions.py                   |  76 ++++++++++
 note_kfet/settings.py                         |   3 +-
 theme/locale/fr/LC_MESSAGES/django.po         |   2 +-
 30 files changed, 883 insertions(+), 330 deletions(-)
 rename {adherents => activity}/__init__.py (71%)
 create mode 100644 activity/admin.py
 rename {adherents => activity}/apps.py (71%)
 create mode 100644 activity/locale/fr/LC_MESSAGES/django.po
 create mode 100644 activity/migrations/0001_initial.py
 rename {adherents => activity}/migrations/__init__.py (100%)
 create mode 100644 activity/models.py
 rename {adherents => activity}/tests/__init__.py (100%)
 delete mode 100644 adherents/locale/fr/LC_MESSAGES/django.po
 delete mode 100644 adherents/migrations/0001_initial.py
 delete mode 100644 adherents/models.py
 create mode 100644 member/__init__.py
 rename {adherents => member}/admin.py (100%)
 create mode 100644 member/apps.py
 rename {adherents => member}/forms.py (100%)
 create mode 100644 member/locale/fr/LC_MESSAGES/django.po
 create mode 100644 member/migrations/0001_initial.py
 create mode 100644 member/migrations/__init__.py
 create mode 100644 member/models.py
 create mode 100644 member/tests/__init__.py
 create mode 100644 note/locale/fr/LC_MESSAGES/django.po
 delete mode 100644 note/models.py
 create mode 100644 note/models/__init__.py
 create mode 100644 note/models/notes.py
 create mode 100644 note/models/transactions.py

diff --git a/.coveragerc b/.coveragerc
index 61b95053..be61ad50 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,11 +1,14 @@
 [run]
 source =
-    adherents
+    activity
+    member
     note
     theme
 omit =
-    adherents/tests/*.py
-    adherents/migrations/*.py
+    activity/tests/*.py
+    activity/migrations/*.py
+    member/tests/*.py
+    member/migrations/*.py
     note/tests/*.py
     note/migrations/*.py
     theme/tests/*.py
\ No newline at end of file
diff --git a/adherents/__init__.py b/activity/__init__.py
similarity index 71%
rename from adherents/__init__.py
rename to activity/__init__.py
index c1217d13..75df9e1f 100644
--- a/adherents/__init__.py
+++ b/activity/__init__.py
@@ -2,4 +2,4 @@
 # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
-default_app_config = 'adherents.apps.AdherentsConfig'
+default_app_config = 'activity.apps.ActivityConfig'
diff --git a/activity/admin.py b/activity/admin.py
new file mode 100644
index 00000000..6ac23376
--- /dev/null
+++ b/activity/admin.py
@@ -0,0 +1,3 @@
+# -*- mode: python; coding: utf-8 -*-
+# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
diff --git a/adherents/apps.py b/activity/apps.py
similarity index 71%
rename from adherents/apps.py
rename to activity/apps.py
index 05e36034..29990f1b 100644
--- a/adherents/apps.py
+++ b/activity/apps.py
@@ -6,6 +6,6 @@ from django.apps import AppConfig
 from django.utils.translation import gettext_lazy as _
 
 
-class AdherentsConfig(AppConfig):
-    name = 'adherents'
-    verbose_name = _('adherents')
+class ActivityConfig(AppConfig):
+    name = 'activity'
+    verbose_name = _('activity')
diff --git a/activity/locale/fr/LC_MESSAGES/django.po b/activity/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..8d21c1b6
--- /dev/null
+++ b/activity/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,54 @@
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-16 12:37+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: apps.py:11
+msgid "activity"
+msgstr "activité"
+
+#: models.py:12 models.py:25
+msgid "name"
+msgstr "nom"
+
+#: models.py:16
+msgid "can invite"
+msgstr "peut inviter"
+
+#: models.py:19
+msgid "guest entry fee"
+msgstr "cotisation de l'entrée invité"
+
+#: models.py:29
+msgid "description"
+msgstr "description"
+
+#: models.py:35
+msgid "type"
+msgstr "type"
+
+#: models.py:41
+msgid "organizer"
+msgstr "organisateur"
+
+#: models.py:47
+msgid "attendees club"
+msgstr ""
+
+#: models.py:50
+msgid "start date"
+msgstr "date de début"
+
+#: models.py:53
+msgid "end date"
+msgstr "date de fin"
diff --git a/activity/migrations/0001_initial.py b/activity/migrations/0001_initial.py
new file mode 100644
index 00000000..ff697212
--- /dev/null
+++ b/activity/migrations/0001_initial.py
@@ -0,0 +1,64 @@
+# Generated by Django 2.2.3 on 2019-07-16 10:33
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('note', '0001_initial'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('member', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Activity',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255, verbose_name='name')),
+                ('description', models.TextField(verbose_name='description')),
+                ('date_start', models.DateTimeField(verbose_name='start date')),
+                ('date_end', models.DateTimeField(verbose_name='end date')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='ActivityType',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255, verbose_name='name')),
+                ('can_invite', models.BooleanField(verbose_name='can invite')),
+                ('guest_entry_fee', models.PositiveIntegerField(verbose_name='guest entry fee')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Guest',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255)),
+                ('entry', models.DateTimeField(null=True)),
+                ('activity', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.Activity')),
+                ('entry_transaction', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.Transaction')),
+                ('inviter', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.AddField(
+            model_name='activity',
+            name='activity_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.ActivityType', verbose_name='type'),
+        ),
+        migrations.AddField(
+            model_name='activity',
+            name='attendees_club',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='attendees club'),
+        ),
+        migrations.AddField(
+            model_name='activity',
+            name='organizer',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='organizer'),
+        ),
+    ]
diff --git a/adherents/migrations/__init__.py b/activity/migrations/__init__.py
similarity index 100%
rename from adherents/migrations/__init__.py
rename to activity/migrations/__init__.py
diff --git a/activity/models.py b/activity/models.py
new file mode 100644
index 00000000..6f36b9e0
--- /dev/null
+++ b/activity/models.py
@@ -0,0 +1,77 @@
+# -*- mode: python; coding: utf-8 -*-
+# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+from django.conf import settings
+
+
+class ActivityType(models.Model):
+    name = models.CharField(
+        verbose_name=_('name'),
+        max_length=255,
+    )
+    can_invite = models.BooleanField(
+        verbose_name=_('can invite'),
+    )
+    guest_entry_fee = models.PositiveIntegerField(
+        verbose_name=_('guest entry fee'),
+    )
+
+
+class Activity(models.Model):
+    name = models.CharField(
+        verbose_name=_('name'),
+        max_length=255,
+    )
+    description = models.TextField(
+        verbose_name=_('description'),
+    )
+    activity_type = models.ForeignKey(
+        ActivityType,
+        on_delete=models.PROTECT,
+        related_name='+',
+        verbose_name=_('type'),
+    )
+    organizer = models.ForeignKey(
+        'member.Club',
+        on_delete=models.PROTECT,
+        related_name='+',
+        verbose_name=_('organizer'),
+    )
+    attendees_club = models.ForeignKey(
+        'member.Club',
+        on_delete=models.PROTECT,
+        related_name='+',
+        verbose_name=_('attendees club'),
+    )
+    date_start = models.DateTimeField(
+        verbose_name=_('start date'),
+    )
+    date_end = models.DateTimeField(
+        verbose_name=_('end date'),
+    )
+
+
+class Guest(models.Model):
+    activity = models.ForeignKey(
+        Activity,
+        on_delete=models.PROTECT,
+        related_name='+',
+    )
+    name = models.CharField(
+        max_length=255,
+    )
+    inviter = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.PROTECT,
+        related_name='+',
+    )
+    entry = models.DateTimeField(
+        null=True,
+    )
+    entry_transaction = models.ForeignKey(
+        'note.Transaction',
+        on_delete=models.PROTECT,
+    )
diff --git a/adherents/tests/__init__.py b/activity/tests/__init__.py
similarity index 100%
rename from adherents/tests/__init__.py
rename to activity/tests/__init__.py
diff --git a/adherents/locale/fr/LC_MESSAGES/django.po b/adherents/locale/fr/LC_MESSAGES/django.po
deleted file mode 100644
index cad278eb..00000000
--- a/adherents/locale/fr/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,51 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-07-08 13:45+0200\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#: models.py:26
-msgid "phone number"
-msgstr "numéro de téléphone"
-
-#: models.py:30
-msgid "section"
-msgstr "section"
-
-#: models.py:31
-msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
-msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
-
-#: models.py:35 models.py:36
-msgid "user profile"
-msgstr "profil utilisateur"
-
-#: models.py:52
-msgid "date"
-msgstr "date"
-
-#: models.py:57
-msgid "amount"
-msgstr "montant"
-
-#: models.py:61
-msgid "membership fee"
-msgstr "cotisation"
-
-#: models.py:62
-msgid "membership fees"
-msgstr "cotisations"
diff --git a/adherents/migrations/0001_initial.py b/adherents/migrations/0001_initial.py
deleted file mode 100644
index 32433430..00000000
--- a/adherents/migrations/0001_initial.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Generated by Django 2.2.3 on 2019-07-16 07:17
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='Profile',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('avatar', models.ImageField(blank=True, max_length=255, upload_to='', verbose_name='profile picture')),
-                ('phone_number', models.CharField(blank=True, default='', max_length=50, null=True, verbose_name='phone number')),
-                ('section', models.CharField(help_text='e.g. "1A0", "9A♥", "SAPHIRE"', max_length=255, verbose_name='section')),
-                ('genre', models.CharField(blank=True, choices=[(None, 'ND'), ('M', 'M'), ('F', 'F')], max_length=1, null=True)),
-                ('address', models.TextField(blank=True, null=True)),
-                ('paid', models.BooleanField(default=False, verbose_name='paid')),
-                ('is_active', models.BooleanField(default=True, verbose_name='is active')),
-                ('is_deleted', models.BooleanField(default=False, verbose_name='is deleted')),
-                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
-            ],
-            options={
-                'verbose_name': 'user profile',
-                'verbose_name_plural': 'user profile',
-            },
-        ),
-        migrations.CreateModel(
-            name='MembershipFee',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('date', models.DateField(max_length=255, verbose_name='date')),
-                ('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='amount')),
-                ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
-            ],
-            options={
-                'verbose_name': 'membership fee',
-                'verbose_name_plural': 'membership fees',
-            },
-        ),
-    ]
diff --git a/adherents/models.py b/adherents/models.py
deleted file mode 100644
index 947499bd..00000000
--- a/adherents/models.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- mode: python; coding: utf-8 -*-
-# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from django.conf import settings
-from django.contrib.auth.models import User
-from django.db import models
-from django.db.models.signals import post_save
-from django.dispatch import receiver
-from django.utils.translation import gettext_lazy as _
-
-
-class Profile(models.Model):
-    """
-    An user profile
-
-    We do not want to patch the Django Contrib Auth User class
-    so this model add an user profile with additional information.
-    """
-    GENRES = [
-        (None, "ND"),
-        ("M", "M"),
-        ("F", "F"),
-    ]
-
-    user = models.OneToOneField(
-        settings.AUTH_USER_MODEL,
-        on_delete=models.CASCADE,
-    )
-    avatar = models.ImageField(
-        max_length=255,
-        blank=True,
-        verbose_name=_('profile picture'),
-    )
-    phone_number = models.CharField(
-        max_length=50,
-        blank=True,
-        null=True,
-        default='',
-        verbose_name=_('phone number'),
-    )
-    section = models.CharField(
-        max_length=255,
-        verbose_name=_('section'),
-        help_text=_('e.g. "1A0", "9A♥", "SAPHIRE"'),
-    )
-    genre = models.CharField(
-        max_length=1,
-        blank=True,
-        null=True,
-        choices=GENRES,
-    )
-    address = models.TextField(
-        blank=True,
-        null=True,
-    )
-    paid = models.BooleanField(
-        verbose_name=_("paid"),
-        default=False,
-    )
-    is_active = models.BooleanField(
-        verbose_name=_("is active"),
-        default=True,
-    )
-    is_deleted = models.BooleanField(
-        verbose_name=_("is deleted"),
-        default=False,
-    )
-
-    class Meta:
-        verbose_name = _('user profile')
-        verbose_name_plural = _('user profile')
-
-
-class MembershipFee(models.Model):
-    """
-    User can become member by paying a membership fee
-    """
-    user = models.ForeignKey(
-        settings.AUTH_USER_MODEL,
-        on_delete=models.PROTECT,
-    )
-    date = models.DateField(
-        max_length=255,
-        verbose_name=_('date'),
-    )
-    amount = models.DecimalField(
-        max_digits=5,  # Max 999.99 €
-        decimal_places=2,
-        verbose_name=_('amount'),
-    )
-
-    class Meta:
-        verbose_name = _('membership fee')
-        verbose_name_plural = _('membership fees')
-
-
-@receiver(post_save, sender=User)
-def save_user_profile(instance, created, **_kwargs):
-    """
-    Hook to save an user profile when an user is updated
-    """
-    if created:
-        Profile.objects.create(user=instance)
-    instance.profile.save()
diff --git a/member/__init__.py b/member/__init__.py
new file mode 100644
index 00000000..ec189d6f
--- /dev/null
+++ b/member/__init__.py
@@ -0,0 +1,5 @@
+# -*- mode: python; coding: utf-8 -*-
+# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+default_app_config = 'member.apps.MemberConfig'
diff --git a/adherents/admin.py b/member/admin.py
similarity index 100%
rename from adherents/admin.py
rename to member/admin.py
diff --git a/member/apps.py b/member/apps.py
new file mode 100644
index 00000000..928c00e4
--- /dev/null
+++ b/member/apps.py
@@ -0,0 +1,11 @@
+# -*- mode: python; coding: utf-8 -*-
+# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+class MemberConfig(AppConfig):
+    name = 'member'
+    verbose_name = _('member')
diff --git a/adherents/forms.py b/member/forms.py
similarity index 100%
rename from adherents/forms.py
rename to member/forms.py
diff --git a/member/locale/fr/LC_MESSAGES/django.po b/member/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..36a4e414
--- /dev/null
+++ b/member/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,119 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-16 12:37+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: apps.py:11
+msgid "member"
+msgstr "adhérent"
+
+#: models.py:24
+msgid "profile picture"
+msgstr "image de profil"
+
+#: models.py:29
+msgid "phone number"
+msgstr "numéro de téléphone"
+
+#: models.py:36
+msgid "section"
+msgstr "section"
+
+#: models.py:37
+msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
+msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
+
+#: models.py:41
+msgid "address"
+msgstr "adresse"
+
+#: models.py:47 models.py:61
+msgid "paid"
+msgstr "payé"
+
+#: models.py:52 models.py:53
+msgid "user profile"
+msgstr "profil utilisateur"
+
+#: models.py:65
+msgid "email"
+msgstr "courriel"
+
+#: models.py:70
+msgid "membership fee"
+msgstr "cotisation"
+
+#: models.py:74
+msgid "membership duration"
+msgstr "durée de l'adhésion"
+
+#: models.py:75
+msgid "The longest time a membership can last (NULL = infinite)."
+msgstr "La durée maximale d'une adhésion (NULL = infinie)."
+
+#: models.py:79
+msgid "membership start"
+msgstr "début de l'adhésion"
+
+#: models.py:80
+msgid "How long after January 1st the members can renew their membership."
+msgstr ""
+
+#: models.py:84
+msgid "membership end"
+msgstr "fin de l'adhésion"
+
+#: models.py:85
+msgid ""
+"How long the membership can last after January 1st of the next year after "
+"members can renew their membership."
+msgstr ""
+
+#: models.py:95
+msgid "name"
+msgstr "nom"
+
+#: models.py:100
+msgid "role"
+msgstr "rôle"
+
+#: models.py:101
+msgid "roles"
+msgstr "rôles"
+
+#: models.py:118
+msgid "membership starts on"
+msgstr "l'adhésion commence le"
+
+#: models.py:121
+#, fuzzy
+#| msgid "membership fees"
+msgid "membership ends on"
+msgstr "l'adhésion finie le"
+
+#: models.py:125
+msgid "fee"
+msgstr "cotisation"
+
+#: models.py:129
+msgid "membership"
+msgstr "adhésion"
+
+#: models.py:130
+msgid "memberships"
+msgstr "adhésions"
diff --git a/member/migrations/0001_initial.py b/member/migrations/0001_initial.py
new file mode 100644
index 00000000..d090655d
--- /dev/null
+++ b/member/migrations/0001_initial.py
@@ -0,0 +1,72 @@
+# Generated by Django 2.2.3 on 2019-07-16 10:33
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Club',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255, verbose_name='paid')),
+                ('email', models.EmailField(max_length=254, verbose_name='email')),
+                ('membership_fee', models.PositiveIntegerField(verbose_name='membership fee')),
+                ('membership_duration', models.DurationField(help_text='The longest time a membership can last (NULL = infinite).', null=True, verbose_name='membership duration')),
+                ('membership_start', models.DurationField(help_text='How long after January 1st the members can renew their membership.', null=True, verbose_name='membership start')),
+                ('membership_end', models.DurationField(help_text='How long the membership can last after January 1st of the next year after members can renew their membership.', null=True, verbose_name='membership end')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Role',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255, verbose_name='name')),
+            ],
+            options={
+                'verbose_name': 'role',
+                'verbose_name_plural': 'roles',
+            },
+        ),
+        migrations.CreateModel(
+            name='Profile',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('profile_picture', models.ImageField(blank=True, max_length=255, upload_to='', verbose_name='profile picture')),
+                ('phone_number', models.CharField(blank=True, default='', max_length=50, null=True, verbose_name='phone number')),
+                ('section', models.CharField(help_text='e.g. "1A0", "9A♥", "SAPHIRE"', max_length=255, verbose_name='section')),
+                ('address', models.CharField(blank=True, max_length=255, null=True, verbose_name='address')),
+                ('paid', models.BooleanField(default=False, verbose_name='paid')),
+                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'verbose_name': 'user profile',
+                'verbose_name_plural': 'user profile',
+            },
+        ),
+        migrations.CreateModel(
+            name='Membership',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date_start', models.DateField(verbose_name='membership starts on')),
+                ('date_end', models.DateField(null=True, verbose_name='membership ends on')),
+                ('fee', models.PositiveIntegerField(verbose_name='fee')),
+                ('club', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='member.Club')),
+                ('roles', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='member.Role')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'verbose_name': 'membership',
+                'verbose_name_plural': 'memberships',
+            },
+        ),
+    ]
diff --git a/member/migrations/__init__.py b/member/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/member/models.py b/member/models.py
new file mode 100644
index 00000000..dc3dcd8c
--- /dev/null
+++ b/member/models.py
@@ -0,0 +1,140 @@
+# -*- mode: python; coding: utf-8 -*-
+# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.conf import settings
+from django.db import models
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from django.utils.translation import gettext_lazy as _
+
+
+class Profile(models.Model):
+    """
+    An user profile
+
+    We do not want to patch the Django Contrib Auth User class
+    so this model add an user profile with additional information.
+    """
+    user = models.OneToOneField(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.CASCADE,
+    )
+    profile_picture = models.ImageField(
+        verbose_name=_('profile picture'),
+        max_length=255,
+        blank=True,
+    )
+    phone_number = models.CharField(
+        verbose_name=_('phone number'),
+        max_length=50,
+        blank=True,
+        null=True,
+        default='',
+    )
+    section = models.CharField(
+        verbose_name=_('section'),
+        help_text=_('e.g. "1A0", "9A♥", "SAPHIRE"'),
+        max_length=255,
+    )
+    address = models.CharField(
+        verbose_name=_('address'),
+        max_length=255,
+        blank=True,
+        null=True,
+    )
+    paid = models.BooleanField(
+        verbose_name=_("paid"),
+        default=False,
+    )
+
+    class Meta:
+        verbose_name = _('user profile')
+        verbose_name_plural = _('user profile')
+
+
+class Club(models.Model):
+    """
+    A student club
+    """
+    name = models.CharField(
+        verbose_name=_('paid'),
+        max_length=255,
+    )
+    email = models.EmailField(
+        verbose_name=_('email'),
+    )
+
+    # Memberships
+    membership_fee = models.PositiveIntegerField(
+        verbose_name=_('membership fee'),
+    )
+    membership_duration = models.DurationField(
+        null=True,
+        verbose_name=_('membership duration'),
+        help_text=_('The longest time a membership can last (NULL = infinite).'),
+    )
+    membership_start = models.DurationField(
+        null=True,
+        verbose_name=_('membership start'),
+        help_text=_('How long after January 1st the members can renew their membership.'),
+    )
+    membership_end = models.DurationField(
+        null=True,
+        verbose_name=_('membership end'),
+        help_text=_('How long the membership can last after January 1st of the next year '
+                    'after members can renew their membership.'),
+    )
+
+
+class Role(models.Model):
+    """
+    Role that an user can have in a club
+    """
+    name = models.CharField(
+        verbose_name=_('name'),
+        max_length=255,
+    )
+
+    class Meta:
+        verbose_name = _('role')
+        verbose_name_plural = _('roles')
+
+
+class Membership(models.Model):
+    user = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.PROTECT
+    )
+    club = models.ForeignKey(
+        Club,
+        on_delete=models.PROTECT
+    )
+    roles = models.ForeignKey(
+        Role,
+        on_delete=models.PROTECT
+    )
+    date_start = models.DateField(
+        verbose_name=_('membership starts on'),
+    )
+    date_end = models.DateField(
+        verbose_name=_('membership ends on'),
+        null=True,
+    )
+    fee = models.PositiveIntegerField(
+        verbose_name=_('fee'),
+    )
+
+    class Meta:
+        verbose_name = _('membership')
+        verbose_name_plural = _('memberships')
+
+
+@receiver(post_save, sender=settings.AUTH_USER_MODEL)
+def save_user_profile(instance, created, **_kwargs):
+    """
+    Hook to save an user profile when an user is updated
+    """
+    if created:
+        Profile.objects.create(user=instance)
+    instance.profile.save()
diff --git a/member/tests/__init__.py b/member/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/note/admin.py b/note/admin.py
index 122c11e3..b7fcbed4 100644
--- a/note/admin.py
+++ b/note/admin.py
@@ -1,10 +1,9 @@
 from django.contrib import admin
 
-from .models import NoteClub, NoteSpec, NoteUser
-from .models import Alias
+from .models.notes import NoteClub, NoteUser, NoteSpecial, Alias
 
 # Register your models here.
 admin.site.register(NoteClub)
-admin.site.register(NoteSpec)
 admin.site.register(NoteUser)
+admin.site.register(NoteSpecial)
 admin.site.register(Alias)
diff --git a/note/locale/fr/LC_MESSAGES/django.po b/note/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..1ab6395e
--- /dev/null
+++ b/note/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,91 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-07-16 12:42+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: apps.py:11
+msgid "note"
+msgstr "note"
+
+#: models/notes.py:19
+msgid "account balance"
+msgstr "solde du compte"
+
+#: models/notes.py:20
+msgid "in centimes, money credited for this instance"
+msgstr "en centimes, argent crédité pour cette instance"
+
+#: models/notes.py:23
+msgid "active"
+msgstr "actif"
+
+#: models/notes.py:26
+msgid ""
+"Designates whether this note should be treated as active. Unselect this "
+"instead of deleting notes."
+msgstr ""
+"Indique si la note est active. Désactiver cela plutôt que supprimer la note."
+
+#: models/notes.py:43
+msgid "one's note"
+msgstr "note d'un utilisateur"
+
+#: models/notes.py:44
+msgid "users note"
+msgstr "notes des utilisateurs"
+
+#: models/notes.py:58
+msgid "club note"
+msgstr "note d'un club"
+
+#: models/notes.py:59
+msgid "clubs notes"
+msgstr "notes des clubs"
+
+#: models/notes.py:72 models/transactions.py:31 models/transactions.py:60
+msgid "type"
+msgstr "type"
+
+#: models/notes.py:83 models/transactions.py:18
+msgid "name"
+msgstr "nom"
+
+#: models/transactions.py:25 models/transactions.py:47
+#: models/transactions.py:50
+msgid "destination"
+msgstr "destination"
+
+#: models/transactions.py:28 models/transactions.py:57
+msgid "amount"
+msgstr "montant"
+
+#: models/transactions.py:41
+msgid "source"
+msgstr "source"
+
+#: models/transactions.py:54
+msgid "quantity"
+msgstr "quantité"
+
+#: models/transactions.py:64
+msgid "description"
+msgstr "description"
+
+#: models/transactions.py:67
+msgid "valid"
+msgstr "valide"
diff --git a/note/migrations/0001_initial.py b/note/migrations/0001_initial.py
index b694bbf9..761e7bf3 100644
--- a/note/migrations/0001_initial.py
+++ b/note/migrations/0001_initial.py
@@ -1,8 +1,9 @@
-# Generated by Django 2.2.3 on 2019-07-16 07:17
+# Generated by Django 2.2.3 on 2019-07-16 10:33
 
 from django.conf import settings
 from django.db import migrations, models
 import django.db.models.deletion
+import django.utils.timezone
 
 
 class Migration(migrations.Migration):
@@ -10,54 +11,89 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
-        ('contenttypes', '0002_remove_content_type_name'),
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('member', '0001_initial'),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='NoteClub',
+            name='Note',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('balance', models.DecimalField(decimal_places=2, default=0, help_text='money credited for this instance', max_digits=8, verbose_name='account balance')),
-                ('is_active', models.BooleanField(default=True, verbose_name='is active')),
+                ('balance', models.IntegerField(help_text='in centimes, money credited for this instance', verbose_name='account balance')),
+                ('is_active', models.BooleanField(default=True, help_text='Designates whether this note should be treated as active. Unselect this instead of deleting notes.', verbose_name='active')),
             ],
-            options={
-                'abstract': False,
-            },
         ),
         migrations.CreateModel(
-            name='NoteSpec',
+            name='Transaction',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('balance', models.DecimalField(decimal_places=2, default=0, help_text='money credited for this instance', max_digits=8, verbose_name='account balance')),
-                ('is_active', models.BooleanField(default=True, verbose_name='is active')),
-                ('account_type', models.CharField(choices=[('CH', 'bank check'), ('CB', 'credit card'), ('VB', 'bank transfer'), ('CA', 'cash'), ('RB', 'refund')], max_length=2, unique=True)),
+                ('datetime', models.DateTimeField(default=django.utils.timezone.now, verbose_name='destination')),
+                ('quantity', models.PositiveSmallIntegerField(verbose_name='quantity')),
+                ('amount', models.PositiveIntegerField(verbose_name='amount')),
+                ('transaction_type', models.CharField(max_length=31, verbose_name='type')),
+                ('description', models.TextField(verbose_name='description')),
+                ('valid', models.NullBooleanField(verbose_name='valid')),
+                ('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.Note', verbose_name='destination')),
+                ('source', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.Note', verbose_name='source')),
             ],
-            options={
-                'abstract': False,
-            },
         ),
         migrations.CreateModel(
-            name='NoteUser',
+            name='NoteSpecial',
+            fields=[
+                ('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')),
+                ('special_type', models.CharField(max_length=255, unique=True, verbose_name='type')),
+            ],
+            bases=('note.note',),
+        ),
+        migrations.CreateModel(
+            name='TransactionTemplate',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255, verbose_name='name')),
+                ('amount', models.PositiveIntegerField(verbose_name='amount')),
+                ('template_type', models.CharField(max_length=31, verbose_name='type')),
+                ('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.Note', verbose_name='destination')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Alias',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('balance', models.DecimalField(decimal_places=2, default=0, help_text='money credited for this instance', max_digits=8, verbose_name='account balance')),
-                ('is_active', models.BooleanField(default=True, verbose_name='is active')),
-                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+                ('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
+                ('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.Note')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='NoteUser',
+            fields=[
+                ('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')),
+                ('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to=settings.AUTH_USER_MODEL)),
             ],
             options={
                 'verbose_name': "one's note",
                 'verbose_name_plural': 'users note',
             },
+            bases=('note.note',),
         ),
         migrations.CreateModel(
-            name='Alias',
+            name='NoteClub',
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('alias', models.TextField(unique=True, verbose_name='alias')),
-                ('owner_id', models.PositiveIntegerField()),
-                ('owner_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(('app_label', 'note'), ('model', 'NoteUser')), models.Q(('app_label', 'note'), ('model', 'NoteClub')), _connector='OR'), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
+                ('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')),
+                ('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to='member.Club')),
+            ],
+            options={
+                'verbose_name': 'club note',
+                'verbose_name_plural': 'clubs notes',
+            },
+            bases=('note.note',),
+        ),
+        migrations.CreateModel(
+            name='MembershipTransaction',
+            fields=[
+                ('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
+                ('membership', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='transaction', to='member.Membership')),
             ],
+            bases=('note.transaction',),
         ),
     ]
diff --git a/note/models.py b/note/models.py
deleted file mode 100644
index 8424355d..00000000
--- a/note/models.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- mode: python; coding: utf-8 -*-
-# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from django.conf import settings
-from django.contrib.auth.models import User
-from django.db import models
-
-from django.utils.translation import gettext_lazy as _
-from django.contrib.contenttypes.models import ContentType
-from django.contrib.contenttypes.fields import GenericForeignKey
-
-
-class Alias(models.Model):
-    """
-    A alias labels a Note instance, only for user and clubs
-    """
-    alias = models.TextField(
-        "alias",
-        unique=True,
-        blank=False,
-        null=False,
-    )
-
-    # Owner can be linked to an user note or a club note
-    limit = models.Q(app_label="note", model="NoteUser") | models.Q(app_label="note", model="NoteClub")
-    owner_id = models.PositiveIntegerField()
-    owner_type = models.ForeignKey(
-        ContentType,
-        on_delete=models.CASCADE,
-        limit_choices_to=limit
-    )
-    owner = GenericForeignKey('owner_type', 'owner_id')
-
-
-class Note(models.Model):
-    """
-    An abstract model, use to add transactions capabilities to a user
-    """
-    balance = models.DecimalField(
-        verbose_name=_('account balance'),
-        help_text=_("money credited for this instance"),
-        decimal_places=2,  # Limit to centimes
-        max_digits=8,  # Limit to 999999,99€
-        default=0,
-    )
-    is_active = models.BooleanField(
-        default=True,
-        verbose_name=_('is active')
-    )
-
-    class Meta:
-        abstract = True
-
-
-class NoteUser(Note):
-    """
-    A Note associated to an User
-    """
-    user = models.OneToOneField(
-        settings.AUTH_USER_MODEL,
-        on_delete=models.CASCADE,
-    )
-
-    class Meta:
-        verbose_name = _("one's note")
-        verbose_name_plural = _("users note")
-
-
-class NoteSpec(Note):
-    """
-    A Note for special account, where real money enter or leave the system
-    """
-    account_type = models.CharField(
-        max_length=2,
-        choices=(
-            ("CH", _("bank check")),
-            ("CB", _("credit card")),
-            ("VB", _("bank transfer")),
-            ("CA", _("cash")),
-            ("RB", _("refund")),
-        ),
-        unique=True,
-    )
-
-
-class NoteClub(Note):
-    # to be added
-    pass
diff --git a/note/models/__init__.py b/note/models/__init__.py
new file mode 100644
index 00000000..852104c0
--- /dev/null
+++ b/note/models/__init__.py
@@ -0,0 +1,6 @@
+# -*- mode: python; coding: utf-8 -*-
+# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from .notes import *
+from .transactions import *
diff --git a/note/models/notes.py b/note/models/notes.py
new file mode 100644
index 00000000..3596bb85
--- /dev/null
+++ b/note/models/notes.py
@@ -0,0 +1,90 @@
+# -*- mode: python; coding: utf-8 -*-
+# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.conf import settings
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+"""
+Defines each note types
+"""
+
+
+class Note(models.Model):
+    """
+    An abstract model, use to add transactions capabilities to a user
+    """
+    balance = models.IntegerField(
+        verbose_name=_('account balance'),
+        help_text=_('in centimes, money credited for this instance'),
+    )
+    is_active = models.BooleanField(
+        _('active'),
+        default=True,
+        help_text=_(
+            'Designates whether this note should be treated as active. '
+            'Unselect this instead of deleting notes.'
+        ),
+    )
+
+
+class NoteUser(Note):
+    """
+    A Note associated to an User
+    """
+    user = models.OneToOneField(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.PROTECT,
+        related_name='note',
+    )
+
+    class Meta:
+        verbose_name = _("one's note")
+        verbose_name_plural = _("users note")
+
+
+class NoteClub(Note):
+    """
+    A Note associated to a Club
+    """
+    user = models.OneToOneField(
+        'member.Club',
+        on_delete=models.PROTECT,
+        related_name='note',
+    )
+
+    class Meta:
+        verbose_name = _("club note")
+        verbose_name_plural = _("clubs notes")
+
+
+class NoteSpecial(Note):
+    """
+    A Note for special account, where real money enter or leave the system
+    - bank check
+    - credit card
+    - bank transfer
+    - cash
+    - refund
+    """
+    special_type = models.CharField(
+        verbose_name=_('type'),
+        max_length=255,
+        unique=True,
+    )
+
+
+class Alias(models.Model):
+    """
+    An alias labels a Note instance, only for user and clubs
+    """
+    name = models.CharField(
+        verbose_name=_('name'),
+        max_length=255,
+        unique=True,
+    )
+    note = models.ForeignKey(
+        Note,
+        on_delete=models.PROTECT,
+    )
diff --git a/note/models/transactions.py b/note/models/transactions.py
new file mode 100644
index 00000000..d9dd856b
--- /dev/null
+++ b/note/models/transactions.py
@@ -0,0 +1,76 @@
+# -*- mode: python; coding: utf-8 -*-
+# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.db import models
+from django.utils import timezone
+from django.utils.translation import gettext_lazy as _
+
+from .notes import Note
+
+"""
+Defines transactions
+"""
+
+
+class TransactionTemplate(models.Model):
+    name = models.CharField(
+        verbose_name=_('name'),
+        max_length=255,
+    )
+    destination = models.ForeignKey(
+        Note,
+        on_delete=models.PROTECT,
+        related_name='+',  # no reverse
+        verbose_name=_('destination'),
+    )
+    amount = models.PositiveIntegerField(
+        verbose_name=_('amount'),
+    )
+    template_type = models.CharField(
+        verbose_name=_('type'),
+        max_length=31
+    )
+
+
+class Transaction(models.Model):
+    source = models.ForeignKey(
+        Note,
+        on_delete=models.PROTECT,
+        related_name='+',
+        verbose_name=_('source'),
+    )
+    destination = models.ForeignKey(
+        Note,
+        on_delete=models.PROTECT,
+        related_name='+',
+        verbose_name=_('destination'),
+    )
+    datetime = models.DateTimeField(
+        verbose_name=_('destination'),
+        default=timezone.now,
+    )
+    quantity = models.PositiveSmallIntegerField(
+        verbose_name=_('quantity'),
+    )
+    amount = models.PositiveIntegerField(
+        verbose_name=_('amount'),
+    )
+    transaction_type = models.CharField(
+        verbose_name=_('type'),
+        max_length=31,
+    )
+    description = models.TextField(
+        verbose_name=_('description'),
+    )
+    valid = models.NullBooleanField(
+        verbose_name=_('valid'),
+    )
+
+
+class MembershipTransaction(Transaction):
+    membership = models.OneToOneField(
+        'member.Membership',
+        on_delete=models.PROTECT,
+        related_name='transaction',
+    )
diff --git a/note_kfet/settings.py b/note_kfet/settings.py
index f3d13ccf..1fc0feb3 100644
--- a/note_kfet/settings.py
+++ b/note_kfet/settings.py
@@ -46,7 +46,8 @@ INSTALLED_APPS = [
     'guardian',
 
     # Note apps
-    'adherents',
+    'activity',
+    'member',
     'note',
 ]
 
diff --git a/theme/locale/fr/LC_MESSAGES/django.po b/theme/locale/fr/LC_MESSAGES/django.po
index 4163a3da..7b9adc7d 100644
--- a/theme/locale/fr/LC_MESSAGES/django.po
+++ b/theme/locale/fr/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-07-08 13:45+0200\n"
+"POT-Creation-Date: 2019-07-16 12:36+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
-- 
GitLab