|
| 1 | +Title: Changer une image d'avatar dans un profil utilisateur |
| 2 | +Date: 2019-01-18 09:50 |
| 3 | +Modified: 2019-01-08 08:00 |
| 4 | +Category: Articles |
| 5 | +Tags: Django, Pillow |
| 6 | +Slug: |
| 7 | +Author: Aurélia Gourbère |
| 8 | + |
| 9 | + |
| 10 | +# Changer une image d'avatar dans un profil utilisateur |
| 11 | + |
| 12 | +## Contraintes de la fonctionnailité: |
| 13 | + |
| 14 | +* Pouvoir charger une image d'avatar (upload). |
| 15 | +* Pouvoir mettre à jour l'avatar (update). |
| 16 | +* Redimensionner l'image chargée |
| 17 | +* Renommer l'image chargée (date_user_id.jpg) |
| 18 | +* Pouvoir envoyer du png comme du jpeg et sortir du jpeg. |
| 19 | +* Nettoyer le répertoire de stockage de manière à ce que seule l'image de l'avatar courant soit en stock. |
| 20 | + |
| 21 | +## Librairies utilisées |
| 22 | + |
| 23 | +```django-model-utils``` Fieldtracker [ Doc](https://django-model-utils.readthedocs.io/en/latest/utilities.html) |
| 24 | + |
| 25 | +```Pillow ``` [Doc](https://pillow.readthedocs.io/en/stable/) |
| 26 | + |
| 27 | +## Surcharge de la méthode save() du Model Userprofile |
| 28 | + |
| 29 | + |
| 30 | +``` models.py ``` |
| 31 | + |
| 32 | +Dépendances et bibliothèques |
| 33 | + |
| 34 | +``` |
| 35 | +from model_utils import FieldTracker |
| 36 | +from PIL import Image |
| 37 | +from datetime import date |
| 38 | +from io import BytesIO |
| 39 | +from django.core.files.uploadedfile import InMemoryUploadedFile |
| 40 | +import sys |
| 41 | +import os |
| 42 | +from django.conf import settings |
| 43 | +
|
| 44 | +``` |
| 45 | +Model Userprofile |
| 46 | + |
| 47 | +``` |
| 48 | +class UserProfile(models.Model): |
| 49 | + ''' |
| 50 | + Custom User Profile |
| 51 | + ''' |
| 52 | +
|
| 53 | + GENDER_CHOICES = ( |
| 54 | + ('H', 'Homme'), |
| 55 | + ('F', 'Femme'), |
| 56 | + ) |
| 57 | +
|
| 58 | + user = models.OneToOneField(User, on_delete=models.CASCADE) |
| 59 | + username = models.CharField(max_length=60, blank=True) |
| 60 | + bio = models.TextField(max_length=500, blank=True) |
| 61 | + dept = models.CharField(max_length=5, blank=True) |
| 62 | + town = models.CharField(max_length=60, blank=True) |
| 63 | + birth_year = YearField(null=True, blank=True) |
| 64 | + avatar = models.ImageField(null=True, blank=True, upload_to='user_avatar/') |
| 65 | + gender = models.CharField('gender' , max_length=1 , choices=GENDER_CHOICES) |
| 66 | + created_at = models.DateTimeField(auto_now_add=True) |
| 67 | + updated_at = models.DateTimeField(auto_now=True) |
| 68 | +
|
| 69 | + # Here we instantiate a Fieltracker to track any fields specially avatar field |
| 70 | + tracker = FieldTracker() |
| 71 | + |
| 72 | +``` |
| 73 | +``` |
| 74 | + def save(self, *args, **kwargs): |
| 75 | + super().save() |
| 76 | +
|
| 77 | + # we get here the self avatar condition |
| 78 | + # in case of there is no self.avatar as example |
| 79 | + # after a clear action. |
| 80 | + if self.avatar and self.tracker.has_changed('avatar'): |
| 81 | +
|
| 82 | + # keep the upload image path in order to delete it after |
| 83 | + upload_image = self.avatar.path |
| 84 | +
|
| 85 | + # rename avatar image |
| 86 | + avatar_name = '{}-{}.jpg'.format(date.today(), self.user.id) |
| 87 | +
|
| 88 | + img = Image.open(upload_image) |
| 89 | +
|
| 90 | + # convert all picture to jpg |
| 91 | + img = img.convert('RGB') |
| 92 | +
|
| 93 | + # resize picture |
| 94 | + img = img.resize((140, 140), Image.ANTIALIAS) |
| 95 | +
|
| 96 | + # make readable picture |
| 97 | + output = BytesIO() |
| 98 | + img.save(output, format='JPEG', quality=100) |
| 99 | + output.seek(0) |
| 100 | + self.avatar = InMemoryUploadedFile(output, |
| 101 | + 'ImageField', |
| 102 | + avatar_name, |
| 103 | + 'image/jpeg', |
| 104 | + sys.getsizeof(output), |
| 105 | + None) |
| 106 | +
|
| 107 | + super(UserProfile, self).save() |
| 108 | +
|
| 109 | + # delete the upload of avatar before resize it |
| 110 | + os.remove(upload_image) |
| 111 | +
|
| 112 | + # delete old image file |
| 113 | + if self.tracker.previous('avatar'): |
| 114 | + old_avatar = '{}{}'.format(settings.MEDIA_ROOT, self.tracker.previous('avatar')) |
| 115 | + os.remove(old_avatar) |
| 116 | +
|
| 117 | + # delete old image file even in case of "clear" image action |
| 118 | + if not self.avatar and self.tracker.has_changed('avatar'): |
| 119 | + old_avatar = '{}{}'.format(settings.MEDIA_ROOT , self.tracker.previous('avatar')) |
| 120 | + os.remove(old_avatar) |
| 121 | + |
| 122 | +``` |
| 123 | + |
| 124 | +views.py |
| 125 | + |
| 126 | +``` |
| 127 | +@login_required |
| 128 | +@transaction.atomic |
| 129 | +def update_profile(request): |
| 130 | +
|
| 131 | + if request.method == 'POST': |
| 132 | +
|
| 133 | + profile_form = ProfileForm(request.POST, |
| 134 | + request.FILES, |
| 135 | + instance=request.user.userprofile) |
| 136 | +
|
| 137 | + if profile_form.is_valid(): |
| 138 | + profile_form.save() |
| 139 | + messages.success(request , _('Your profile was successfully updated!')) |
| 140 | + return redirect('musicians:profile') |
| 141 | + else: |
| 142 | + messages.error(request , _('Please correct the error below.')) |
| 143 | + # |
| 144 | + else: |
| 145 | + profile_form = ProfileForm(instance=request.user.userprofile) |
| 146 | +
|
| 147 | + return render(request , 'musicians/update_profile.html', { |
| 148 | +
|
| 149 | + 'profile_form': profile_form |
| 150 | + }) |
| 151 | +``` |
| 152 | + |
| 153 | +Template : l'aspect frontend n'est absolument pas mis en valeur, mais le code fonctionne. |
| 154 | + |
| 155 | +``` update_profile.html ``` |
| 156 | + |
| 157 | +``` |
| 158 | +{% extends 'core/base.html' %} |
| 159 | +{% load static %} |
| 160 | +{% block content %} |
| 161 | +
|
| 162 | +<div class="container"> |
| 163 | + <div class="row"> |
| 164 | + <div class="col s12 "> |
| 165 | +
|
| 166 | +<form method="post" enctype="multipart/form-data"> |
| 167 | + {% csrf_token %} |
| 168 | + {{ profile_form.as_p }} |
| 169 | + <input type="submit" value="Save"> |
| 170 | +
|
| 171 | +</form> |
| 172 | +
|
| 173 | + </div> |
| 174 | + </div> |
| 175 | +</div> |
| 176 | +
|
| 177 | +
|
| 178 | +{% endblock %} |
| 179 | +``` |
| 180 | + |
| 181 | +``` profile.html ``` |
| 182 | + |
| 183 | +``` |
| 184 | +{% extends 'core/base.html' %} |
| 185 | +{% load static %} |
| 186 | +
|
| 187 | +{% block content %} |
| 188 | +<div class="container"> |
| 189 | + <div class="row"> |
| 190 | + <div class="col s12 "> |
| 191 | +
|
| 192 | +<h2>Profil de {{user.userprofile.username}}</h2> |
| 193 | +<h2>email :{{user.email}}</h2> |
| 194 | +<h4>Bio : {{user.userprofile.bio}}</h4> |
| 195 | + {% if user.userprofile.avatar %} |
| 196 | + <img src="{{ user.userprofile.avatar.url }}" /> |
| 197 | + {% else %} |
| 198 | + <img src="{% static 'core/img/0.jpg' %}" /> |
| 199 | + {% endif %} |
| 200 | +
|
| 201 | +
|
| 202 | +<a href="{% url 'musicians:update_profile' %}" class="waves-effect waves-light btn">button</a> |
| 203 | +</div> |
| 204 | + </div> |
| 205 | +</div> |
| 206 | +
|
| 207 | +{% endblock %} |
| 208 | +
|
| 209 | +``` |
| 210 | + |
0 commit comments