Merge pull request #126 from revsys/121-upload-new-photo

 User can upload a new profile photo
This commit is contained in:
Lacey Williams Henschel
2023-02-23 09:22:47 -08:00
committed by GitHub
7 changed files with 137 additions and 7 deletions

View File

@@ -8,7 +8,12 @@ from rest_framework import routers
from machina import urls as machina_urls
from users.views import UserViewSet, CurrentUserView, ProfileViewSet
from users.views import (
UserViewSet,
CurrentUserView,
ProfileViewSet,
ProfilePhotoUploadView,
)
from ak.views import (
HomepageView,
ForbiddenView,
@@ -42,6 +47,7 @@ urlpatterns = (
path("", HomepageView.as_view(), name="home"),
path("admin/", admin.site.urls),
path("accounts/", include("allauth.urls")),
path("users/me/photo/", ProfilePhotoUploadView.as_view(), name="profile-photo"),
path("users/me/", CurrentUserView.as_view(), name="current-user"),
path("users/<int:pk>/", ProfileViewSet.as_view(), name="profile-user"),
path("api/v1/", include(router.urls)),

View File

@@ -15,6 +15,7 @@ gunicorn
psycopg2-binary
whitenoise
django-click
Pillow==9.4.0
# Logging
django-tracer

View File

@@ -185,8 +185,10 @@ pexpect==4.8.0
# via ipython
pickleshare==0.7.5
# via ipython
pillow==8.4.0
# via django-machina
pillow==9.4.0
# via
# -r ./requirements.in
# django-machina
pip-tools==6.6.2
# via -r ./requirements.in
pluggy==0.13.1

View File

@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% load static %}
{% block subnav %}
<div class="py-3 px-4 md:px-0 flex border-b border-slate md:border-0 items-center">
<div>
{% if user.image.url %}
<img src="{{ user.image.url }}" alt="user" class="inline w-[80px] rounded-lg" />
{% else %}
<i class="fas fa-user text-5xl mr-2 text-white/60"></i>
{% endif %}
</div>
<div class="text-sm ml-4">
<span class="block">{{ user.get_full_name }}</span>
<span class="text-slate text-xs">Joined {{ user.date_joined }}</span>
</div>
</div>
<div class="space-y-4 border-b border-slate py-8 md:py-2 px-4 md:px-0 md:space-x-10 text-sm uppercase relative">
<a href="" class="block md:inline md:py-1 md:border-b md:border-orange text-orange">Account</a>
<a href="" class="block md:inline">Settings</a>
<a href="#" class="block md:inline">Password</a>
<a href="#" class="block md:inline">Help</a>
<a href="#" class="block md:inline md:absolute md:right-0 md:bottom-0 md:pb-2">Log Out</a>
</div>
{% endblock %}
{% block content %}
<div class="container">
<div class="content-section">
<div class="media">
<img class="w-32 h-32 md:h-auto rounded-full" src="{{ user.profile.image.url }}">
<div class="media-body">
</div>
</div>
<!-- FORM GOES HERE -->
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-botton ">Update Profile Photo</legend>
{{ form.as_p }}
</fieldset>
<div class="form-group mb-4">
<button class="btn btn-outline-info" type="submit">Update</button>
</div>
</form>
</div>
</div>
{% endblock content %}

11
users/forms.py Normal file
View File

@@ -0,0 +1,11 @@
from django.contrib.auth import get_user_model
from django import forms
User = get_user_model()
class UserProfilePhotoForm(forms.ModelForm):
class Meta:
model = User
fields = ["image"]

View File

@@ -1,3 +1,9 @@
import tempfile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings
def test_login_url(tp, db):
"""
GET /accounts/login/
@@ -36,3 +42,33 @@ def test_password_reset_url(tp, db):
"""
res = tp.get("account_reset_password")
tp.response_200(res)
def test_profile_photo_auth(tp, db):
"""
POST /users/me/photo/
Canary test that the photo upload page is protected.
"""
res = tp.post("profile-photo")
tp.response_302(res)
assert "/accounts/login" in res.url
@override_settings(MEDIA_ROOT=tempfile.gettempdir())
def test_profile_photo_update_success(tp, user, client):
"""
POST /users/me/photo
Confirm that user can update their profile photo
"""
old_image = user.image
client.force_login(user)
image = SimpleUploadedFile(
"/image/fpo/user.png", b"file_content", content_type="image/png"
)
res = tp.post("profile-photo", data={"image": image})
tp.response_302(res)
assert f"/users/{user.pk}" in res.url
user.refresh_from_db()
assert user.image != old_image

View File

@@ -1,13 +1,18 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.views.generic import DetailView
from django.views.generic.edit import FormView
from rest_framework import viewsets
from rest_framework import generics
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .serializers import UserSerializer, FullUserSerializer, CurrentUserSerializer
from .permissions import CustomUserPermissions
from .forms import UserProfilePhotoForm
from .models import User
from .permissions import CustomUserPermissions
from .serializers import UserSerializer, FullUserSerializer, CurrentUserSerializer
class UserViewSet(viewsets.ModelViewSet):
@@ -58,3 +63,23 @@ class ProfileViewSet(DetailView):
context["authored"] = user.authors.all()
context["maintained"] = user.maintainers.all()
return context
@method_decorator(login_required, name="dispatch")
class ProfilePhotoUploadView(FormView):
"""Allows a user to change their profile photo"""
template_name = "users/photo_upload.html"
form_class = UserProfilePhotoForm
def get_success_url(self, **kwargs):
return reverse_lazy("profile-user", args=[self.request.user.pk])
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, request.FILES, instance=self.request.user)
if form.is_valid():
form.save()
messages.success(request, "Your profile photo has been updated")
return super().form_valid(form)
else:
return super().form_invalid()