Merge pull request #839 from mouse-reeve/onboarding
Get started flow for new users
This commit is contained in:
commit
3b725fab63
14 changed files with 552 additions and 33 deletions
|
@ -9,6 +9,7 @@ from .federation import Federation, FederatedServer
|
|||
from .feed import DirectMessage, Feed, Replies, Status
|
||||
from .follow import follow, unfollow
|
||||
from .follow import accept_follow_request, delete_follow_request
|
||||
from .get_started import GetStartedBooks, GetStartedProfile, GetStartedUsers
|
||||
from .goal import Goal, hide_goal
|
||||
from .import_data import Import, ImportStatus
|
||||
from .inbox import Inbox
|
||||
|
|
137
bookwyrm/views/get_started.py
Normal file
137
bookwyrm/views/get_started.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
""" Helping new users figure out the lay of the land """
|
||||
import re
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.db.models.functions import Greatest
|
||||
from django.db.models import Count, Q
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from .helpers import get_suggested_users
|
||||
from .user import save_user_form
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class GetStartedProfile(View):
|
||||
""" tell us about yourself """
|
||||
|
||||
next_view = "get-started-books"
|
||||
|
||||
def get(self, request):
|
||||
""" basic profile info """
|
||||
data = {
|
||||
"form": forms.LimitedEditUserForm(instance=request.user),
|
||||
"next": self.next_view,
|
||||
}
|
||||
return TemplateResponse(request, "get_started/profile.html", data)
|
||||
|
||||
def post(self, request):
|
||||
""" update your profile """
|
||||
form = forms.LimitedEditUserForm(
|
||||
request.POST, request.FILES, instance=request.user
|
||||
)
|
||||
if not form.is_valid():
|
||||
data = {"form": form, "next": "get-started-books"}
|
||||
return TemplateResponse(request, "get_started/profile.html", data)
|
||||
save_user_form(form)
|
||||
return redirect(self.next_view)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class GetStartedBooks(View):
|
||||
""" name a book, any book, we gotta start somewhere """
|
||||
|
||||
next_view = "get-started-users"
|
||||
|
||||
def get(self, request):
|
||||
""" info about a book """
|
||||
query = request.GET.get("query")
|
||||
book_results = []
|
||||
if query:
|
||||
book_results = connector_manager.local_search(query, raw=True)[:5]
|
||||
if len(book_results) < 5:
|
||||
popular_books = (
|
||||
models.Edition.objects.exclude(
|
||||
# exclude already shelved
|
||||
Q(
|
||||
parent_work__in=[
|
||||
b.book.parent_work
|
||||
for b in request.user.shelfbook_set.distinct().all()
|
||||
]
|
||||
)
|
||||
| Q( # and exclude if it's already in search results
|
||||
parent_work__in=[b.parent_work for b in book_results]
|
||||
)
|
||||
)
|
||||
.annotate(Count("shelfbook"))
|
||||
.order_by("-shelfbook__count")[: 5 - len(book_results)]
|
||||
)
|
||||
|
||||
data = {
|
||||
"book_results": book_results,
|
||||
"popular_books": popular_books,
|
||||
"next": self.next_view,
|
||||
}
|
||||
return TemplateResponse(request, "get_started/books.html", data)
|
||||
|
||||
def post(self, request):
|
||||
""" shelve some books """
|
||||
shelve_actions = [
|
||||
(k, v)
|
||||
for k, v in request.POST.items()
|
||||
if re.match(r"\d+", k) and re.match(r"\d+", v)
|
||||
]
|
||||
for (book_id, shelf_id) in shelve_actions:
|
||||
book = get_object_or_404(models.Edition, id=book_id)
|
||||
shelf = get_object_or_404(models.Shelf, id=shelf_id)
|
||||
if shelf.user != request.user:
|
||||
# hmmmmm
|
||||
return HttpResponseNotFound()
|
||||
models.ShelfBook.objects.create(book=book, shelf=shelf, user=request.user)
|
||||
return redirect(self.next_view)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class GetStartedUsers(View):
|
||||
""" find friends """
|
||||
|
||||
def get(self, request):
|
||||
""" basic profile info """
|
||||
query = request.GET.get("query")
|
||||
user_results = (
|
||||
models.User.viewer_aware_objects(request.user)
|
||||
.annotate(
|
||||
similarity=Greatest(
|
||||
TrigramSimilarity("username", query),
|
||||
TrigramSimilarity("localname", query),
|
||||
)
|
||||
)
|
||||
.filter(
|
||||
similarity__gt=0.5,
|
||||
)
|
||||
.order_by("-similarity")[:5]
|
||||
)
|
||||
|
||||
if user_results.count() < 5:
|
||||
suggested_users = (
|
||||
get_suggested_users(
|
||||
request.user,
|
||||
~Q(id=request.user.id),
|
||||
~Q(followers=request.user),
|
||||
~Q(id__in=user_results),
|
||||
bookwyrm_user=True,
|
||||
)
|
||||
.order_by("shared_books", "-mutuals", "-last_active_date")
|
||||
.all()[: 5 - user_results.count()]
|
||||
)
|
||||
data = {
|
||||
"suggested_users": list(user_results) + list(suggested_users),
|
||||
}
|
||||
return TemplateResponse(request, "get_started/users.html", data)
|
|
@ -163,22 +163,28 @@ class EditUser(View):
|
|||
data = {"form": form, "user": request.user}
|
||||
return TemplateResponse(request, "preferences/edit_user.html", data)
|
||||
|
||||
user = form.save(commit=False)
|
||||
|
||||
if "avatar" in form.files:
|
||||
# crop and resize avatar upload
|
||||
image = Image.open(form.files["avatar"])
|
||||
image = crop_avatar(image)
|
||||
|
||||
# set the name to a hash
|
||||
extension = form.files["avatar"].name.split(".")[-1]
|
||||
filename = "%s.%s" % (uuid4(), extension)
|
||||
user.avatar.save(filename, image, save=False)
|
||||
user.save()
|
||||
user = save_user_form(form)
|
||||
|
||||
return redirect(user.local_path)
|
||||
|
||||
|
||||
def save_user_form(form):
|
||||
""" special handling for the user form """
|
||||
user = form.save(commit=False)
|
||||
|
||||
if "avatar" in form.files:
|
||||
# crop and resize avatar upload
|
||||
image = Image.open(form.files["avatar"])
|
||||
image = crop_avatar(image)
|
||||
|
||||
# set the name to a hash
|
||||
extension = form.files["avatar"].name.split(".")[-1]
|
||||
filename = "%s.%s" % (uuid4(), extension)
|
||||
user.avatar.save(filename, image, save=False)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
def crop_avatar(image):
|
||||
""" reduce the size and make an avatar square """
|
||||
target_size = 120
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue