mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Part responsible owner (#5774)
* Add "responsible_owner" field to part model - Will replace "responsible" field * Data migration - Adds 'responsible_owner' value for parts which have 'responsible' set - Selects correct content type - Performs reverse migratoin * Update part serializer - Point to the new field - Rename to preserve compatibility - OPTIONS metadata will take care of the rest * Remove old 'responsible' field * Bump API version * Fix typo * Fix serializer field
This commit is contained in:
parent
2dfe2d97bc
commit
39c499622d
@ -2,11 +2,14 @@
|
||||
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 140
|
||||
INVENTREE_API_VERSION = 141
|
||||
|
||||
"""
|
||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||
|
||||
v141 -> 2023-10-23 : https://github.com/inventree/InvenTree/pull/5774
|
||||
- Changed 'part.responsible' from User to Owner
|
||||
|
||||
v140 -> 2023-10-20 : https://github.com/inventree/InvenTree/pull/5664
|
||||
- Expand API token functionality
|
||||
- Multiple API tokens can be generated per user
|
||||
|
@ -96,7 +96,7 @@ for provider in providers.registry.get_list():
|
||||
social_auth_urlpatterns += provider_urlpatterns
|
||||
|
||||
|
||||
class SocialProvierListView(ListAPIView):
|
||||
class SocialProviderListView(ListAPIView):
|
||||
"""List of available social providers."""
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
|
@ -38,7 +38,7 @@ from web.urls import urlpatterns as platform_urls
|
||||
|
||||
from .api import APISearchView, InfoView, NotFoundView
|
||||
from .magic_login import GetSimpleLoginView
|
||||
from .social_auth_urls import SocialProvierListView, social_auth_urlpatterns
|
||||
from .social_auth_urls import SocialProviderListView, social_auth_urlpatterns
|
||||
from .views import (AboutView, AppearanceSelectView, CustomConnectionsView,
|
||||
CustomEmailView, CustomLoginView,
|
||||
CustomPasswordResetFromKeyView,
|
||||
@ -83,7 +83,7 @@ apipatterns = [
|
||||
path('auth/', include([
|
||||
re_path(r'^registration/account-confirm-email/(?P<key>[-:\w]+)/$', ConfirmEmailView.as_view(), name='account_confirm_email'),
|
||||
path('registration/', include('dj_rest_auth.registration.urls')),
|
||||
path('providers/', SocialProvierListView.as_view(), name='social_providers'),
|
||||
path('providers/', SocialProviderListView.as_view(), name='social_providers'),
|
||||
path('social/', include(social_auth_urlpatterns)),
|
||||
path('social/', SocialAccountListView.as_view(), name='social_account_list'),
|
||||
path('social/<int:pk>/disconnect/', SocialAccountDisconnectView.as_view(), name='social_account_disconnect'),
|
||||
|
20
InvenTree/part/migrations/0115_part_responsible_owner.py
Normal file
20
InvenTree/part/migrations/0115_part_responsible_owner.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.22 on 2023-10-23 01:59
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0009_auto_20231020_2356'),
|
||||
('part', '0114_alter_part_minimum_stock'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='part',
|
||||
name='responsible_owner',
|
||||
field=models.ForeignKey(blank=True, help_text='Owner responsible for this part', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parts_responsible', to='users.owner', verbose_name='Responsible'),
|
||||
),
|
||||
]
|
76
InvenTree/part/migrations/0116_auto_20231023_0332.py
Normal file
76
InvenTree/part/migrations/0116_auto_20231023_0332.py
Normal file
@ -0,0 +1,76 @@
|
||||
# Generated by Django 3.2.22 on 2023-10-23 03:32
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_part_responsible_owner(apps, schema_editor):
|
||||
"""Copy existing part.responsible field to part.responsible_owner"""
|
||||
|
||||
Owner = apps.get_model('users', 'Owner')
|
||||
Part = apps.get_model('part', 'Part')
|
||||
User = apps.get_model('auth', 'user')
|
||||
ContentType = apps.get_model('contenttypes', 'contenttype')
|
||||
|
||||
user_type = ContentType.objects.get_for_model(User)
|
||||
|
||||
parts = Part.objects.exclude(responsible=None)
|
||||
|
||||
for part in parts:
|
||||
|
||||
# Find a corresponding Owner object, or create one if it does not exist
|
||||
owner, _created = Owner.objects.get_or_create(
|
||||
owner_type=user_type,
|
||||
owner_id=part.responsible.id,
|
||||
)
|
||||
|
||||
part.responsible_owner = owner
|
||||
part.save()
|
||||
|
||||
if parts.count() > 0:
|
||||
print(f"Added 'responsible_owner' for {parts.count()} parts")
|
||||
|
||||
|
||||
def reverse_owner_migration(apps, schema_editor):
|
||||
"""Reverse the owner migration:
|
||||
|
||||
- Set the 'responsible' field to a selected user
|
||||
- Only where 'responsible_owner' is set
|
||||
- Only where 'responsible_owner' is a User object
|
||||
"""
|
||||
|
||||
Part = apps.get_model('part', 'Part')
|
||||
User = apps.get_model('auth', 'user')
|
||||
ContentType = apps.get_model('contenttypes', 'contenttype')
|
||||
|
||||
user_type = ContentType.objects.get_for_model(User)
|
||||
|
||||
parts = Part.objects.exclude(responsible_owner=None)
|
||||
|
||||
for part in parts:
|
||||
|
||||
if part.responsible_owner.owner_type == user_type:
|
||||
|
||||
# Attempt to find matching user
|
||||
try:
|
||||
user = User.objects.get(pk=part.responsible_owner.owner_id)
|
||||
part.responsible = user
|
||||
part.save()
|
||||
except User.DoesNotExist:
|
||||
print("User does not exist:", part.responsible_owner.owner_id)
|
||||
|
||||
if parts.count() > 0:
|
||||
print(f"Added 'responsible' for {parts.count()} parts")
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0115_part_responsible_owner'),
|
||||
('users', '0005_owner_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
migrate_part_responsible_owner,
|
||||
reverse_code=reverse_owner_migration,
|
||||
)
|
||||
]
|
17
InvenTree/part/migrations/0117_remove_part_responsible.py
Normal file
17
InvenTree/part/migrations/0117_remove_part_responsible.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 3.2.22 on 2023-10-23 05:28
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0116_auto_20231023_0332'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='part',
|
||||
name='responsible',
|
||||
),
|
||||
]
|
@ -41,6 +41,7 @@ import InvenTree.fields
|
||||
import InvenTree.ready
|
||||
import InvenTree.tasks
|
||||
import part.settings as part_settings
|
||||
import users.models
|
||||
from build import models as BuildModels
|
||||
from common.models import InvenTreeSetting
|
||||
from common.settings import currency_code_default
|
||||
@ -379,7 +380,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
|
||||
notes: Additional notes field for this part
|
||||
creation_date: Date that this part was added to the database
|
||||
creation_user: User who added this part to the database
|
||||
responsible: User who is responsible for this part (optional)
|
||||
responsible_owner: Owner (either user or group) which is responsible for this part (optional)
|
||||
last_stocktake: Date at which last stocktake was performed for this Part
|
||||
"""
|
||||
|
||||
@ -1036,7 +1037,13 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
|
||||
|
||||
creation_user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Creation User'), related_name='parts_created')
|
||||
|
||||
responsible = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Responsible'), help_text=_('User responsible for this part'), related_name='parts_responible')
|
||||
responsible_owner = models.ForeignKey(
|
||||
users.models.Owner, on_delete=models.SET_NULL,
|
||||
blank=True, null=True,
|
||||
verbose_name=_('Responsible'),
|
||||
help_text=_('Owner responsible for this part'),
|
||||
related_name='parts_responsible'
|
||||
)
|
||||
|
||||
last_stocktake = models.DateField(
|
||||
blank=True, null=True,
|
||||
|
@ -26,6 +26,7 @@ import part.filters
|
||||
import part.stocktake
|
||||
import part.tasks
|
||||
import stock.models
|
||||
import users.models
|
||||
from InvenTree.status_codes import BuildStatusGroups
|
||||
from InvenTree.tasks import offload_task
|
||||
|
||||
@ -695,6 +696,12 @@ class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serialize
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
responsible = serializers.PrimaryKeyRelatedField(
|
||||
queryset=users.models.Owner.objects.all(),
|
||||
required=False, allow_null=True,
|
||||
source='responsible_owner',
|
||||
)
|
||||
|
||||
# Annotated fields
|
||||
allocated_to_build_orders = serializers.FloatField(read_only=True)
|
||||
allocated_to_sales_orders = serializers.FloatField(read_only=True)
|
||||
|
@ -384,11 +384,11 @@
|
||||
<td>{% include 'clip_link.html' with link=part.link new_window=True %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if part.responsible %}
|
||||
{% if part.responsible_owner %}
|
||||
<tr>
|
||||
<td><span class='fas fa-user'></span></td>
|
||||
<td>{% trans "Responsible" %}</td>
|
||||
<td> <span class='badge badge-right rounded-pill bg-dark'>{{ part.responsible }}</span></td>
|
||||
<td> <span class='badge badge-right rounded-pill bg-dark'>{{ part.responsible_owner }}</span></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
Loading…
Reference in New Issue
Block a user