Switched to global owner model, need to validate use-cases table and fix tests

This commit is contained in:
eeintech 2021-01-11 17:41:29 -05:00
parent 6a88bdb37d
commit 6f3cbb4e14
7 changed files with 180 additions and 94 deletions

View File

@ -10,7 +10,6 @@ from django.forms.utils import ErrorDict
from django.utils.translation import ugettext as _
from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User, Group
from mptt.fields import TreeNodeChoiceField
@ -86,37 +85,15 @@ class EditStockItemTestResultForm(HelperForm):
class EditStockLocationForm(HelperForm):
""" Form for editing a StockLocation """
owner = forms.ChoiceField(
label=_('Owner'),
help_text=_('Select Owner')
)
class Meta:
model = StockLocation
fields = [
'name',
'parent',
'description',
'owner',
]
def get_owner_choices(self):
choices = [('', '-' * 10)]
for group in Group.objects.all():
choices.append((f'group_{group.name}', f'{group} (Group)'))
for user in User.objects.all():
choices.append((f'user_{user.username}', f'{user} (User)'))
return choices
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['owner'].choices = self.get_owner_choices()
class ConvertStockItemForm(HelperForm):
"""

View File

@ -0,0 +1,25 @@
# Generated by Django 3.0.7 on 2021-01-11 21:54
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0004_owner_model'),
('stock', '0056_stockitem_expiry_date'),
]
operations = [
migrations.AddField(
model_name='stockitem',
name='owner',
field=models.ForeignKey(blank=True, help_text='Select Owner', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_items', to='users.Owner'),
),
migrations.AddField(
model_name='stocklocation',
name='owner',
field=models.ForeignKey(blank=True, help_text='Select Owner', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stock_locations', to='users.Owner'),
),
]

View File

@ -16,11 +16,9 @@ from django.db import models, transaction
from django.db.models import Sum, Q
from django.db.models.functions import Coalesce
from django.core.validators import MinValueValidator
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import User
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from markdownx.models import MarkdownxField
@ -39,6 +37,8 @@ from InvenTree.status_codes import StockStatus
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
from InvenTree.fields import InvenTreeURLField
from users.models import Owner
from company import models as CompanyModels
from part import models as PartModels
@ -49,28 +49,9 @@ class StockLocation(InvenTreeTree):
Stock locations can be heirarchical as required
"""
owner_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
owner_id = models.PositiveIntegerField(null=True, blank=True)
owner = GenericForeignKey('owner_type', 'owner_id')
def save(self, *args, **kwargs):
""" Custom save method to process StockLocation owner """
# Extract owner
try:
owner = kwargs.pop('owner')
except KeyError:
owner = ''
# Set the owner
if owner.startswith('group'):
group_name = owner.replace('group_', '')
self.owner = Group.objects.get(name=group_name)
elif owner.startswith('user'):
user_name = owner.replace('user_', '')
self.owner = User.objects.get(username=user_name)
super(StockLocation, self).save(*args, **kwargs)
owner = models.ForeignKey(Owner, on_delete=models.CASCADE, blank=True, null=True,
help_text='Select Owner',
related_name='stock_locations')
def get_absolute_url(self):
return reverse('stock-location-detail', kwargs={'pk': self.id})
@ -499,9 +480,9 @@ class StockItem(MPTTModel):
help_text=_('Single unit purchase price at time of purchase'),
)
owner = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True,
help_text='Owner (User)',
related_name='owner_stockitems')
owner = models.ForeignKey(Owner, on_delete=models.SET_NULL, blank=True, null=True,
help_text='Select Owner',
related_name='stock_items')
def is_stale(self):
"""

View File

@ -11,7 +11,7 @@ from django.views.generic import DetailView, ListView, UpdateView
from django.forms.models import model_to_dict
from django.forms import HiddenInput
from django.urls import reverse
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import Group, User
from django.utils.translation import ugettext as _
@ -125,24 +125,6 @@ class StockLocationEdit(AjaxUpdateView):
ajax_form_title = _('Edit Stock Location')
role_required = 'stock.change'
def get_owner_initial(self):
initial = ''
location = self.get_object()
owner = location.owner
if owner:
if type(owner) is Group:
group_name = owner.name
initial = f'group_{group_name}'
elif type(owner) is User:
user_name = owner.username
initial = f'user_{user_name}'
return initial
def get_form(self):
""" Customize form data for StockLocation editing.
@ -162,12 +144,12 @@ class StockLocationEdit(AjaxUpdateView):
# Is ownership control enabled?
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
owner_initial = self.get_owner_initial()
owner = location.owner
if not stock_ownership_control:
form.fields['owner'].widget = HiddenInput()
else:
form.fields['owner'].initial = owner_initial
form.fields['owner'].initial = owner
if location.parent:
form.fields['owner'].initial = location.parent.owner
if not self.request.user.is_superuser:
@ -180,21 +162,14 @@ class StockLocationEdit(AjaxUpdateView):
- update all children's owners with location's owner
"""
self.object = form.save(commit=False)
# parent = form.cleaned_data.get('parent', None)
self.object = form.save()
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
if stock_ownership_control:
owner = form.cleaned_data.get('owner', None)
self.object.save(**{'owner': owner})
if self.object.get_children():
for child in self.object.get_children():
child.owner = self.object.owner
child.save()
else:
self.object.save()
return self.object
@ -1386,10 +1361,18 @@ class StockItemEdit(AjaxUpdateView):
if not stock_ownership_control:
form.fields['owner'].widget = HiddenInput()
else:
if location:
# Check if location has owner
if location.owner:
form.fields['owner'].queryset = User.objects.filter(groups=location.owner)
location_owner = location.owner
# Check if location has owner
if location_owner:
# Check location owner type and filter
if type(location_owner.owner) is Group:
queryset = location_owner.get_users()
if self.request.user in queryset:
form.fields['owner'].initial = self.request.user
form.fields['owner'].queryset = queryset
elif type(location_owner.owner) is User:
form.fields['owner'].disabled = True
form.fields['owner'].initial = location_owner
return form
@ -1492,13 +1475,12 @@ class StockLocationCreate(AjaxCreateView):
self.object = form.save(commit=False)
parent = form.cleaned_data.get('parent', None)
owner = form.cleaned_data.get('owner', None)
if parent:
# Select parent's owner
self.object.owner = parent.owner
self.object.save()
else:
self.object.save(**{'owner': owner})
self.object.save()
return self.object
@ -1734,13 +1716,17 @@ class StockItemCreate(AjaxCreateView):
if not stock_ownership_control:
form.fields['owner'].widget = HiddenInput()
else:
if location:
# Check if location has owner
if location.owner:
queryset = User.objects.filter(groups=location.owner)
location_owner = location.owner
if location_owner:
# Check location owner type and filter
if type(location_owner.owner) is Group:
queryset = location_owner.get_users()
if self.request.user in queryset:
form.fields['owner'].initial = self.request.user
form.fields['owner'].queryset = queryset
elif type(location_owner.owner) is User:
form.fields['owner'].disabled = True
form.fields['owner'].initial = location_owner
return form

View File

@ -16,6 +16,11 @@ class UsersConfig(AppConfig):
except (OperationalError, ProgrammingError):
pass
try:
self.update_owners()
except (OperationalError, ProgrammingError):
pass
def assign_permissions(self):
from django.contrib.auth.models import Group
@ -31,3 +36,9 @@ class UsersConfig(AppConfig):
for group in Group.objects.all():
update_group_roles(group)
def update_owners(self):
from users.models import create_owners
create_owners(full_update=True)

View File

@ -0,0 +1,27 @@
# Generated by Django 3.0.7 on 2021-01-11 18:54
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('users', '0003_auto_20201005_2227'),
]
operations = [
migrations.CreateModel(
name='Owner',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('owner_id', models.PositiveIntegerField(blank=True, null=True)),
('owner_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
),
migrations.AddConstraint(
model_name='owner',
constraint=models.UniqueConstraint(fields=('owner_type', 'owner_id'), name='unique_owner'),
),
]

View File

@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.models import Group, Permission
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db.models import UniqueConstraint
from django.db.utils import IntegrityError
from django.db import models
from django.utils.translation import gettext_lazy as _
@ -385,3 +388,79 @@ def check_user_role(user, role, permission):
# No matching permissions found
return False
class Owner(models.Model):
"""
An owner is either a group or user.
Owner can be associated to any InvenTree model (part, stock, etc.)
"""
class Meta:
constraints = [
UniqueConstraint(fields=['owner_type', 'owner_id'],
name='unique_owner')
]
owner_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
owner_id = models.PositiveIntegerField(null=True, blank=True)
owner = GenericForeignKey('owner_type', 'owner_id')
def __str__(self):
return f'{self.owner} ({self.owner_type.name})'
def get_users(self):
owner_users = None
if type(self.owner) is Group:
users = User.objects.filter(groups__name=self.owner.name)
owner_users = Owner.objects.filter(owner_id__in=users,
owner_type=ContentType.objects.get_for_model(User).id)
return owner_users
def create_owners(full_update=False, group=None, user=None):
""" Create all owners """
if full_update:
# Create group owners
for group in Group.objects.all():
try:
Owner.objects.create(owner=group)
except IntegrityError:
pass
# Create user owners
for user in User.objects.all():
try:
Owner.objects.create(owner=user)
except IntegrityError:
pass
else:
if group:
try:
Owner.objects.create(owner=group)
except IntegrityError:
pass
if user:
try:
Owner.objects.create(owner=user)
except IntegrityError:
pass
@receiver(post_save, sender=Group)
def create_new_owner_group(sender, instance, **kwargs):
""" Called *after* a Group object is saved. """
create_owners(group=instance)
@receiver(post_save, sender=User)
def create_new_owner_user(sender, instance, **kwargs):
""" Called *after* a User object is saved. """
create_owners(user=instance)