Intitial teams support #147

Merged
Oleg-Komarov merged 34 commits from teams-support into main 2024-05-23 19:43:54 +02:00
7 changed files with 28 additions and 46 deletions
Showing only changes of commit 42144b6fa8 - Show all commits

View File

@ -136,10 +136,12 @@ class ExtensionManager(models.Manager):
return filter return filter
def authored_by(self, user): def authored_by(self, user):
return self.filter(self._authored_by_filter(user)) return self.filter(self._authored_by_filter(user)).distinct()
def listed_or_authored_by(self, user): def listed_or_authored_by(self, user):
return self.filter(Q(status=self.model.STATUSES.APPROVED) | self._authored_by_filter(user)) return self.filter(
Q(status=self.model.STATUSES.APPROVED) | self._authored_by_filter(user)
).distinct()
class Extension(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, models.Model): class Extension(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, models.Model):

View File

@ -601,7 +601,7 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
{ {
**POST_DATA, **POST_DATA,
'team': '-', 'team': '-',
'save_draft': '', 'save': '',
}, },
) )
self.assertEqual(response.status_code, 200, _get_all_form_errors(response)) self.assertEqual(response.status_code, 200, _get_all_form_errors(response))
@ -620,7 +620,7 @@ class UpdateTest(CheckFilePropertiesMixin, TestCase):
{ {
**POST_DATA, **POST_DATA,
'team': 'test-team2', 'team': 'test-team2',
'save_draft': '', 'save': '',
}, },
) )
# the field is ignored: no error expected and the team wasn't updated # the field is ignored: no error expected and the team wasn't updated

View File

@ -2,7 +2,6 @@
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db import transaction from django.db import transaction
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, reverse from django.shortcuts import get_object_or_404, redirect, reverse
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
@ -100,13 +99,7 @@ class ManageListView(LoginRequiredMixin, ListView):
template_name = 'extensions/manage/list.html' template_name = 'extensions/manage/list.html'
def get_queryset(self): def get_queryset(self):
filter = Q(maintainer__user_id=self.request.user.pk) return Extension.objects.authored_by(self.request.user).prefetch_related(
user_teams = self.request.user.teams.all()
if user_teams:
filter = filter | Q(team__in=[t.pk for t in user_teams])
return (
Extension.objects.filter(filter)
.prefetch_related(
'authors', 'authors',
'preview_set', 'preview_set',
'preview_set__file', 'preview_set__file',
@ -116,8 +109,6 @@ class ManageListView(LoginRequiredMixin, ListView):
'versions__file', 'versions__file',
'versions__tags', 'versions__tags',
) )
.distinct()
)
class UpdateExtensionView( class UpdateExtensionView(
@ -372,10 +363,7 @@ class DraftExtensionView(
"""Add all the additional forms to the context.""" """Add all the additional forms to the context."""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if not extension_form: if not extension_form:
extension_form = ExtensionUpdateForm( extension_form = ExtensionUpdateForm(instance=self.extension, request=self.request)
instance=self.extension,
request=self.request,
)
context['extension_form'] = extension_form context['extension_form'] = extension_form
context['edit_preview_formset'] = extension_form.edit_preview_formset context['edit_preview_formset'] = extension_form.edit_preview_formset
context['add_preview_formset'] = extension_form.add_preview_formset context['add_preview_formset'] = extension_form.add_preview_formset
@ -387,10 +375,7 @@ class DraftExtensionView(
"""Handle bound forms and valid/invalid logic with the extra forms.""" """Handle bound forms and valid/invalid logic with the extra forms."""
form = self.get_form() form = self.get_form()
extension_form = ExtensionUpdateForm( extension_form = ExtensionUpdateForm(
self.request.POST, self.request.POST, self.request.FILES, instance=self.extension, request=self.request
self.request.FILES,
instance=self.extension,
request=self.request,
) )
if form.is_valid() and extension_form.is_valid(): if form.is_valid() and extension_form.is_valid():
return self.form_valid(form, extension_form) return self.form_valid(form, extension_form)

View File

@ -1,5 +1,4 @@
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.db.models import Q
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from extensions.models import Extension from extensions.models import Extension
@ -24,7 +23,7 @@ class ExtensionQuerysetMixin:
if self.request.user.is_staff: if self.request.user.is_staff:
return Extension.objects.all() return Extension.objects.all()
if self.request.user.is_authenticated: if self.request.user.is_authenticated:
return Extension.objects.listed_or_authored_by(self.request.user).distinct() return Extension.objects.listed_or_authored_by(self.request.user)
return Extension.objects.listed return Extension.objects.listed
@ -32,12 +31,8 @@ class MaintainedExtensionMixin:
"""Fetch an extension by slug if current user is a maintainer.""" """Fetch an extension by slug if current user is a maintainer."""
def dispatch(self, *args, **kwargs): def dispatch(self, *args, **kwargs):
filter = Q(maintainer__user_id=self.request.user.pk)
user_teams = self.request.user.teams.all()
if user_teams:
filter = filter | Q(team__in=[t.pk for t in user_teams])
self.extension = get_object_or_404( self.extension = get_object_or_404(
Extension.objects.filter(filter).distinct(), Extension.objects.authored_by(self.request.user),
slug=self.kwargs['slug'], slug=self.kwargs['slug'],
) )
return super().dispatch(*args, **kwargs) return super().dispatch(*args, **kwargs)

View File

@ -18,10 +18,8 @@ class UploadFileView(LoginRequiredMixin, CreateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
drafts = ( drafts = Extension.objects.authored_by(self.request.user).filter(
Extension.objects.authored_by(self.request.user) status=Extension.STATUSES.DRAFT
.filter(status=Extension.STATUSES.DRAFT)
.distinct()
) )
context['drafts'] = drafts context['drafts'] = drafts
return context return context

View File

@ -52,13 +52,15 @@ class TeamsUsers(CreatedModifiedMixin, models.Model):
@transaction.atomic @transaction.atomic
def delete(self): def delete(self):
# erase extension.team field if the user was the last maintainer from the team # This runs when a user is leaving a team.
# If the user had authored an extension, other team members shouldn't have access to it,
# unless the extension has another maintainer who is still on that team.
for extension in self.user.extensions.filter(team=self.team).all(): for extension in self.user.extensions.filter(team=self.team).all():
# assuming small datasets, not optimizing db access # assuming small datasets, not optimizing db access
authors = extension.authors.all() authors = extension.authors.all()
has_other_authors_from_the_team = False has_other_authors_from_the_team = False
for author in authors: for author in authors:
if author == self.user: if author.pk == self.user.pk:
continue continue
if self.team in author.teams.all(): if self.team in author.teams.all():
has_other_authors_from_the_team = True has_other_authors_from_the_team = True

View File

@ -36,8 +36,8 @@ class LeaveTeamView(LoginRequiredMixin, DetailView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['may_leave'] = team_user.may_leave context['may_leave'] = team_user.may_leave
context['will_lose_access_to'] = list( context['will_lose_access_to'] = list(
Extension.objects.authored_by(self.request.user) Extension.objects.authored_by(self.request.user).exclude(
.exclude(maintainer__user_id=self.request.user.pk) maintainer__user_id=self.request.user.pk
.all() )
) )
return context return context