Oleg Komarov
b4aac8b791
This PR adds new features described in #106: - new `Assign Team` field in extension edit form that can be set only by extension maintainers (original author) - it is now possible to leave a team - all team members can edit/upload new versions for team extensions (except for changing the team assignment) - team extensions are listed in My Extensions for all team members Co-authored-by: Márton Lente <marton@blender.org> Reviewed-on: #147 Reviewed-by: Anna Sirota <railla@noreply.localhost>
88 lines
2.9 KiB
Python
88 lines
2.9 KiB
Python
import logging
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.db import models, transaction
|
|
from django.urls import reverse
|
|
|
|
from common.model_mixins import CreatedModifiedMixin
|
|
from constants.base import (
|
|
TEAM_ROLE_CHOICES,
|
|
TEAM_ROLE_MANAGER,
|
|
TEAM_ROLE_MEMBER,
|
|
)
|
|
import utils
|
|
|
|
User = get_user_model()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Team(CreatedModifiedMixin, models.Model):
|
|
slug = models.SlugField(unique=True, null=False, blank=False, editable=True)
|
|
name = models.CharField(max_length=128, null=False, blank=False)
|
|
users = models.ManyToManyField(User, through='TeamsUsers', related_name='teams')
|
|
|
|
def clean(self) -> None:
|
|
if not self.slug:
|
|
self.slug = utils.slugify(self.name)
|
|
super().clean()
|
|
|
|
def __str__(self) -> str:
|
|
return f'<Team {self.name}>'
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.clean()
|
|
return super().save(*args, **kwargs)
|
|
|
|
def get_absolute_url(self) -> str:
|
|
return reverse('extensions:by-team', kwargs={'team_slug': self.slug})
|
|
|
|
|
|
class TeamsUsers(CreatedModifiedMixin, models.Model):
|
|
class Meta:
|
|
verbose_name = 'Team member'
|
|
verbose_name_plural = 'Team members'
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='team_users')
|
|
role = models.SmallIntegerField(default=TEAM_ROLE_MEMBER, choices=TEAM_ROLE_CHOICES)
|
|
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='team_users')
|
|
|
|
@property
|
|
def is_manager(self) -> bool:
|
|
return self.role == TEAM_ROLE_MANAGER
|
|
|
|
@transaction.atomic
|
|
def delete(self):
|
|
# 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():
|
|
# assuming small datasets, not optimizing db access
|
|
authors = extension.authors.all()
|
|
has_other_authors_from_the_team = False
|
|
for author in authors:
|
|
if author.pk == self.user.pk:
|
|
continue
|
|
if self.team in author.teams.all():
|
|
has_other_authors_from_the_team = True
|
|
break
|
|
if not has_other_authors_from_the_team:
|
|
extension.team = None
|
|
extension.save(update_fields={'team'})
|
|
|
|
return super().delete()
|
|
|
|
@property
|
|
def may_leave(self) -> bool:
|
|
nr_of_managers = TeamsUsers.objects.filter(role=TEAM_ROLE_MANAGER, team=self.team).count()
|
|
user_is_manager = (
|
|
TeamsUsers.objects.filter(
|
|
role=TEAM_ROLE_MANAGER,
|
|
team=self.team,
|
|
user=self.user,
|
|
).first()
|
|
is not None
|
|
)
|
|
if user_is_manager and nr_of_managers < 2:
|
|
return False
|
|
return True
|