Skip to content
This repository has been archived by the owner on May 13, 2024. It is now read-only.

Commit

Permalink
feat: assign users to customers, projects and tasks
Browse files Browse the repository at this point in the history
update serializers
create new through-tables for assigned users
update django-admin for user assignement
  • Loading branch information
trowik committed Apr 28, 2021
1 parent 4c2e207 commit 6ff4259
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 5 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pytz==2021.1
pyexcel-webio==0.1.4
pyexcel-io==0.6.4
django-excel==0.0.10
django-nested-inline==0.4.2
pyexcel-ods3==0.6.0
pyexcel-xlsx==0.6.0
pyexcel-ezodf==0.3.4
Expand Down
24 changes: 20 additions & 4 deletions timed/projects/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,34 @@
from django.forms.models import BaseInlineFormSet
from django.utils.translation import gettext_lazy as _

from nested_inline.admin import NestedModelAdmin, NestedStackedInline
from timed.forms import DurationInHoursField
from timed.projects import models
from timed.redmine.admin import RedmineProjectInline
from timed.subscription.admin import CustomerPasswordInline


class CustomerAssigneeInline(admin.TabularInline):
model = models.CustomerAssignee
extra = 0


class ProjectAssigneeInline(admin.TabularInline):
model = models.ProjectAssignee
extra = 0

class TaskAssigneeInline(NestedStackedInline):
model = models.TaskAssignee
extra = 1


@admin.register(models.Customer)
class CustomerAdmin(admin.ModelAdmin):
"""Customer admin view."""

list_display = ["name"]
search_fields = ["name"]
inlines = [CustomerPasswordInline]
inlines = [CustomerPasswordInline, CustomerAssigneeInline]

def has_delete_permission(self, request, obj=None):
return obj and not obj.projects.exists()
Expand Down Expand Up @@ -68,11 +83,12 @@ def __init__(self, *args, **kwargs):
self.extra += len(self.initial)


class TaskInline(admin.TabularInline):
class TaskInline(NestedStackedInline):
formset = TaskInlineFormset
form = TaskForm
model = models.Task
extra = 0
inlines = [TaskAssigneeInline]

def has_delete_permission(self, request, obj=None):
# for some reason obj is parent object and not task
Expand All @@ -96,15 +112,15 @@ class ProjectForm(forms.ModelForm):


@admin.register(models.Project)
class ProjectAdmin(admin.ModelAdmin):
class ProjectAdmin(NestedModelAdmin):
"""Project admin view."""

form = ProjectForm
list_display = ["name", "customer"]
list_filter = ["customer"]
search_fields = ["name", "customer__name"]

inlines = [TaskInline, ReviewerInline, RedmineProjectInline]
inlines = [TaskInline, ReviewerInline, RedmineProjectInline, ProjectAssigneeInline]
exclude = ("reviewers",)

def has_delete_permission(self, request, obj=None):
Expand Down
64 changes: 64 additions & 0 deletions timed/projects/migrations/0011_auto_20210419_1459.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Generated by Django 3.1.7 on 2021-04-19 12:59

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('projects', '0010_project_billed'),
]

operations = [
migrations.CreateModel(
name='TaskAssignee',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_resource', models.BooleanField(default=False)),
('is_reviewer', models.BooleanField(default=False)),
('is_manager', models.BooleanField(default=False)),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='task_assignees', to='projects.task')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='task_assignees', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='ProjectAssignee',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_resource', models.BooleanField(default=False)),
('is_reviewer', models.BooleanField(default=False)),
('is_manager', models.BooleanField(default=False)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_assignees', to='projects.project')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_assignees', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='CustomerAssignee',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_resource', models.BooleanField(default=False)),
('is_reviewer', models.BooleanField(default=False)),
('is_manager', models.BooleanField(default=False)),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customer_assignees', to='projects.customer')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customer_assignees', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='customer',
name='assignees',
field=models.ManyToManyField(related_name='assigned_to_customers', through='projects.CustomerAssignee', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='project',
name='assignees',
field=models.ManyToManyField(related_name='assigned_to_projects', through='projects.ProjectAssignee', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='task',
name='assignees',
field=models.ManyToManyField(related_name='assigned_to_tasks', through='projects.TaskAssignee', to=settings.AUTH_USER_MODEL),
),
]
72 changes: 72 additions & 0 deletions timed/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class Customer(models.Model):
website = models.URLField(blank=True)
comment = models.TextField(blank=True)
archived = models.BooleanField(default=False)
assignees = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through="CustomerAssignee",
related_name="assigned_to_customers",
)

def __str__(self):
"""Represent the model as a string.
Expand Down Expand Up @@ -97,6 +102,11 @@ class Project(models.Model):
amount_invoiced = MoneyField(
max_digits=10, decimal_places=2, default_currency="CHF", blank=True, null=True
)
assignees = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through="ProjectAssignee",
related_name="assigned_to_projects",
)

def __str__(self):
"""Represent the model as a string.
Expand Down Expand Up @@ -137,6 +147,11 @@ class Task(models.Model):
amount_invoiced = MoneyField(
max_digits=10, decimal_places=2, default_currency="CHF", blank=True, null=True
)
assignees = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through="TaskAssignee",
related_name="assigned_to_tasks",
)

