You've already forked hotpocket
This commit is contained in:
395
services/backend/hotpocket_backend/apps/ui/views/associations.py
Normal file
395
services/backend/hotpocket_backend/apps/ui/views/associations.py
Normal file
@@ -0,0 +1,395 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from django.contrib import messages
|
||||
import django.db
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import FormView, View
|
||||
from django_htmx.http import trigger_client_event
|
||||
|
||||
from hotpocket_backend.apps.accounts.decorators import account_required
|
||||
from hotpocket_backend.apps.accounts.mixins import AccountRequiredMixin
|
||||
from hotpocket_backend.apps.htmx import messages as htmx_messages
|
||||
from hotpocket_backend.apps.ui.constants import StarUnstarAssociationViewMode
|
||||
from hotpocket_backend.apps.ui.dto.saves import BrowseParams
|
||||
from hotpocket_backend.apps.ui.forms.associations import (
|
||||
ArchiveForm,
|
||||
DeleteForm,
|
||||
EditForm,
|
||||
RefreshForm,
|
||||
)
|
||||
from hotpocket_backend.apps.ui.services import UIAssociationsService
|
||||
from hotpocket_common.constants import NULL_UUID, AssociationsSearchMode
|
||||
from hotpocket_soa.dto.associations import (
|
||||
AssociationOut,
|
||||
AssociationsQuery,
|
||||
AssociationUpdateIn,
|
||||
AssociationWithTargetOut,
|
||||
)
|
||||
from hotpocket_soa.services import AssociationsService, SaveProcessorService
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AssociationMixin:
|
||||
ALLOW_ARCHIVED_ASSOCIATION = False
|
||||
|
||||
def get_association(self,
|
||||
with_target: bool = False,
|
||||
) -> AssociationOut | AssociationWithTargetOut:
|
||||
attribute = '_instance'
|
||||
if with_target is True:
|
||||
attribute = '_instance_with_target'
|
||||
|
||||
if hasattr(self, attribute) is False:
|
||||
setattr(
|
||||
self,
|
||||
attribute,
|
||||
UIAssociationsService().get_or_404(
|
||||
account_uuid=self.request.user.pk, # type: ignore[attr-defined]
|
||||
pk=self.kwargs['pk'], # type: ignore[attr-defined]
|
||||
with_target=with_target,
|
||||
allow_archived=self.ALLOW_ARCHIVED_ASSOCIATION,
|
||||
),
|
||||
)
|
||||
|
||||
return getattr(self, attribute)
|
||||
|
||||
|
||||
class DetailView(AssociationMixin, AccountRequiredMixin, FormView):
|
||||
def get_context_data(self, **kwargs) -> dict:
|
||||
result = super().get_context_data(**kwargs)
|
||||
|
||||
result.update({
|
||||
'association': self.get_association(),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class ConfirmationView(DetailView):
|
||||
def get_initial(self) -> dict:
|
||||
result = super().get_initial()
|
||||
|
||||
association: AssociationWithTargetOut = self.get_association( # type: ignore[assignment]
|
||||
with_target=True,
|
||||
)
|
||||
|
||||
result.update({
|
||||
'canhazconfirm': 'hai',
|
||||
'title': association.title,
|
||||
'url': association.target.url,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('ui.associations.browse')
|
||||
|
||||
|
||||
@account_required
|
||||
def index(request: HttpRequest) -> HttpResponse:
|
||||
return redirect(reverse('ui.associations.browse'))
|
||||
|
||||
|
||||
@account_required
|
||||
def browse(request: HttpRequest) -> HttpResponse:
|
||||
params = BrowseParams.from_request(request=request)
|
||||
|
||||
associations = AssociationsService().search(
|
||||
query=AssociationsQuery.model_validate(dict(
|
||||
account_uuid=request.user.pk,
|
||||
before=params.before,
|
||||
search=params.search or None,
|
||||
mode=params.mode,
|
||||
)),
|
||||
limit=params.limit,
|
||||
)
|
||||
|
||||
before: uuid.UUID | None = None
|
||||
if len(associations) == params.limit:
|
||||
before = associations[-1].pk
|
||||
|
||||
next_url: str | None = None
|
||||
if before is not None:
|
||||
next_url = reverse('ui.associations.browse', query=[
|
||||
('before', before),
|
||||
('search', params.search or ''),
|
||||
('limit', params.limit),
|
||||
('mode', params.mode.value),
|
||||
])
|
||||
|
||||
context = {
|
||||
'associations': associations,
|
||||
'params': params,
|
||||
'before': before,
|
||||
'next_url': next_url,
|
||||
}
|
||||
|
||||
if request.htmx:
|
||||
response = render(
|
||||
request,
|
||||
'ui/associations/partials/associations.html',
|
||||
context,
|
||||
)
|
||||
|
||||
return trigger_client_event(
|
||||
response,
|
||||
'HotPocket:BrowseSavesView:updateLoadMoreButton',
|
||||
{'next_url': next_url},
|
||||
after='swap',
|
||||
)
|
||||
|
||||
title: str
|
||||
match params.mode:
|
||||
case AssociationsSearchMode.STARRED:
|
||||
title = _('Starred')
|
||||
|
||||
case AssociationsSearchMode.ARCHIVED:
|
||||
title = _('Archived')
|
||||
|
||||
case _:
|
||||
title = 'HotPocket'
|
||||
|
||||
context['title'] = title
|
||||
|
||||
return render(
|
||||
request,
|
||||
'ui/associations/browse.html',
|
||||
context,
|
||||
)
|
||||
|
||||
|
||||
def view(request: HttpRequest, pk: uuid.UUID) -> HttpResponse:
|
||||
account_uuid: uuid.UUID | None = None
|
||||
allow_archived = False
|
||||
|
||||
is_share = request.GET.get('share', None) is not None
|
||||
if request.user.is_anonymous is True or request.user.is_active is False:
|
||||
if is_share is True:
|
||||
account_uuid = None
|
||||
else:
|
||||
account_uuid = NULL_UUID
|
||||
else:
|
||||
if is_share is False:
|
||||
account_uuid = request.user.pk
|
||||
allow_archived = True
|
||||
|
||||
association = UIAssociationsService().get_or_404(
|
||||
account_uuid=account_uuid,
|
||||
pk=pk,
|
||||
with_target=True,
|
||||
allow_archived=allow_archived,
|
||||
)
|
||||
|
||||
show_controls = True
|
||||
if association.archived_at is not None:
|
||||
show_controls = show_controls and False
|
||||
|
||||
if request.user.is_anonymous is True:
|
||||
show_controls = show_controls and False
|
||||
else:
|
||||
if is_share is True:
|
||||
show_controls = show_controls and False
|
||||
|
||||
return render(
|
||||
request,
|
||||
'ui/associations/view.html',
|
||||
{
|
||||
'association': association,
|
||||
'show_controls': show_controls,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class EditView(DetailView):
|
||||
template_name = 'ui/associations/edit.html'
|
||||
form_class = EditForm
|
||||
|
||||
def get_initial(self) -> dict:
|
||||
result = super().get_initial()
|
||||
|
||||
association = self.get_association(with_target=True)
|
||||
|
||||
result.update({
|
||||
'url': association.target.url, # type: ignore[attr-defined]
|
||||
'target_title': association.get_title(),
|
||||
'target_description': association.get_description(),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
def form_valid(self, form: EditForm) -> HttpResponse:
|
||||
with django.db.transaction.atomic():
|
||||
updated_association = AssociationsService().update( # noqa: F841
|
||||
association=self.get_association(),
|
||||
update=AssociationUpdateIn.model_validate(form.cleaned_data),
|
||||
)
|
||||
|
||||
messages.add_message(
|
||||
self.request,
|
||||
messages.SUCCESS,
|
||||
_('The save has been updated!'),
|
||||
)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse(
|
||||
'ui.associations.view',
|
||||
args=(self.get_association(with_target=True).pk,),
|
||||
)
|
||||
|
||||
|
||||
class StarUnstarView(AssociationMixin, AccountRequiredMixin, View):
|
||||
mode = StarUnstarAssociationViewMode.STAR
|
||||
|
||||
def get(self, request: HttpRequest, pk: uuid.UUID) -> HttpResponse:
|
||||
with django.db.transaction.atomic():
|
||||
association = self.get_association()
|
||||
|
||||
match self.mode:
|
||||
case StarUnstarAssociationViewMode.STAR:
|
||||
_ = AssociationsService().star(association=association)
|
||||
|
||||
case StarUnstarAssociationViewMode.UNSTAR:
|
||||
_ = AssociationsService().unstar(association=association)
|
||||
|
||||
case _:
|
||||
raise ValueError(f'Unknown mode: {self.mode.value}')
|
||||
|
||||
if request.htmx:
|
||||
return render(
|
||||
request,
|
||||
'ui/associations/partials/association_card.html',
|
||||
{
|
||||
'association': self.get_association(with_target=True),
|
||||
},
|
||||
)
|
||||
|
||||
return redirect('ui.associations.browse')
|
||||
|
||||
|
||||
@account_required
|
||||
def post_save(request: HttpRequest, pk: uuid.UUID) -> HttpResponse:
|
||||
association = UIAssociationsService().get_or_404(
|
||||
account_uuid=request.user.pk,
|
||||
pk=pk,
|
||||
with_target=True,
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
'ui/associations/post_save.html',
|
||||
{
|
||||
'association': association,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class RefreshView(ConfirmationView):
|
||||
template_name = 'ui/associations/refresh.html'
|
||||
form_class = RefreshForm
|
||||
|
||||
def form_valid(self, form: RefreshForm) -> HttpResponse:
|
||||
result = SaveProcessorService().schedule_process_save(
|
||||
save=self.get_association(with_target=True).target, # type: ignore[attr-defined]
|
||||
)
|
||||
|
||||
if self.request.htmx:
|
||||
response = JsonResponse({
|
||||
'status': 'ok',
|
||||
'result': result.id,
|
||||
})
|
||||
|
||||
htmx_messages.add_htmx_message(
|
||||
request=self.request,
|
||||
response=response,
|
||||
level=htmx_messages.SUCCESS,
|
||||
message=_('The save has been refreshed!'),
|
||||
)
|
||||
|
||||
return response
|
||||
else:
|
||||
messages.add_message(
|
||||
self.request,
|
||||
messages.SUCCESS,
|
||||
_('The save has been refreshed!'),
|
||||
)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class ArchiveView(ConfirmationView):
|
||||
template_name = 'ui/associations/archive.html'
|
||||
form_class = ArchiveForm
|
||||
|
||||
def form_valid(self, form: ArchiveView) -> HttpResponse:
|
||||
with django.db.transaction.atomic():
|
||||
result = AssociationsService().archive(
|
||||
association=self.get_association(),
|
||||
)
|
||||
|
||||
if self.request.htmx:
|
||||
return JsonResponse({
|
||||
'status': 'ok',
|
||||
'result': result,
|
||||
})
|
||||
|
||||
messages.add_message(
|
||||
self.request,
|
||||
messages.SUCCESS,
|
||||
_('The save has been archived.'),
|
||||
)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DeleteView(ConfirmationView):
|
||||
template_name = 'ui/associations/delete.html'
|
||||
form_class = DeleteForm
|
||||
|
||||
ALLOW_ARCHIVED_ASSOCIATION = True
|
||||
|
||||
def form_valid(self, form: DeleteForm) -> HttpResponse:
|
||||
with django.db.transaction.atomic():
|
||||
result = AssociationsService().delete(
|
||||
association=self.get_association(),
|
||||
)
|
||||
|
||||
if self.request.htmx:
|
||||
response = JsonResponse({
|
||||
'status': 'ok',
|
||||
'result': result,
|
||||
})
|
||||
|
||||
htmx_messages.add_htmx_message(
|
||||
request=self.request,
|
||||
response=response,
|
||||
level=htmx_messages.SUCCESS,
|
||||
message=_('The save has been deleted.'),
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
messages.add_message(
|
||||
self.request,
|
||||
messages.SUCCESS,
|
||||
_('The save has been deleted.'),
|
||||
)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse(
|
||||
'ui.associations.browse',
|
||||
query={
|
||||
'mode': AssociationsSearchMode.ARCHIVED.value,
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user