diff --git a/.gitignore b/.gitignore
index b57ed74ab225239f4c21de167f26f6f1ed7f2aff..f908240373a1b6a7eea63818259b2afc803c902a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,7 +37,7 @@ coverage
 # Local data
 secrets.py
 *.log
-
+media/
 # Virtualenv
 env/
 venv/
diff --git a/apps/member/urls.py b/apps/member/urls.py
index 6a7ed5ce4004879805378e7645ebb78a2e374684..d9dfd18153a14fecc9c9fc0c397350ac77050412 100644
--- a/apps/member/urls.py
+++ b/apps/member/urls.py
@@ -15,8 +15,10 @@ urlpatterns = [
     path('user/', views.UserListView.as_view(), name="user_list"),
     path('user/<int:pk>', views.UserDetailView.as_view(), name="user_detail"),
     path('user/<int:pk>/update', views.UserUpdateView.as_view(), name="user_update_profile"),
+    path('user/<int:pk>/update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"),
+    path('user/<int:pk>/aliases', views.AliasView.as_view(), name="user_alias"),
+    path('user/aliases/delete/<int:pk>', views.DeleteAliasView.as_view(), name="user_alias_delete"),
     path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
-
     # API for the user autocompleter
     path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"),
 ]
diff --git a/apps/member/views.py b/apps/member/views.py
index d03a94e0ceb388fc57262cfc0e56d92217815191..870079cc4a387d2b61d4795512f8e8b731fcc657 100644
--- a/apps/member/views.py
+++ b/apps/member/views.py
@@ -1,19 +1,28 @@
 # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
-from dal import autocomplete
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.shortcuts import redirect
 from django.utils.translation import gettext_lazy as _
-from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
+from django.views.generic import CreateView, DetailView, UpdateView, TemplateView,DeleteView
+from django.views.generic.edit import FormMixin
 from django.contrib.auth.models import User
+from django.contrib import messages
 from django.urls import reverse_lazy
+from django.http import HttpResponseRedirect
 from django.db.models import Q
+from django.core.exceptions import ValidationError
+from django.conf import settings
 from django_tables2.views import SingleTableView
 from rest_framework.authtoken.models import Token
+from dal import autocomplete
+from PIL import Image
+import io
+
 from note.models import Alias, NoteUser
 from note.models.transactions import Transaction
-from note.tables import HistoryTable
+from note.tables import HistoryTable, AliasTable
+from note.forms import AliasForm, ImageForm
 
 from .models import Profile, Club, Membership
 from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
@@ -52,30 +61,25 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
     fields = ['first_name', 'last_name', 'username', 'email']
     template_name = 'member/profile_update.html'
     context_object_name = 'user_object'
-    second_form = ProfileForm
+    profile_form = ProfileForm
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        context["profile_form"] = self.second_form(
-            instance=context['user_object'].profile)
+        context['profile_form'] = self.profile_form(instance=context['user_object'].profile)
         context['title'] = _("Update Profile")
-
         return context
 
     def get_form(self, form_class=None):
         form = super().get_form(form_class)
         if 'username' not in form.data:
             return form
-
         new_username = form.data['username']
-
         # Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant
         note = NoteUser.objects.filter(
             alias__normalized_name=Alias.normalize(new_username))
         if note.exists() and note.get().user != self.object:
             form.add_error('username',
                            _("An alias with a similar name already exists."))
-
         return form
 
     def form_valid(self, form):
@@ -153,7 +157,104 @@ class UserListView(LoginRequiredMixin, SingleTableView):
         context["filter"] = self.filter
         return context
 
+class AliasView(LoginRequiredMixin,FormMixin,DetailView):
+    model = User
+    template_name = 'member/profile_alias.html'
+    context_object_name = 'user_object'
+    form_class = AliasForm
+
+    def get_context_data(self,**kwargs):
+        context = super().get_context_data(**kwargs)
+        note = context['user_object'].note
+        context["aliases"] = AliasTable(note.alias_set.all())
+        return context
+
+    def get_success_url(self):
+        return reverse_lazy('member:user_alias', kwargs={'pk': self.object.id})
+
+    def post(self,request,*args,**kwargs):
+        self.object = self.get_object()
+        form = self.get_form()
+        if form.is_valid():
+            return self.form_valid(form)
+        else:
+            return self.form_invalid(form)
+
+    def form_valid(self, form):
+        alias = form.save(commit=False)
+        alias.note = self.object.note
+        alias.save()
+        return super().form_valid(form)
+
+class DeleteAliasView(LoginRequiredMixin, DeleteView):
+    model = Alias
+
+    def delete(self,request,*args,**kwargs):
+        try:
+            self.object = self.get_object()
+            self.object.delete()
+        except ValidationError as e:
+            # TODO: pass message to redirected view.
+            messages.error(self.request,str(e))
+        else:
+            messages.success(self.request,_("Alias successfully deleted"))
+        return HttpResponseRedirect(self.get_success_url())
+    
+    def get_success_url(self):
+        print(self.request)
+        return reverse_lazy('member:user_alias',kwargs={'pk':self.object.note.user.pk})
+
+    def get(self, request, *args, **kwargs):
+        return self.post(request, *args, **kwargs)
+
+class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView):
+    model = User
+    template_name = 'member/profile_picture_update.html'
+    context_object_name = 'user_object'
+    form_class = ImageForm
+    def get_context_data(self,*args,**kwargs):
+        context = super().get_context_data(*args,**kwargs)
+        context['form'] = self.form_class(self.request.POST,self.request.FILES)
+        return context
+        
+    def get_success_url(self):
+        return reverse_lazy('member:user_detail', kwargs={'pk': self.object.id})
+
+    def post(self,request,*args,**kwargs):
+        form  = self.get_form()
+        self.object = self.get_object()
+        if form.is_valid():
+            return self.form_valid(form)
+        else:
+            print('is_invalid')
+            print(form)
+            return self.form_invalid(form)
+
+    def form_valid(self,form):
+        image_field = form.cleaned_data['image']
+        x = form.cleaned_data['x']
+        y = form.cleaned_data['y']
+        w = form.cleaned_data['width']
+        h = form.cleaned_data['height']
+        # image crop and resize
+        image_file = io.BytesIO(image_field.read())
+        ext = image_field.name.split('.')[-1]
+        image = Image.open(image_file)
+        image = image.crop((x, y, x+w, y+h))
+        image_clean = image.resize((settings.PIC_WIDTH,
+                             settings.PIC_RATIO*settings.PIC_WIDTH),
+                             Image.ANTIALIAS)
+        image_file = io.BytesIO()
+        image_clean.save(image_file,ext)
+        image_field.file = image_file
+        # renaming
+        filename = "{}_pic.{}".format(self.object.note.pk, ext)
+        image_field.name = filename
+        self.object.note.display_image = image_field
+        self.object.note.save()
+        return super().form_valid(form)
 