def __str__(self):
"""Represent the model as a string.
Expand Down Expand Up @@ -171,3 +186,60 @@ def __str__(self):

class Meta:
ordering = ["name"]


class CustomerAssignee(models.Model):
"""Customer assignee model.
Customer assignee is an employee that is assigned to a specific customer.
"""

user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="customer_assignees",
)
customer = models.ForeignKey(
"projects.Customer", on_delete=models.CASCADE, related_name="customer_assignees"
)
is_resource = models.BooleanField(default=False)
is_reviewer = models.BooleanField(default=False)
is_manager = models.BooleanField(default=False)


class ProjectAssignee(models.Model):
"""Project assignee model.
Project assignee is an employee that is assigned to a specific project.
"""

user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="project_assignees",
)
project = models.ForeignKey(
"projects.Project", on_delete=models.CASCADE, related_name="project_assignees"
)
is_resource = models.BooleanField(default=False)
is_reviewer = models.BooleanField(default=False)
is_manager = models.BooleanField(default=False)


class TaskAssignee(models.Model):
"""Task assignee model.
Task assignee is an employee that is assigned to a specific task.
"""

user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="task_assignees",
)
task = models.ForeignKey(
"projects.Task", on_delete=models.CASCADE, related_name="task_assignees"
)
is_resource = models.BooleanField(default=False)
is_reviewer = models.BooleanField(default=False)
is_manager = models.BooleanField(default=False)
10 changes: 9 additions & 1 deletion timed/projects/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
class CustomerSerializer(ModelSerializer):
"""Customer serializer."""

included_serializers = {
"assignees": "timed.employment.serializers.UserSerializer",
}

class Meta:
"""Meta information for the customer serializer."""

model = models.Customer
fields = ["name", "reference", "email", "website", "comment", "archived"]
fields = ["name", "reference", "email", "website", "comment", "archived", "assignees",]


class BillingTypeSerializer(ModelSerializer):
Expand All @@ -43,6 +47,7 @@ class ProjectSerializer(ModelSerializer):
"billing_type": "timed.projects.serializers.BillingTypeSerializer",
"cost_center": "timed.projects.serializers.CostCenterSerializer",
"reviewers": "timed.employment.serializers.UserSerializer",
"assignees": "timed.employment.serializers.UserSerializer",
}

def get_root_meta(self, resource, many):
Expand Down Expand Up @@ -77,6 +82,7 @@ class Meta:
"cost_center",
"reviewers",
"customer_visible",
"assignees",
]


Expand All @@ -89,6 +95,7 @@ class TaskSerializer(ModelSerializer):
"activities": "timed.tracking.serializers.ActivitySerializer",
"project": "timed.projects.serializers.ProjectSerializer",
"cost_center": "timed.projects.serializers.CostCenterSerializer",
"assignees": "timed.employment.serializers.UserSerializer",
}

def get_root_meta(self, resource, many):
Expand All @@ -111,4 +118,5 @@ class Meta:
"archived",
"project",
"cost_center",
"assignees",
]
4 changes: 4 additions & 0 deletions timed/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ def default(default_dev=env.NOTSET, default_prod=env.NOTSET):
"django_filters",
"djmoney",
"mozilla_django_oidc",
<<<<<<< HEAD
"django_prometheus",
=======
"nested_inline",
>>>>>>> feat: assign users to customers, projects and tasks
"timed.employment",
"timed.projects",
"timed.tracking",
Expand Down

0 comments on commit 6ff4259

Please sign in to comment.