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.utils.translation import ugettext as _
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.auth.models import User, Group
from mptt.fields import TreeNodeChoiceField from mptt.fields import TreeNodeChoiceField
@ -86,37 +85,15 @@ class EditStockItemTestResultForm(HelperForm):
class EditStockLocationForm(HelperForm): class EditStockLocationForm(HelperForm):
""" Form for editing a StockLocation """ """ Form for editing a StockLocation """
owner = forms.ChoiceField(
label=_('Owner'),
help_text=_('Select Owner')
)
class Meta: class Meta:
model = StockLocation model = StockLocation
fields = [ fields = [
'name', 'name',
'parent', 'parent',
'description', '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): 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 import Sum, Q
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.core.validators import MinValueValidator 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.db.models.signals import pre_delete
from django.dispatch import receiver from django.dispatch import receiver
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from markdownx.models import MarkdownxField from markdownx.models import MarkdownxField
@ -39,6 +37,8 @@ from InvenTree.status_codes import StockStatus
from InvenTree.models import InvenTreeTree, InvenTreeAttachment from InvenTree.models import InvenTreeTree, InvenTreeAttachment
from InvenTree.fields import InvenTreeURLField from InvenTree.fields import InvenTreeURLField
from users.models import Owner
from company import models as CompanyModels from company import models as CompanyModels
from part import models as PartModels from part import models as PartModels
@ -49,28 +49,9 @@ class StockLocation(InvenTreeTree):
Stock locations can be heirarchical as required Stock locations can be heirarchical as required
""" """
owner_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True) owner = models.ForeignKey(Owner, on_delete=models.CASCADE, blank=True, null=True,
owner_id = models.PositiveIntegerField(null=True, blank=True) help_text='Select Owner',
owner = GenericForeignKey('owner_type', 'owner_id') related_name='stock_locations')
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)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('stock-location-detail', kwargs={'pk': self.id}) 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'), help_text=_('Single unit purchase price at time of purchase'),
) )
owner = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, owner = models.ForeignKey(Owner, on_delete=models.SET_NULL, blank=True, null=True,
help_text='Owner (User)', help_text='Select Owner',
related_name='owner_stockitems') related_name='stock_items')
def is_stale(self): 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.models import model_to_dict
from django.forms import HiddenInput from django.forms import HiddenInput
from django.urls import reverse 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 _ from django.utils.translation import ugettext as _
@ -125,24 +125,6 @@ class StockLocationEdit(AjaxUpdateView):
ajax_form_title = _('Edit Stock Location') ajax_form_title = _('Edit Stock Location')
role_required = 'stock.change' 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): def get_form(self):
""" Customize form data for StockLocation editing. """ Customize form data for StockLocation editing.
@ -162,12 +144,12 @@ class StockLocationEdit(AjaxUpdateView):
# Is ownership control enabled? # Is ownership control enabled?
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
owner_initial = self.get_owner_initial() owner = location.owner
if not stock_ownership_control: if not stock_ownership_control:
form.fields['owner'].widget = HiddenInput() form.fields['owner'].widget = HiddenInput()
else: else:
form.fields['owner'].initial = owner_initial form.fields['owner'].initial = owner
if location.parent: if location.parent:
form.fields['owner'].initial = location.parent.owner form.fields['owner'].initial = location.parent.owner
if not self.request.user.is_superuser: if not self.request.user.is_superuser:
@ -180,21 +162,14 @@ class StockLocationEdit(AjaxUpdateView):
- update all children's owners with location's owner - update all children's owners with location's owner
""" """
self.object = form.save(commit=False) self.object = form.save()
# parent = form.cleaned_data.get('parent', None)
stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
if stock_ownership_control: if stock_ownership_control:
owner = form.cleaned_data.get('owner', None)
self.object.save(**{'owner': owner})
if self.object.get_children(): if self.object.get_children():
for child in self.object.get_children(): for child in self.object.get_children():
child.owner = self.object.owner child.owner = self.object.owner
child.save() child.save()
else:
self.object.save()
return self.object return self.object
@ -1386,10 +1361,18 @@ class StockItemEdit(AjaxUpdateView):
if not stock_ownership_control: if not stock_ownership_control:
form.fields['owner'].widget = HiddenInput() form.fields['owner'].widget = HiddenInput()
else: else:
if location: location_owner = location.owner
# Check if location has owner # Check if location has owner
if location.owner: if location_owner:
form.fields['owner'].queryset = User.objects.filter(groups=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 return form
@ -1492,13 +1475,12 @@ class StockLocationCreate(AjaxCreateView):
self.object = form.save(commit=False) self.object = form.save(commit=False)
parent = form.cleaned_data.get('parent', None) parent = form.cleaned_data.get('parent', None)
owner = form.cleaned_data.get('owner', None)
if parent: if parent:
# Select parent's owner
self.object.owner = parent.owner self.object.owner = parent.owner
self.object.save() self.object.save()
else:
self.object.save(**{'owner': owner})
return self.object return self.object
@ -1734,13 +1716,17 @@ class StockItemCreate(AjaxCreateView):
if not stock_ownership_control: if not stock_ownership_control:
form.fields['owner'].widget = HiddenInput() form.fields['owner'].widget = HiddenInput()
else: else:
if location: location_owner = location.owner
# Check if location has owner if location_owner:
if location.owner: # Check location owner type and filter
queryset = User.objects.filter(groups=location.owner) if type(location_owner.owner) is Group:
queryset = location_owner.get_users()
if self.request.user in queryset: if self.request.user in queryset:
form.fields['owner'].initial = self.request.user form.fields['owner'].initial = self.request.user
form.fields['owner'].queryset = queryset 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 return form

View File

@ -16,6 +16,11 @@ class UsersConfig(AppConfig):
except (OperationalError, ProgrammingError): except (OperationalError, ProgrammingError):
pass pass
try:
self.update_owners()
except (OperationalError, ProgrammingError):
pass
def assign_permissions(self): def assign_permissions(self):
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
@ -31,3 +36,9 @@ class UsersConfig(AppConfig):
for group in Group.objects.all(): for group in Group.objects.all():
update_group_roles(group) 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 -*- # -*- 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.contrib.contenttypes.models import ContentType
from django.db.models import UniqueConstraint
from django.db.utils import IntegrityError
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -385,3 +388,79 @@ def check_user_role(user, role, permission):
# No matching permissions found # No matching permissions found
return False 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)