mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Switched to global owner model, need to validate use-cases table and fix tests
This commit is contained in:
parent
6a88bdb37d
commit
6f3cbb4e14
@ -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):
|
||||||
"""
|
"""
|
||||||
|
25
InvenTree/stock/migrations/0057_stock_location_item_owner.py
Normal file
25
InvenTree/stock/migrations/0057_stock_location_item_owner.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
27
InvenTree/users/migrations/0004_owner_model.py
Normal file
27
InvenTree/users/migrations/0004_owner_model.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user