+    
 class ManageAuthTokens(LoginRequiredMixin, TemplateView):
     """
     Affiche le jeton d'authentification, et permet de le regénérer
diff --git a/apps/note/forms.py b/apps/note/forms.py
index 3222acec14b18adc92dd7d170319c9dfb52a599c..819ed97a45aa2654646c666c7d767f318a951a10 100644
--- a/apps/note/forms.py
+++ b/apps/note/forms.py
@@ -3,11 +3,39 @@
 
 from dal import autocomplete
 from django import forms
+from django.conf import settings
 from django.utils.translation import gettext_lazy as _
 
-from .models import Transaction, TransactionTemplate, TemplateTransaction
+import os
+
+from crispy_forms.helper import FormHelper
+from crispy_forms.bootstrap import Div
+from crispy_forms.layout import Layout, HTML
 
+from .models import Transaction, TransactionTemplate, TemplateTransaction
+from .models import Note, Alias
 
+class AliasForm(forms.ModelForm):
+    class Meta:
+        model = Alias
+        fields = ("name",)
+
+    def __init__(self,*args,**kwargs):
+        super().__init__(*args,**kwargs)
+        self.fields["name"].label = False
+        self.fields["name"].widget.attrs={"placeholder":_('New Alias')}
+        
+
+class ImageForm(forms.Form):
+    image = forms.ImageField(required = False,
+                             label=_('select an image'),
+                             help_text=_('Maximal size: 2MB'))
+    x = forms.FloatField(widget=forms.HiddenInput())
+    y = forms.FloatField(widget=forms.HiddenInput())
+    width = forms.FloatField(widget=forms.HiddenInput())
+    height = forms.FloatField(widget=forms.HiddenInput())
+
+   
 class TransactionTemplateForm(forms.ModelForm):
     class Meta:
         model = TransactionTemplate
diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py
index 62811735a0bd7c46df48df4fa90758895ec84cf8..4b06c93adaba324d9faaf40825ea0ded8add3db4 100644
--- a/apps/note/models/notes.py
+++ b/apps/note/models/notes.py
@@ -43,7 +43,10 @@ class Note(PolymorphicModel):
     display_image = models.ImageField(
         verbose_name=_('display image'),
         max_length=255,
-        blank=True,
+        blank=False,
+        null=False,
+        upload_to='pic/',
+        default='pic/default.png'
     )
     created_at = models.DateTimeField(
         verbose_name=_('created at'),
@@ -219,14 +222,6 @@ class Alias(models.Model):
             if all(not unicodedata.category(char).startswith(cat)
                    for cat in {'M', 'P', 'Z', 'C'})).casefold()
 
-    def save(self, *args, **kwargs):
-        """
-        Handle normalized_name
-        """
-        self.normalized_name = Alias.normalize(self.name)
-        if len(self.normalized_name) < 256:
-            super().save(*args, **kwargs)
-
     def clean(self):
         normalized_name = Alias.normalize(self.name)
         if len(normalized_name) >= 255:
@@ -235,12 +230,13 @@ class Alias(models.Model):
         try:
             sim_alias = Alias.objects.get(normalized_name=normalized_name)
             if self != sim_alias:
-                raise ValidationError(_('An alias with a similar name already exists:'),
+                raise ValidationError(_('An alias with a similar name already exists: {} '.format(sim_alias)),
                                        code="same_alias"
                 )
         except Alias.DoesNotExist:
             pass
-
+        self.normalized_name = normalized_name
+        
     def delete(self, using=None, keep_parents=False):
         if self.name == str(self.note):
             raise ValidationError(_("You can't delete your main alias."),
diff --git a/apps/note/tables.py b/apps/note/tables.py
index 43a1ef7413566ce1c127131d7addf094910efbb0..20476cb664460502b4daf1e500b22d29f7e015df 100644
--- a/apps/note/tables.py
+++ b/apps/note/tables.py
@@ -3,9 +3,9 @@
 
 import django_tables2 as tables
 from django.db.models import F
-
+from django_tables2.utils import A
 from .models.transactions import Transaction
-
+from .models.notes import Alias
 
 class HistoryTable(tables.Table):
     class Meta:
@@ -24,3 +24,22 @@ class HistoryTable(tables.Table):
         queryset = queryset.annotate(total=F('amount') * F('quantity')) \
             .order_by(('-' if is_descending else '') + 'total')
         return (queryset, True)
+
+class AliasTable(tables.Table):
+    class Meta:
+        attrs = {
+            'class':
+            'table table condensed table-striped table-hover'
+        }
+        model = Alias
+        fields =('name',)
+        template_name = 'django_tables2/bootstrap4.html'
+
+    show_header = False
+    name = tables.Column(attrs={'td':{'class':'text-center'}})
+    delete = tables.LinkColumn('member:user_alias_delete',
+                               args=[A('pk')],
+                               attrs={
+                                   'td': {'class':'col-sm-2'},
+                                   'a': {'class': 'btn btn-danger'} },
+                               text='delete',accessor='pk')
diff --git a/media/pic/default.png b/media/pic/default.png
new file mode 100644
index 0000000000000000000000000000000000000000..f933bc341619178775520150d514effb2339b10a
Binary files /dev/null and b/media/pic/default.png differ
diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index 39b4124b78d680e8dacc5bce379ef3189ebdb4de..5a3c3f6b348d9b2703b1f32bf0afeba8fb26ce24 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -96,6 +96,7 @@ TEMPLATES = [
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
                 'django.template.context_processors.request',
+              #  'django.template.context_processors.media',
             ],
         },
     },
@@ -193,6 +194,13 @@ STATIC_URL = '/static/'
 
 ALIAS_VALIDATOR_REGEX = r''
 
+MEDIA_ROOT=os.path.join(BASE_DIR,"media")
+MEDIA_URL='/media/'
+
+# Profile Picture Settings
+PIC_WIDTH = 200
+PIC_RATIO = 1
+
 # CAS Settings
 CAS_AUTO_CREATE_USER = False
 CAS_LOGO_URL = "/static/img/Saperlistpopette.png"
diff --git a/note_kfet/urls.py b/note_kfet/urls.py
index ce2c745a3c87dd5939f08d913f16cc267eb47f88..a5502412785677d2f0a4609cc53de34887b14c02 100644
--- a/note_kfet/urls.py
+++ b/note_kfet/urls.py
@@ -1,10 +1,13 @@
 # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
-from cas import views as cas_views
 from django.contrib import admin
 from django.urls import path, include
 from django.views.generic import RedirectView
+from django.conf.urls.static import static
+from django.conf import settings
+
+from cas import views as cas_views
 
 urlpatterns = [
     # Dev so redirect to something random
@@ -30,3 +33,6 @@ urlpatterns = [
     # Include Django REST API
     path('api/', include('api.urls')),
 ]
+
+urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
+urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
diff --git a/templates/member/profile_alias.html b/templates/member/profile_alias.html
new file mode 100644
index 0000000000000000000000000000000000000000..a83d7c3ef8f450aefd53c9d052ed759325eb984e
--- /dev/null
+++ b/templates/member/profile_alias.html
@@ -0,0 +1,19 @@
+{% extends "member/profile_detail.html" %}
+{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
+
+{% block profile_content %}
+        <div class="d-flex justify-content-center">
+            <form class=" text-center form my-2" action="" method="post">
+                {% csrf_token %}
+                {{ form |crispy }}
+                <button class="btn btn-primary mx-2" type="submit">
+                    {% trans "Add alias" %}
+                </button>
+            </form>
+        </div>
+        <div class="card bg-light shadow">
+            <div class="card-body">
+                {% render_table aliases %}
+            </div>
+        </div>
+{% endblock %}
diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html
index 6b5c127a86e3a24fe7407a2c72f7cd213f537bbb..e997b333005d051cd33b371666ff5c6b5fd4d773 100644
--- a/templates/member/profile_detail.html
+++ b/templates/member/profile_detail.html
@@ -5,7 +5,11 @@
 <div class="row mt-4">
     <div class="col-md-3 mb-4">
         <div class="card bg-light shadow">
-            <img src="{{ object.note.display_image }}" class="card-img-top" alt="">
+            <div class="card-top text-center">
+                <a  href="{% url 'member:user_update_pic' object.pk  %}">
+                    <img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >
+                </a>
+            </div>
             <div class="card-body">
                 <dl class="row">
                     <dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
@@ -30,21 +34,25 @@
                     <dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
                     <dd class="col-xl-6">{{ object.note.balance | pretty_money }}</dd>
 
-                    <dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
-                    <dd class="col-xl-6">{{ object.note.alias_set.all|join:", " }}</dd>
+                    <dt class="col-xl-6"> <a href="{% url 'member:user_alias' object.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
+                    <dd class="col-xl-6 text-truncate">{{ object.note.alias_set.all|join:", " }}</dd>
                 </dl>
 
                 {% if object.pk == user.pk %}
                     <a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
                 {% endif %}
             </div>
-            <div class="card-footer">
+            <div class="card-footer text-center">
                 <a class="btn btn-primary btn-sm" href="{% url 'member:user_update_profile' object.pk %}">{% trans 'Update Profile' %}</a>
+                {% url 'member:user_detail' object.pk as user_profile_url %}
+                {%if request.get_full_path != user_profile_url %}
+                <a class="btn btn-primary btn-sm" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
+                {% endif %}
             </div>
         </div>
     </div>
-
     <div class="col-md-9">
+    {% block profile_content %}
         <div class="accordion shadow" id="accordionProfile">
             <div class="card">
                 <div class="card-header position-relative" id="clubListHeading">
@@ -72,6 +80,7 @@
                 </div>
             </div>
         </div>
+    {% endblock %}
     </div>
 </div>
 {% endblock %}
diff --git a/templates/member/profile_picture_update.html b/templates/member/profile_picture_update.html
new file mode 100644
index 0000000000000000000000000000000000000000..36e53dcd3444f40407b56200a92a12f761220902
--- /dev/null
+++ b/templates/member/profile_picture_update.html
@@ -0,0 +1,97 @@
+{% extends "member/profile_detail.html" %}
+{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
+
+{% block profile_content %}
+<div class="text-center">
+<form method="post" enctype="multipart/form-data" id="formUpload">
+  {% csrf_token %}
+  {{ form |crispy }}
+</form>
+</div>
+<!-- MODAL TO CROP THE IMAGE -->
+<div class="modal fade" id="modalCrop">
+  <div class="modal-dialog">
+    <div class="modal-content">
+      <div class="modal-body">
+        <img src="" id="modal-image" style="max-width: 100%;">
+      </div>
+      <div class="modal-footer">
+        <div class="btn-group pull-left" role="group">
+          <button type="button" class="btn btn-default" id="js-zoom-in">
+            <span class="glyphicon glyphicon-zoom-in"></span>
+          </button>
+          <button type="button" class="btn btn-default js-zoom-out">
+            <span class="glyphicon glyphicon-zoom-out"></span>
+          </button>
+        </div>
+        <button type="button" class="btn btn-default" data-dismiss="modal">Nevermind</button>
+        <button type="button" class="btn btn-primary js-crop-and-upload">Crop and upload</button>
+      </div>
+    </div>
+  </div>
+</div>
+{% endblock %}
+{% block extracss %}
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css" rel="stylesheet">
+{% endblock %}
+
+{% block extrajavascript%}
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/jquery-cropper@1.0.1/dist/jquery-cropper.min.js"></script>
+    <script>
+     $(function () {
+
+         /* SCRIPT TO OPEN THE MODAL WITH THE PREVIEW */
+         $("#id_image").change(function (e) {
+             if (this.files && this.files[0]) {
+                 var reader = new FileReader();
+                 reader.onload = function (e) {
+                     $("#modal-image").attr("src", e.target.result);
+                     $("#modalCrop").modal("show");
+                 }
+                 reader.readAsDataURL(this.files[0]);
+             }
+         });
+
+         /* SCRIPTS TO HANDLE THE CROPPER BOX */
+         var $image = $("#modal-image");
+         var cropBoxData;
+         var canvasData;
+         $("#modalCrop").on("shown.bs.modal", function () {
+             $image.cropper({
+                 viewMode: 1,
+                 aspectRatio: 1/1,
+                 minCropBoxWidth: 200,
+                 minCropBoxHeight: 200,
+                 ready: function () {
+                     $image.cropper("setCanvasData", canvasData);
+                     $image.cropper("setCropBoxData", cropBoxData);
+                 }
+             });
+         }).on("hidden.bs.modal", function () {
+             cropBoxData = $image.cropper("getCropBoxData");
+             canvasData = $image.cropper("getCanvasData");
+             $image.cropper("destroy");
+         });
+
+         $(".js-zoom-in").click(function () {
+             $image.cropper("zoom", 0.1);
+         });
+
+         $(".js-zoom-out").click(function () {
+             $image.cropper("zoom", -0.1);
+         });
+
+         /* SCRIPT TO COLLECT THE DATA AND POST TO THE SERVER */
+         $(".js-crop-and-upload").click(function () {
+             var cropData = $image.cropper("getData");
+             $("#id_x").val(cropData["x"]);
+             $("#id_y").val(cropData["y"]);
+             $("#id_height").val(cropData["height"]);
+             $("#id_width").val(cropData["width"]);
+             $("#formUpload").submit();
+         });
+
+     });
+    </script>
+{% endblock %}