mirror of
https://github.com/boostorg/website-v2.git
synced 2026-02-27 17:42:08 +00:00
Merge pull request #126 from revsys/121-upload-new-photo
✨ User can upload a new profile photo
This commit is contained in:
@@ -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)),
|
||||
|
||||
@@ -15,6 +15,7 @@ gunicorn
|
||||
psycopg2-binary
|
||||
whitenoise
|
||||
django-click
|
||||
Pillow==9.4.0
|
||||
|
||||
# Logging
|
||||
django-tracer
|
||||
|
||||
@@ -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
|
||||
|
||||
49
templates/users/photo_upload.html
Normal file
49
templates/users/photo_upload.html
Normal 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
11
users/forms.py
Normal 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"]
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user